diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index a588d6d0c..d6f9e2cb0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -6,7 +6,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node: ['16.x'] + node: ['20.x'] name: Node ${{ matrix.node }} sample steps: - uses: actions/checkout@v2 diff --git a/.gitignore b/.gitignore index f96f6d322..bea819f62 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,4 @@ tests/states.json tests/configs.json .idea/ local-scripts -tests/debug-price-route.json \ No newline at end of file +tests/debug-price-route.json diff --git a/package.json b/package.json index 01f789c13..64171d680 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.9.3", + "version": "4.0.7", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", @@ -17,11 +17,11 @@ "@types/express": "^4.17.14", "@types/jest": "26.0.24", "@types/lodash": "4.14.178", - "@types/node": "^17.0.21", + "@types/node": "^20", "@types/uuid": "^9.0.1", "@types/websocket": "^1.0.10", - "@typescript-eslint/eslint-plugin": "^5.47.0", - "@typescript-eslint/parser": "^5.47.0", + "@typescript-eslint/eslint-plugin": "^8.17.0", + "@typescript-eslint/parser": "^8.17.0", "change-case": "^4.1.2", "dotenv": "16.0.0", "eslint": "^8.30.0", @@ -33,9 +33,9 @@ "jest": "^29.0.3", "prettier": "^2.8.1", "pretty-quick": "^3.1.3", - "ts-jest": "^29.0.3", - "ts-node": "^10.6.0", - "typescript": "4.6.4", + "ts-jest": "^29.2.5", + "ts-node": "^10.9.2", + "typescript": "^5.7.2", "yargs": "^17.0.1" }, "scripts": { @@ -52,12 +52,14 @@ }, "dependencies": { "@0x/utils": "^4.5.2", + "@balancer-labs/balancer-maths": "^0.0.19", "@balancer-labs/sor": "4.1.1-beta.4", "@bgd-labs/aave-address-book": "2.21.1", "@ethersproject/abi": "^5.7.0", "@hashflow/sdk": "^2.2.7", "@hashflow/taker-js": "^0.3.7", "@paraswap/core": "2.4.0", + "@types/ws": "^8.5.12", "async": "^3.2.4", "axios": "0.27.2", "bignumber.js": "9.1.0", @@ -69,6 +71,7 @@ "lodash": "4.17.21", "log4js": "6.6.1", "node-cache": "^5.1.2", + "protobufjs": "^7.4.0", "ts-essentials": "9.1.2", "uuid": "^9.0.0", "web3": "1.6.0", @@ -77,7 +80,7 @@ "web3-eth-abi": "1.4.0", "web3-eth-contract": "1.6.0", "web3-utils": "1.6.0", - "websocket": "1.0.35" + "ws": "^8.18.0" }, "sideEffects": false } diff --git a/scripts/run-dex-integration-v3.ts b/scripts/run-dex-integration-v3.ts new file mode 100644 index 000000000..cba5dd68d --- /dev/null +++ b/scripts/run-dex-integration-v3.ts @@ -0,0 +1,78 @@ +// npx ts-node scripts/run-dex-integration-v3.ts +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Network, SwapSide } from '../src/constants'; +import { BalancerV3 } from '../src/dex/balancer-v3/balancer-v3'; +import { DummyDexHelper } from '../src/dex-helper/index'; +import { BI_POWS } from '../src/bigint-constants'; + +const stataUSDC = { + address: '0x8a88124522dbbf1e56352ba3de1d9f78c143751e'.toLowerCase(), + decimals: 6, +}; + +const stataDAI = { + address: '0xde46e43f46ff74a23a65ebb0580cbe3dfe684a17'.toLowerCase(), + decimals: 18, +}; + +const stataUSDT = { + address: '0x978206fae13faf5a8d293fb614326b237684b750'.toLowerCase(), + decimals: 6, +}; + +const bal = { + address: '0xb19382073c7a0addbb56ac6af1808fa49e377b75'.toLowerCase(), + decimals: 18, +}; + +const daiAave = { + address: '0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357'.toLowerCase(), + decimals: 18, +}; + +const usdcAave = { + address: '0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8'.toLowerCase(), + decimals: 6, +}; + +const amounts = [0n, BI_POWS[6], BI_POWS[7], BI_POWS[8]]; + +async function main() { + const dexHelper = new DummyDexHelper(Network.SEPOLIA); + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + + const balancerV3 = new BalancerV3(Network.SEPOLIA, 'BalancerV3', dexHelper); + + await balancerV3.initializePricing(blocknumber); + + const from = stataUSDC; + const to = stataUSDT; + // const from = daiAave; + // const to = usdcAave; + + const pools = await balancerV3.getPoolIdentifiers( + from, + to, + SwapSide.SELL, + blocknumber, + ); + console.log('Pool Identifiers: ', from.address, to.address, pools); + + const prices = await balancerV3.getPricesVolume( + from, + to, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log('Pool Prices: ', prices); + + const poolLiquidity = await balancerV3.getTopPoolsForToken(from.address, 10); + console.log('Top Pools:', poolLiquidity); +} + +main(); diff --git a/src/abi/SSPM.json b/src/abi/SSPM.json new file mode 100644 index 000000000..6842a14bf --- /dev/null +++ b/src/abi/SSPM.json @@ -0,0 +1,829 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CallerNotManager", + "type": "error" + }, + { + "inputs": [], + "name": "CallerNotStaderContract", + "type": "error" + }, + { + "inputs": [], + "name": "CooldownNotComplete", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidDepositAmount", + "type": "error" + }, + { + "inputs": [], + "name": "PoolIdDoesNotExit", + "type": "error" + }, + { + "inputs": [], + "name": "TransferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "UnsupportedOperation", + "type": "error" + }, + { + "inputs": [], + "name": "UnsupportedOperationInSafeMode", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "AuctionedEthReceived", + "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" + }, + { + "indexed": false, + "internalType": "string", + "name": "referralId", + "type": "string" + } + ], + "name": "DepositReferral", + "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": "Deposited", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "poolId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "poolAddress", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "validatorCount", + "type": "uint256" + } + ], + "name": "ETHTransferredToPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "ExecutionLayerRewardsReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + } + ], + "name": "ReceivedExcessEthFromPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TransferredETHToUserWithdrawManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "excessETHDepositCoolDown", + "type": "uint256" + } + ], + "name": "UpdatedExcessETHDepositCoolDown", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "staderConfig", + "type": "address" + } + ], + "name": "UpdatedStaderConfig", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawVaultUserShareReceived", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_shares", + "type": "uint256" + } + ], + "name": "convertToAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_assets", + "type": "uint256" + } + ], + "name": "convertToShares", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_receiver", + "type": "address" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "depositETHOverTargetWeight", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "excessETHDepositCoolDown", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "address", + "name": "_staderConfig", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isVaultHealthy", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastExcessETHDepositBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "maxDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "minDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "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": "previewWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "receiveEthFromAuction", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "_poolId", + "type": "uint8" + } + ], + "name": "receiveExcessEthFromPool", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "receiveExecutionLayerRewards", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "receiveWithdrawVaultUserShare", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "staderConfig", + "outputs": [ + { + "internalType": "contract IStaderConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAssets", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "transferETHToUserWithdrawManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_excessETHDepositCoolDown", + "type": "uint256" + } + ], + "name": "updateExcessETHDepositCoolDown", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_staderConfig", + "type": "address" + } + ], + "name": "updateStaderConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "_poolId", + "type": "uint8" + } + ], + "name": "validatorBatchDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/StaderOracle.json b/src/abi/StaderOracle.json new file mode 100644 index 000000000..442ce907d --- /dev/null +++ b/src/abi/StaderOracle.json @@ -0,0 +1,2170 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "CallerNotManager", + "type": "error" + }, + { + "inputs": [], + "name": "CooldownNotComplete", + "type": "error" + }, + { + "inputs": [], + "name": "DuplicateSubmissionFromNode", + "type": "error" + }, + { + "inputs": [], + "name": "ERChangeLimitCrossed", + "type": "error" + }, + { + "inputs": [], + "name": "ERChangeLimitNotCrossed", + "type": "error" + }, + { + "inputs": [], + "name": "ERPermissibleChangeOutofBounds", + "type": "error" + }, + { + "inputs": [], + "name": "FrequencyUnchanged", + "type": "error" + }, + { + "inputs": [], + "name": "InspectionModeActive", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientTrustedNodes", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidERDataSource", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMAPDIndex", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidMerkleRootIndex", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidPubkeyLength", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidReportingBlock", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidUpdate", + "type": "error" + }, + { + "inputs": [], + "name": "NodeAlreadyTrusted", + "type": "error" + }, + { + "inputs": [], + "name": "NodeNotTrusted", + "type": "error" + }, + { + "inputs": [], + "name": "NotATrustedNode", + "type": "error" + }, + { + "inputs": [], + "name": "PageNumberAlreadyReported", + "type": "error" + }, + { + "inputs": [], + "name": "ReportingFutureBlockData", + "type": "error" + }, + { + "inputs": [], + "name": "ReportingPreviousCycleData", + "type": "error" + }, + { + "inputs": [], + "name": "UpdateFrequencyNotSet", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroAddress", + "type": "error" + }, + { + "inputs": [], + "name": "ZeroFrequency", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "isPORBasedERData", + "type": "bool" + } + ], + "name": "ERDataSourceToggled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "erInspectionMode", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ERInspectionModeActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalEth", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethxSupply", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ExchangeRateSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalEth", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "ethxSupply", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ExchangeRateUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "pubkeys", + "type": "bytes[]" + } + ], + "name": "MissedAttestationPenaltySubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "pubkeys", + "type": "bytes[]" + } + ], + "name": "MissedAttestationPenaltyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sdPriceInETH", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportedBlock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + } + ], + "name": "SDPriceSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "sdPriceInETH", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "reportedBlock", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + } + ], + "name": "SDPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "SafeModeDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "SafeModeEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + } + ], + "name": "SocializingRewardsMerkleRootSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + } + ], + "name": "SocializingRewardsMerkleRootUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + } + ], + "name": "TrustedNodeAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "trustedNodeChangeCoolingPeriod", + "type": "uint256" + } + ], + "name": "TrustedNodeChangeCoolingPeriodUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "node", + "type": "address" + } + ], + "name": "TrustedNodeRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "updateFrequency", + "type": "uint256" + } + ], + "name": "UpdateFrequencyUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "erChangeLimit", + "type": "uint256" + } + ], + "name": "UpdatedERChangeLimit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "staderConfig", + "type": "address" + } + ], + "name": "UpdatedStaderConfig", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "activeValidatorsBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exitedValidatorsBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashedValidatorsBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "activeValidatorsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exitedValidatorsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashedValidatorsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ValidatorStatsSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "activeValidatorsBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exitedValidatorsBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashedValidatorsBalance", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "activeValidatorsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exitedValidatorsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "slashedValidatorsCount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ValidatorStatsUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "sortedReadyToDepositPubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "sortedFrontRunPubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "sortedInvalidSignaturePubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ValidatorVerificationDetailSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "sortedReadyToDepositPubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "sortedFrontRunPubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "sortedInvalidSignaturePubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "ValidatorVerificationDetailUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "pubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "WithdrawnValidatorsSubmitted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "block", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes[]", + "name": "pubkeys", + "type": "bytes[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "time", + "type": "uint256" + } + ], + "name": "WithdrawnValidatorsUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ER_CHANGE_MAX_BPS", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ETHX_ER_UF", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_ER_UPDATE_FREQUENCY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_TRUSTED_NODES", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MISSED_ATTESTATION_PENALTY_UF", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "SD_PRICE_UF", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATOR_STATS_UF", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VALIDATOR_VERIFICATION_DETAIL_UF", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "WITHDRAWN_VALIDATORS_UF", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "addTrustedNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "closeERInspectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "disableERInspectionMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "disableSafeMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "enableSafeMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "erChangeLimit", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "erInspectionMode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "erInspectionModeStartBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "exchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHXSupply", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "_poolId", + "type": "uint8" + } + ], + "name": "getCurrentRewardsIndexByPoolId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getERReportableBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getExchangeRate", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHXSupply", + "type": "uint256" + } + ], + "internalType": "struct ExchangeRate", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "_poolId", + "type": "uint8" + } + ], + "name": "getMerkleRootReportableBlockByPoolId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMissedAttestationPenaltyReportableBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSDPriceInETH", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getSDPriceReportableBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getValidatorStats", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "exitingValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "exitedValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "slashedValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "exitingValidatorsCount", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "exitedValidatorsCount", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "slashedValidatorsCount", + "type": "uint32" + } + ], + "internalType": "struct ValidatorStats", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getValidatorStatsReportableBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getValidatorVerificationDetailReportableBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getWithdrawnValidatorReportableBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_admin", + "type": "address" + }, + { + "internalType": "address", + "name": "_staderConfig", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "inspectionModeExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHXSupply", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isPORFeedBasedERData", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "isTrustedNode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastReportedMAPDIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastReportedSDPriceData", + "outputs": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sdPriceInETH", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "name": "lastReportingBlockNumberForValidatorVerificationDetailByPoolId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "name": "lastReportingBlockNumberForWithdrawnValidatorsByPoolId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lastTrustedNodeCountChangeBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "missedAttestationPenalty", + "outputs": [ + { + "internalType": "uint16", + "name": "", + "type": "uint16" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_nodeAddress", + "type": "address" + } + ], + "name": "removeTrustedNode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "safeMode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_updateFrequency", + "type": "uint256" + } + ], + "name": "setERUpdateFrequency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_updateFrequency", + "type": "uint256" + } + ], + "name": "setMissedAttestationPenaltyUpdateFrequency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_updateFrequency", + "type": "uint256" + } + ], + "name": "setSDPriceUpdateFrequency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_updateFrequency", + "type": "uint256" + } + ], + "name": "setValidatorStatsUpdateFrequency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_updateFrequency", + "type": "uint256" + } + ], + "name": "setValidatorVerificationDetailUpdateFrequency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_updateFrequency", + "type": "uint256" + } + ], + "name": "setWithdrawnValidatorsUpdateFrequency", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "staderConfig", + "outputs": [ + { + "internalType": "contract IStaderConfig", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHBalance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalETHXSupply", + "type": "uint256" + } + ], + "internalType": "struct ExchangeRate", + "name": "_exchangeRate", + "type": "tuple" + } + ], + "name": "submitExchangeRateData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "sortedPubkeys", + "type": "bytes[]" + } + ], + "internalType": "struct MissedAttestationPenaltyData", + "name": "_mapd", + "type": "tuple" + } + ], + "name": "submitMissedAttestationPenalties", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "sdPriceInETH", + "type": "uint256" + } + ], + "internalType": "struct SDPriceData", + "name": "_sdPriceData", + "type": "tuple" + } + ], + "name": "submitSDPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "merkleRoot", + "type": "bytes32" + }, + { + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "operatorETHRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "userETHRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "protocolETHRewards", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "operatorSDRewards", + "type": "uint256" + } + ], + "internalType": "struct RewardsData", + "name": "_rewardsData", + "type": "tuple" + } + ], + "name": "submitSocializingRewardsMerkleRoot", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "exitingValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "exitedValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "slashedValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "exitingValidatorsCount", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "exitedValidatorsCount", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "slashedValidatorsCount", + "type": "uint32" + } + ], + "internalType": "struct ValidatorStats", + "name": "_validatorStats", + "type": "tuple" + } + ], + "name": "submitValidatorStats", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "sortedReadyToDepositPubkeys", + "type": "bytes[]" + }, + { + "internalType": "bytes[]", + "name": "sortedFrontRunPubkeys", + "type": "bytes[]" + }, + { + "internalType": "bytes[]", + "name": "sortedInvalidSignaturePubkeys", + "type": "bytes[]" + } + ], + "internalType": "struct ValidatorVerificationDetail", + "name": "_validatorVerificationDetail", + "type": "tuple" + } + ], + "name": "submitValidatorVerificationDetail", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint8", + "name": "poolId", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "sortedPubkeys", + "type": "bytes[]" + } + ], + "internalType": "struct WithdrawnValidators", + "name": "_withdrawnValidators", + "type": "tuple" + } + ], + "name": "submitWithdrawnValidators", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "togglePORFeedBasedERData", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "trustedNodeChangeCoolingPeriod", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "trustedNodesCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_erChangeLimit", + "type": "uint256" + } + ], + "name": "updateERChangeLimit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "updateERFromPORFeed", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "updateFrequencyMap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_staderConfig", + "type": "address" + } + ], + "name": "updateStaderConfig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_trustedNodeChangeCoolingPeriod", + "type": "uint256" + } + ], + "name": "updateTrustedNodeChangeCoolingPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "validatorStats", + "outputs": [ + { + "internalType": "uint256", + "name": "reportingBlockNumber", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "exitingValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "exitedValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint128", + "name": "slashedValidatorsBalance", + "type": "uint128" + }, + { + "internalType": "uint32", + "name": "exitingValidatorsCount", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "exitedValidatorsCount", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "slashedValidatorsCount", + "type": "uint32" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/balancer-v3/batch-router.json b/src/abi/balancer-v3/batch-router.json new file mode 100644 index 000000000..878e3173a --- /dev/null +++ b/src/abi/balancer-v3/batch-router.json @@ -0,0 +1,989 @@ +[ + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract IWETH", + "name": "weth", + "type": "address" + }, + { + "internalType": "contract IPermit2", + "name": "permit2", + "type": "address" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorSelectorNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "EthTransfer", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "InputLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientEth", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderIsNotVault", + "type": "error" + }, + { + "inputs": [], + "name": "SwapDeadline", + "type": "error" + }, + { + "inputs": [], + "name": "TransientIndexOutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "getSender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IRouterCommon.PermitApproval[]", + "name": "permitBatch", + "type": "tuple[]" + }, + { + "internalType": "bytes[]", + "name": "permitSignatures", + "type": "bytes[]" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "internalType": "struct IAllowanceTransfer.PermitDetails[]", + "name": "details", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "sigDeadline", + "type": "uint256" + } + ], + "internalType": "struct IAllowanceTransfer.PermitBatch", + "name": "permit2Batch", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "permit2Signature", + "type": "bytes" + }, + { + "internalType": "bytes[]", + "name": "multicallData", + "type": "bytes[]" + } + ], + "name": "permitBatchAndCall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "exactAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "querySwapExactIn", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensOut", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "exactAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IBatchRouter.SwapExactInHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "querySwapExactInHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensOut", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "querySwapExactOut", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensIn", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IBatchRouter.SwapExactOutHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "querySwapExactOutHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensIn", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "exactAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "swapExactIn", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensOut", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "exactAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IBatchRouter.SwapExactInHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "swapExactInHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensOut", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "swapExactOut", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensIn", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "bool", + "name": "isBuffer", + "type": "bool" + } + ], + "internalType": "struct IBatchRouter.SwapPathStep[]", + "name": "steps", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + } + ], + "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", + "name": "paths", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IBatchRouter.SwapExactOutHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "swapExactOutHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "pathAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "address[]", + "name": "tokensIn", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/balancer-v3/router.json b/src/abi/balancer-v3/router.json new file mode 100644 index 000000000..e28912159 --- /dev/null +++ b/src/abi/balancer-v3/router.json @@ -0,0 +1,1792 @@ +[ + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "vault", + "type": "address" + }, + { + "internalType": "contract IWETH", + "name": "weth", + "type": "address" + }, + { + "internalType": "contract IPermit2", + "name": "permit2", + "type": "address" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorSelectorNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "EthTransfer", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "InputLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InsufficientEth", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint8", + "name": "bits", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintDowncast", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderIsNotVault", + "type": "error" + }, + { + "inputs": [], + "name": "SwapDeadline", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "addLiquidityCustom", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "enum AddLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouterCommon.AddLiquidityHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "addLiquidityHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "exactBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "addLiquidityProportional", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "exactBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "addLiquiditySingleTokenExactOut", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "exactAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "addLiquidityUnbalanced", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "donate", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getSender", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "exactAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "initialize", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "exactAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouter.InitializeHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "initializeHook", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + } + ], + "internalType": "struct IRouterCommon.PermitApproval[]", + "name": "permitBatch", + "type": "tuple[]" + }, + { + "internalType": "bytes[]", + "name": "permitSignatures", + "type": "bytes[]" + }, + { + "components": [ + { + "components": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "internalType": "struct IAllowanceTransfer.PermitDetails[]", + "name": "details", + "type": "tuple[]" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "sigDeadline", + "type": "uint256" + } + ], + "internalType": "struct IAllowanceTransfer.PermitBatch", + "name": "permit2Batch", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "permit2Signature", + "type": "bytes" + }, + { + "internalType": "bytes[]", + "name": "multicallData", + "type": "bytes[]" + } + ], + "name": "permitBatchAndCall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryAddLiquidityCustom", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "maxAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "enum AddLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouterCommon.AddLiquidityHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "queryAddLiquidityHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryAddLiquidityProportional", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsIn", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryAddLiquiditySingleTokenExactOut", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "exactAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryAddLiquidityUnbalanced", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryRemoveLiquidityCustom", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "maxBptAmountIn", + "type": "uint256" + }, + { + "internalType": "enum RemoveLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouterCommon.RemoveLiquidityHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "queryRemoveLiquidityHook", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryRemoveLiquidityProportional", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + } + ], + "name": "queryRemoveLiquidityRecovery", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + } + ], + "name": "queryRemoveLiquidityRecoveryHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryRemoveLiquiditySingleTokenExactIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "queryRemoveLiquiditySingleTokenExactOut", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "enum SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountGiven", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouter.SwapSingleTokenHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "querySwapHook", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactAmountIn", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "querySwapSingleTokenExactIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCalculated", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "querySwapSingleTokenExactOut", + "outputs": [ + { + "internalType": "uint256", + "name": "amountCalculated", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "removeLiquidityCustom", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "maxBptAmountIn", + "type": "uint256" + }, + { + "internalType": "enum RemoveLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouterCommon.RemoveLiquidityHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "removeLiquidityHook", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "returnData", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "removeLiquidityProportional", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + } + ], + "name": "removeLiquidityRecovery", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + } + ], + "name": "removeLiquidityRecoveryHook", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOut", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "removeLiquiditySingleTokenExactIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxBptAmountIn", + "type": "uint256" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "removeLiquiditySingleTokenExactOut", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountIn", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "swapSingleTokenExactIn", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactAmountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "swapSingleTokenExactOut", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "enum SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountGiven", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "wethIsEth", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct IRouter.SwapSingleTokenHookParams", + "name": "params", + "type": "tuple" + } + ], + "name": "swapSingleTokenHook", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/balancer-v3/vault-extension.json b/src/abi/balancer-v3/vault-extension.json new file mode 100644 index 000000000..c4f0f76c0 --- /dev/null +++ b/src/abi/balancer-v3/vault-extension.json @@ -0,0 +1,2834 @@ +[ + { + "inputs": [ + { + "internalType": "contract IVault", + "name": "mainVault", + "type": "address" + }, + { + "internalType": "contract IVaultAdmin", + "name": "vaultAdmin", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "AfterAddLiquidityHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "AfterInitializeHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "AfterRemoveLiquidityHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "AfterSwapHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "AmountGivenZero", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + } + ], + "name": "AmountInAboveMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "name": "AmountOutBelowMin", + "type": "error" + }, + { + "inputs": [], + "name": "BalanceNotSettled", + "type": "error" + }, + { + "inputs": [], + "name": "BalanceOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "BeforeAddLiquidityHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "BeforeInitializeHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "BeforeRemoveLiquidityHookFailed", + "type": "error" + }, + { + "inputs": [], + "name": "BeforeSwapHookFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + } + ], + "name": "BptAmountInAboveMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "name": "BptAmountOutBelowMin", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "BufferAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "BufferNotInitialized", + "type": "error" + }, + { + "inputs": [], + "name": "BufferSharesInvalidOwner", + "type": "error" + }, + { + "inputs": [], + "name": "BufferSharesInvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + } + ], + "name": "BufferTotalSupplyTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "CannotReceiveEth", + "type": "error" + }, + { + "inputs": [], + "name": "CannotSwapSameToken", + "type": "error" + }, + { + "inputs": [], + "name": "CodecOverflow", + "type": "error" + }, + { + "inputs": [], + "name": "DoesNotSupportAddLiquidityCustom", + "type": "error" + }, + { + "inputs": [], + "name": "DoesNotSupportDonation", + "type": "error" + }, + { + "inputs": [], + "name": "DoesNotSupportRemoveLiquidityCustom", + "type": "error" + }, + { + "inputs": [], + "name": "DoesNotSupportUnbalancedLiquidity", + "type": "error" + }, + { + "inputs": [], + "name": "DynamicSwapFeeHookFailed", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "allowance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "needed", + "type": "uint256" + } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "approver", + "type": "address" + } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "receiver", + "type": "address" + } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [], + "name": "ErrorSelectorNotFound", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "FeePrecisionTooHigh", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxAmountIn", + "type": "uint256" + } + ], + "name": "HookAdjustedAmountInAboveMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minAmountOut", + "type": "uint256" + } + ], + "name": "HookAdjustedAmountOutBelowMin", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "HookAdjustedSwapLimit", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "poolHooksContract", + "type": "address" + }, + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "poolFactory", + "type": "address" + } + ], + "name": "HookRegistrationFailed", + "type": "error" + }, + { + "inputs": [], + "name": "InputLengthMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidAddLiquidityKind", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidRemoveLiquidityKind", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidToken", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTokenConfiguration", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTokenDecimals", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidTokenType", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "InvalidUnderlyingToken", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "issuedShares", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minIssuedShares", + "type": "uint256" + } + ], + "name": "IssuedSharesBelowMin", + "type": "error" + }, + { + "inputs": [], + "name": "MaxTokens", + "type": "error" + }, + { + "inputs": [], + "name": "MinTokens", + "type": "error" + }, + { + "inputs": [], + "name": "NotEnoughBufferShares", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "expectedUnderlyingAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actualUnderlyingAmount", + "type": "uint256" + } + ], + "name": "NotEnoughUnderlying", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "expectedWrappedAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "actualWrappedAmount", + "type": "uint256" + } + ], + "name": "NotEnoughWrapped", + "type": "error" + }, + { + "inputs": [], + "name": "NotStaticCall", + "type": "error" + }, + { + "inputs": [], + "name": "NotVaultDelegateCall", + "type": "error" + }, + { + "inputs": [], + "name": "OutOfBounds", + "type": "error" + }, + { + "inputs": [], + "name": "PauseBufferPeriodDurationTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "PercentageAboveMax", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolAlreadyInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolAlreadyRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolInRecoveryMode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotInRecoveryMode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotInitialized", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolNotRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolPauseWindowExpired", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + } + ], + "name": "PoolTotalSupplyTooLow", + "type": "error" + }, + { + "inputs": [], + "name": "ProtocolFeesExceedTotalCollected", + "type": "error" + }, + { + "inputs": [], + "name": "QueriesDisabled", + "type": "error" + }, + { + "inputs": [], + "name": "QueriesDisabledPermanently", + "type": "error" + }, + { + "inputs": [], + "name": "QuoteResultSpoofed", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "name": "Result", + "type": "error" + }, + { + "inputs": [], + "name": "RouterNotTrusted", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "SafeCastOverflowedUintToInt", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "SenderIsNotVault", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFeePercentageTooHigh", + "type": "error" + }, + { + "inputs": [], + "name": "SwapFeePercentageTooLow", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "limit", + "type": "uint256" + } + ], + "name": "SwapLimit", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "TokenAlreadyRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "TokenNotRegistered", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "expectedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "actualToken", + "type": "address" + } + ], + "name": "TokensMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "TokensNotSorted", + "type": "error" + }, + { + "inputs": [], + "name": "TradeAmountTooSmall", + "type": "error" + }, + { + "inputs": [], + "name": "VaultBuffersArePaused", + "type": "error" + }, + { + "inputs": [], + "name": "VaultIsNotUnlocked", + "type": "error" + }, + { + "inputs": [], + "name": "VaultNotPaused", + "type": "error" + }, + { + "inputs": [], + "name": "VaultPauseWindowDurationTooLarge", + "type": "error" + }, + { + "inputs": [], + "name": "VaultPauseWindowExpired", + "type": "error" + }, + { + "inputs": [], + "name": "VaultPaused", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "WrapAmountTooSmall", + "type": "error" + }, + { + "inputs": [], + "name": "WrongProtocolFeeControllerDeployment", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "internalType": "address", + "name": "underlyingToken", + "type": "address" + } + ], + "name": "WrongUnderlyingToken", + "type": "error" + }, + { + "inputs": [], + "name": "WrongVaultAdminDeployment", + "type": "error" + }, + { + "inputs": [], + "name": "WrongVaultExtensionDeployment", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "aggregateSwapFeePercentage", + "type": "uint256" + } + ], + "name": "AggregateSwapFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "aggregateYieldFeePercentage", + "type": "uint256" + } + ], + "name": "AggregateYieldFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "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": "contract IAuthorizer", + "name": "newAuthorizer", + "type": "address" + } + ], + "name": "AuthorizerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "burnedShares", + "type": "uint256" + } + ], + "name": "BufferSharesBurned", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "issuedShares", + "type": "uint256" + } + ], + "name": "BufferSharesMinted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum AddLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "amountsAddedRaw", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "swapFeeAmountsRaw", + "type": "uint256[]" + } + ], + "name": "LiquidityAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountUnderlying", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountWrapped", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "bufferBalances", + "type": "bytes32" + } + ], + "name": "LiquidityAddedToBuffer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "liquidityProvider", + "type": "address" + }, + { + "indexed": true, + "internalType": "enum RemoveLiquidityKind", + "name": "kind", + "type": "uint8" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalSupply", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "amountsRemovedRaw", + "type": "uint256[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "swapFeeAmountsRaw", + "type": "uint256[]" + } + ], + "name": "LiquidityRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountUnderlying", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountWrapped", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "bufferBalances", + "type": "bytes32" + } + ], + "name": "LiquidityRemovedFromBuffer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "PoolInitialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "PoolPausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "bool", + "name": "recoveryMode", + "type": "bool" + } + ], + "name": "PoolRecoveryModeStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "paysYieldFees", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct TokenConfig[]", + "name": "tokenConfig", + "type": "tuple[]" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint32", + "name": "pauseWindowEndTime", + "type": "uint32" + }, + { + "components": [ + { + "internalType": "address", + "name": "pauseManager", + "type": "address" + }, + { + "internalType": "address", + "name": "swapFeeManager", + "type": "address" + }, + { + "internalType": "address", + "name": "poolCreator", + "type": "address" + } + ], + "indexed": false, + "internalType": "struct PoolRoleAccounts", + "name": "roleAccounts", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "enableHookAdjustedAmounts", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallComputeDynamicSwapFee", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "address", + "name": "hooksContract", + "type": "address" + } + ], + "indexed": false, + "internalType": "struct HooksConfig", + "name": "hooksConfig", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "bool", + "name": "disableUnbalancedLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableRemoveLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableDonation", + "type": "bool" + } + ], + "indexed": false, + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + } + ], + "name": "PoolRegistered", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IProtocolFeeController", + "name": "newProtocolFeeController", + "type": "address" + } + ], + "name": "ProtocolFeeControllerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenIn", + "type": "address" + }, + { + "indexed": true, + "internalType": "contract IERC20", + "name": "tokenOut", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeeAmount", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + } + ], + "name": "SwapFeePercentageChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "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": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "burnedShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "withdrawnUnderlying", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "bufferBalances", + "type": "bytes32" + } + ], + "name": "Unwrap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": false, + "internalType": "string", + "name": "eventKey", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "eventData", + "type": "bytes" + } + ], + "name": "VaultAuxiliary", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "VaultBuffersPausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "paused", + "type": "bool" + } + ], + "name": "VaultPausedStateChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "VaultQueriesDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "VaultQueriesEnabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "depositedUnderlying", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mintedShares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "bufferBalances", + "type": "bytes32" + } + ], + "name": "Wrap", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "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": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "components": [ + { + "internalType": "enum SwapKind", + "name": "kind", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "amountGivenScaled18", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "balancesScaled18", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "indexIn", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "indexOut", + "type": "uint256" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "internalType": "struct PoolSwapParams", + "name": "swapParams", + "type": "tuple" + } + ], + "name": "computeDynamicSwapFeePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "dynamicSwapFeePercentage", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "eventKey", + "type": "string" + }, + { + "internalType": "bytes", + "name": "eventData", + "type": "bytes" + } + ], + "name": "emitAuxiliaryEvent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getAddLiquidityCalledFlag", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getAggregateSwapFeeAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getAggregateYieldFeeAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getBptRate", + "outputs": [ + { + "internalType": "uint256", + "name": "rate", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getCurrentLiveBalances", + "outputs": [ + { + "internalType": "uint256[]", + "name": "balancesLiveScaled18", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "getERC4626BufferAsset", + "outputs": [ + { + "internalType": "address", + "name": "asset", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getHooksConfig", + "outputs": [ + { + "components": [ + { + "internalType": "bool", + "name": "enableHookAdjustedAmounts", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterInitialize", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallComputeDynamicSwapFee", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterSwap", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterAddLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallBeforeRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "shouldCallAfterRemoveLiquidity", + "type": "bool" + }, + { + "internalType": "address", + "name": "hooksContract", + "type": "address" + } + ], + "internalType": "struct HooksConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getNonzeroDeltaCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolConfig", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "bool", + "name": "disableUnbalancedLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableRemoveLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableDonation", + "type": "bool" + } + ], + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "staticSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "aggregateSwapFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "aggregateYieldFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint40", + "name": "tokenDecimalDiffs", + "type": "uint40" + }, + { + "internalType": "uint32", + "name": "pauseWindowEndTime", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "isPoolRegistered", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPoolInitialized", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPoolPaused", + "type": "bool" + }, + { + "internalType": "bool", + "name": "isPoolInRecoveryMode", + "type": "bool" + } + ], + "internalType": "struct PoolConfig", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolData", + "outputs": [ + { + "components": [ + { + "internalType": "PoolConfigBits", + "name": "poolConfigBits", + "type": "bytes32" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "paysYieldFees", + "type": "bool" + } + ], + "internalType": "struct TokenInfo[]", + "name": "tokenInfo", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "balancesRaw", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "balancesLiveScaled18", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "tokenRates", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "decimalScalingFactors", + "type": "uint256[]" + } + ], + "internalType": "struct PoolData", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolPausedState", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + }, + { + "internalType": "uint32", + "name": "", + "type": "uint32" + }, + { + "internalType": "uint32", + "name": "", + "type": "uint32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolRoleAccounts", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pauseManager", + "type": "address" + }, + { + "internalType": "address", + "name": "swapFeeManager", + "type": "address" + }, + { + "internalType": "address", + "name": "poolCreator", + "type": "address" + } + ], + "internalType": "struct PoolRoleAccounts", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolTokenInfo", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "components": [ + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "paysYieldFees", + "type": "bool" + } + ], + "internalType": "struct TokenInfo[]", + "name": "tokenInfo", + "type": "tuple[]" + }, + { + "internalType": "uint256[]", + "name": "balancesRaw", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "lastBalancesLiveScaled18", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolTokenRates", + "outputs": [ + { + "internalType": "uint256[]", + "name": "decimalScalingFactors", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "tokenRates", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getPoolTokens", + "outputs": [ + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProtocolFeeController", + "outputs": [ + { + "internalType": "contract IProtocolFeeController", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getReservesOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getStaticSwapFeePercentage", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + } + ], + "name": "getTokenDelta", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getVaultAdmin", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "contract IERC20[]", + "name": "tokens", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "exactAmountsIn", + "type": "uint256[]" + }, + { + "internalType": "uint256", + "name": "minBptAmountOut", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "userData", + "type": "bytes" + } + ], + "name": "initialize", + "outputs": [ + { + "internalType": "uint256", + "name": "bptAmountOut", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "contract IERC4626", + "name": "wrappedToken", + "type": "address" + } + ], + "name": "isERC4626BufferInitialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPoolInRecoveryMode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPoolInitialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPoolPaused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "isPoolRegistered", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isQueryDisabled", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isQueryDisabledPermanently", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isUnlocked", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "quote", + "outputs": [ + { + "internalType": "bytes", + "name": "result", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "quoteAndRevert", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "reentrancyGuardEntered", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "components": [ + { + "internalType": "contract IERC20", + "name": "token", + "type": "address" + }, + { + "internalType": "enum TokenType", + "name": "tokenType", + "type": "uint8" + }, + { + "internalType": "contract IRateProvider", + "name": "rateProvider", + "type": "address" + }, + { + "internalType": "bool", + "name": "paysYieldFees", + "type": "bool" + } + ], + "internalType": "struct TokenConfig[]", + "name": "tokenConfig", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "swapFeePercentage", + "type": "uint256" + }, + { + "internalType": "uint32", + "name": "pauseWindowEndTime", + "type": "uint32" + }, + { + "internalType": "bool", + "name": "protocolFeeExempt", + "type": "bool" + }, + { + "components": [ + { + "internalType": "address", + "name": "pauseManager", + "type": "address" + }, + { + "internalType": "address", + "name": "swapFeeManager", + "type": "address" + }, + { + "internalType": "address", + "name": "poolCreator", + "type": "address" + } + ], + "internalType": "struct PoolRoleAccounts", + "name": "roleAccounts", + "type": "tuple" + }, + { + "internalType": "address", + "name": "poolHooksContract", + "type": "address" + }, + { + "components": [ + { + "internalType": "bool", + "name": "disableUnbalancedLiquidity", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableAddLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableRemoveLiquidityCustom", + "type": "bool" + }, + { + "internalType": "bool", + "name": "enableDonation", + "type": "bool" + } + ], + "internalType": "struct LiquidityManagement", + "name": "liquidityManagement", + "type": "tuple" + } + ], + "name": "registerPool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "uint256", + "name": "exactBptAmountIn", + "type": "uint256" + }, + { + "internalType": "uint256[]", + "name": "minAmountsOut", + "type": "uint256[]" + } + ], + "name": "removeLiquidityRecovery", + "outputs": [ + { + "internalType": "uint256[]", + "name": "amountsOutRaw", + "type": "uint256[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "vault", + "outputs": [ + { + "internalType": "contract IVault", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/cables/CablesMainnetRFQ.json b/src/abi/cables/CablesMainnetRFQ.json new file mode 100644 index 000000000..11cd0fade --- /dev/null +++ b/src/abi/cables/CablesMainnetRFQ.json @@ -0,0 +1,1083 @@ +[ + { + "inputs": [], + "name": "AccessControlBadConfirmation", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "neededRole", + "type": "bytes32" + } + ], + "name": "AccessControlUnauthorizedAccount", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target", + "type": "address" + } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { + "inputs": [], + "name": "EnforcedPause", + "type": "error" + }, + { + "inputs": [], + "name": "ExpectedPause", + "type": "error" + }, + { + "inputs": [], + "name": "FailedInnerCall", + "type": "error" + }, + { + "inputs": [], + "name": "InvalidInitialization", + "type": "error" + }, + { + "inputs": [], + "name": "NotInitializing", + "type": "error" + }, + { + "inputs": [], + "name": "ReentrancyGuardReentrantCall", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "inputs": [], + "name": "addressCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "atLeastOneAdminNeeded", + "type": "error" + }, + { + "inputs": [], + "name": "batchArraysMismatch", + "type": "error" + }, + { + "inputs": [], + "name": "expired", + "type": "error" + }, + { + "inputs": [], + "name": "expiredByOverride", + "type": "error" + }, + { + "inputs": [], + "name": "invalidMsgValue", + "type": "error" + }, + { + "inputs": [], + "name": "invalidNonce", + "type": "error" + }, + { + "inputs": [], + "name": "invalidSignature", + "type": "error" + }, + { + "inputs": [], + "name": "swapSignerCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "transferFailed", + "type": "error" + }, + { + "inputs": [], + "name": "wethAddressCannotBeZero", + "type": "error" + }, + { + "inputs": [], + "name": "zeroAmount", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "actionName", + "type": "string" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAddress", + "type": "address" + } + ], + "name": "AddressSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "asset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RebalancerWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "previousAdminRole", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "bytes32", + "name": "newAdminRole", + "type": "bytes32" + } + ], + "name": "RoleAdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleGranted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + } + ], + "name": "RoleRevoked", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "actionName", + "type": "string" + }, + { + "indexed": false, + "internalType": "bytes32", + "name": "updatedRole", + "type": "bytes32" + }, + { + "indexed": false, + "internalType": "address", + "name": "updatedAddress", + "type": "address" + } + ], + "name": "RoleUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "uint256", + "name": "nonceAndMeta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "destTrader", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destChainId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "srcAsset", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "destAsset", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "srcAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "destAmount", + "type": "uint256" + } + ], + "name": "SwapExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "nonceAndMeta", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "timestamp", + "type": "uint256" + } + ], + "name": "SwapExpired", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "newSwapSigner", + "type": "address" + } + ], + "name": "SwapSignerUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DEFAULT_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REBALANCER_ADMIN_ROLE", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "VERSION", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "addAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "addRebalancer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "_assets", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "_amounts", + "type": "uint256[]" + } + ], + "name": "batchClaimBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_asset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_amount", + "type": "uint256" + } + ], + "name": "claimBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "completedSwaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { + "internalType": "bytes1", + "name": "fields", + "type": "bytes1" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + }, + { + "internalType": "string", + "name": "version", + "type": "string" + }, + { + "internalType": "uint256", + "name": "chainId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "uint256[]", + "name": "extensions", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "expiredSwaps", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleAdmin", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getRoleMember", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + } + ], + "name": "getRoleMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "grantRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "hasRole", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_swapSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "_wethAddress", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "isAdmin", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "isRebalancer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "_hash", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + } + ], + "name": "isValidSignature", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonceAndMeta", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "expiry", + "type": "uint128" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makerAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takerAmount", + "type": "uint256" + } + ], + "internalType": "struct CablesRFQ.Order", + "name": "_order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "_takerAmount", + "type": "uint256" + } + ], + "name": "partialSwap", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "removeAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_address", + "type": "address" + } + ], + "name": "removeRebalancer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "callerConfirmation", + "type": "address" + } + ], + "name": "renounceRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "role", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "revokeRole", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_swapSigner", + "type": "address" + } + ], + "name": "setSwapSigner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonceAndMeta", + "type": "uint256" + }, + { + "internalType": "uint128", + "name": "expiry", + "type": "uint128" + }, + { + "internalType": "address", + "name": "makerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "takerAsset", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "taker", + "type": "address" + }, + { + "internalType": "uint256", + "name": "makerAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "takerAmount", + "type": "uint256" + } + ], + "internalType": "struct CablesRFQ.Order", + "name": "_order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "_signature", + "type": "bytes" + } + ], + "name": "simpleSwap", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "swapSigner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_nonceAndMeta", + "type": "uint256" + } + ], + "name": "updateSwapExpiry", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "wethAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/fluid-dex/dexFactory.abi.json b/src/abi/fluid-dex/dexFactory.abi.json new file mode 100644 index 000000000..7ed784ad6 --- /dev/null +++ b/src/abi/fluid-dex/dexFactory.abi.json @@ -0,0 +1,506 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "owner_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidDexError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidDexFactoryError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + } + ], + "name": "FluidDexLiquidityOutput", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "FluidDexPerfectLiquidityOutput", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmt", + "type": "uint256" + } + ], + "name": "FluidDexSingleTokenOutput", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "FluidDexSwapResult", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dex", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "dexId", + "type": "uint256" + } + ], + "name": "DexDeployed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "deployer", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "LogSetDeployer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dexAuth", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "allowed", + "type": "bool" + }, + { + "indexed": true, + "internalType": "address", + "name": "dex", + "type": "address" + } + ], + "name": "LogSetDexAuth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "dexDeploymentLogic", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "LogSetDexDeploymentLogic", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "globalAuth", + "type": "address" + }, + { + "indexed": true, + "internalType": "bool", + "name": "allowed", + "type": "bool" + } + ], + "name": "LogSetGlobalAuth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dexDeploymentLogic_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "dexDeploymentData_", + "type": "bytes" + } + ], + "name": "deployDex", + "outputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "dexId_", + "type": "uint256" + } + ], + "name": "getDexAddress", + "outputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "deployer_", + "type": "address" + } + ], + "name": "isDeployer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "isDex", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + }, + { + "internalType": "address", + "name": "dexAuth_", + "type": "address" + } + ], + "name": "isDexAuth", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dexDeploymentLogic_", + "type": "address" + } + ], + "name": "isDexDeploymentLogic", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "globalAuth_", + "type": "address" + } + ], + "name": "isGlobalAuth", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot_", + "type": "bytes32" + } + ], + "name": "readFromStorage", + "outputs": [ + { + "internalType": "uint256", + "name": "result_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "deployer_", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed_", + "type": "bool" + } + ], + "name": "setDeployer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + }, + { + "internalType": "address", + "name": "dexAuth_", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed_", + "type": "bool" + } + ], + "name": "setDexAuth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "deploymentLogic_", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed_", + "type": "bool" + } + ], + "name": "setDexDeploymentLogic", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "globalAuth_", + "type": "address" + }, + { + "internalType": "bool", + "name": "allowed_", + "type": "bool" + } + ], + "name": "setGlobalAuth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "target_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "spell", + "outputs": [ + { + "internalType": "bytes", + "name": "response_", + "type": "bytes" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalDexes", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/fluid-dex/fluid-dex.abi.json b/src/abi/fluid-dex/fluid-dex.abi.json new file mode 100644 index 000000000..cdd5e0f1a --- /dev/null +++ b/src/abi/fluid-dex/fluid-dex.abi.json @@ -0,0 +1,1478 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "dexId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "liquidity", + "type": "address" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "shift", + "type": "address" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "address", + "name": "colOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "debtOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "perfectOperationsAndSwapOut", + "type": "address" + } + ], + "internalType": "struct Structs.Implementations", + "name": "implementations", + "type": "tuple" + }, + { + "internalType": "address", + "name": "deployerContract", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "supplyToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "supplyToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken1Slot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "oracleMapping", + "type": "uint256" + } + ], + "internalType": "struct Structs.ConstantViews", + "name": "constantViews_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidDexError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId", + "type": "uint256" + } + ], + "name": "FluidDexFactoryError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + } + ], + "name": "FluidDexLiquidityOutput", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "FluidDexPerfectLiquidityOutput", + "type": "error" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "lastStoredPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "centerPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "upperRange", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerRange", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "geometricMean", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplyToken0ExchangePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowToken0ExchangePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplyToken1ExchangePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowToken1ExchangePrice", + "type": "uint256" + } + ], + "internalType": "struct Structs.PricesAndExchangePrice", + "name": "pex_", + "type": "tuple" + } + ], + "name": "FluidDexPricesAndExchangeRates", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "tokenAmt", + "type": "uint256" + } + ], + "name": "FluidDexSingleTokenOutput", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + } + ], + "name": "FluidDexSwapResult", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidLiquidityCalcsError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidSafeTransferError", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int256", + "name": "routing", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amtOut", + "type": "uint256" + } + ], + "name": "LogArbitrage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogBorrowDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogBorrowPerfectDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogDepositColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogDepositPerfectColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + ], + "name": "LogPauseSwapAndArbitrage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + ], + "name": "LogUnpauseSwapAndArbitrage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogPaybackDebtInOneToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogPaybackDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogPaybackPerfectDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogWithdrawColInOneToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogWithdrawColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogWithdrawPerfectColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "swap0to1", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "DEX_ID", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSharesAmt_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "borrow", + "outputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minToken0Borrow_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minToken1Borrow_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "borrowPerfect", + "outputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "constantsView", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "dexId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "liquidity", + "type": "address" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "shift", + "type": "address" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "address", + "name": "colOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "debtOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "perfectOperationsAndSwapOut", + "type": "address" + } + ], + "internalType": "struct Structs.Implementations", + "name": "implementations", + "type": "tuple" + }, + { + "internalType": "address", + "name": "deployerContract", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "supplyToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "supplyToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken1Slot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "oracleMapping", + "type": "uint256" + } + ], + "internalType": "struct Structs.ConstantViews", + "name": "constantsView_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "constantsView2", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0NumeratorPrecision", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0DenominatorPrecision", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1NumeratorPrecision", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1DenominatorPrecision", + "type": "uint256" + } + ], + "internalType": "struct Structs.ConstantViews2", + "name": "constantsView2_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minSharesAmt_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "estimate_", + "type": "bool" + } + ], + "name": "deposit", + "outputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxToken0Deposit_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxToken1Deposit_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "estimate_", + "type": "bool" + } + ], + "name": "depositPerfect", + "outputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "geometricMean_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "upperRange_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerRange_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0SupplyExchangePrice_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1SupplyExchangePrice_", + "type": "uint256" + } + ], + "name": "getCollateralReserves", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct Structs.CollateralReserves", + "name": "c_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "geometricMean_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "upperRange_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerRange_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0BorrowExchangePrice_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1BorrowExchangePrice_", + "type": "uint256" + } + ], + "name": "getDebtReserves", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct Structs.DebtReserves", + "name": "d_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPricesAndExchangePrices", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "liquidityCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "secondsAgos_", + "type": "uint256[]" + } + ], + "name": "oraclePrice", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "twap1by0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowestPrice1by0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "highestPrice1by0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "twap0by1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowestPrice0by1", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "highestPrice0by1", + "type": "uint256" + } + ], + "internalType": "struct Structs.Oracle[]", + "name": "twaps_", + "type": "tuple[]" + }, + { + "internalType": "uint256", + "name": "currentPrice_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minSharesAmt_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "estimate_", + "type": "bool" + } + ], + "name": "payback", + "outputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxToken0Payback_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxToken1Payback_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "estimate_", + "type": "bool" + } + ], + "name": "paybackPerfect", + "outputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxToken0_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxToken1_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "estimate_", + "type": "bool" + } + ], + "name": "paybackPerfectInOneToken", + "outputs": [ + { + "internalType": "uint256", + "name": "paybackAmt_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "slot_", + "type": "bytes32" + } + ], + "name": "readFromStorage", + "outputs": [ + { + "internalType": "uint256", + "name": "result_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "swapIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "swapInWithCallback", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMax_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "swapOut", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMax_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "swapOutWithCallback", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxSharesAmt_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minToken0Withdraw_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minToken1Withdraw_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "withdrawPerfect", + "outputs": [ + { + "internalType": "uint256", + "name": "token0Amt_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Amt_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minToken0_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minToken1_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "withdrawPerfectInOneToken", + "outputs": [ + { + "internalType": "uint256", + "name": "withdrawAmt_", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/fluid-dex/liquidityUserModule.abi.json b/src/abi/fluid-dex/liquidityUserModule.abi.json new file mode 100644 index 000000000..a892ce865 --- /dev/null +++ b/src/abi/fluid-dex/liquidityUserModule.abi.json @@ -0,0 +1,145 @@ +[ + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidLiquidityCalcsError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidLiquidityError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidSafeTransferError", + "type": "error" + }, + { + "anonymous": false, + "inputs": [], + "name": "BorrowRateMaxCap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "int256", + "name": "supplyAmount", + "type": "int256" + }, + { + "indexed": false, + "internalType": "int256", + "name": "borrowAmount", + "type": "int256" + }, + { + "indexed": false, + "internalType": "address", + "name": "withdrawTo", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "borrowTo", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalAmounts", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "exchangePricesAndConfig", + "type": "uint256" + } + ], + "name": "LogOperate", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "int256", + "name": "supplyAmount_", + "type": "int256" + }, + { + "internalType": "int256", + "name": "borrowAmount_", + "type": "int256" + }, + { + "internalType": "address", + "name": "withdrawTo_", + "type": "address" + }, + { + "internalType": "address", + "name": "borrowTo_", + "type": "address" + }, + { + "internalType": "bytes", + "name": "callbackData_", + "type": "bytes" + } + ], + "name": "operate", + "outputs": [ + { + "internalType": "uint256", + "name": "memVar3_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "memVar4_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + } +] diff --git a/src/abi/fluid-dex/resolver.abi.json b/src/abi/fluid-dex/resolver.abi.json new file mode 100644 index 000000000..1664bce5a --- /dev/null +++ b/src/abi/fluid-dex/resolver.abi.json @@ -0,0 +1,2000 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "factory_", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidity_", + "type": "address" + }, + { + "internalType": "address", + "name": "liquidityResolver_", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "inputs": [], + "name": "FACTORY", + "outputs": [ + { + "internalType": "contract IFluidDexFactory", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIQUIDITY", + "outputs": [ + { + "internalType": "contract IFluidLiquidity", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "LIQUIDITY_RESOLVER", + "outputs": [ + { + "internalType": "contract IFluidLiquidityResolver", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + }, + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin_", + "type": "uint256" + } + ], + "name": "estimateSwapIn", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + }, + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMax_", + "type": "uint256" + } + ], + "name": "estimateSwapOut", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllPoolAddresses", + "outputs": [ + { + "internalType": "address[]", + "name": "pools_", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllPools", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "internalType": "struct Structs.Pool[]", + "name": "pools_", + "type": "tuple[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getAllPoolsReserves", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves[]", + "name": "poolsReserves_", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getAllPoolsReservesAdjusted", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves[]", + "name": "poolsReserves_", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "getDexCollateralReserves", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "reserves_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "getDexCollateralReservesAdjusted", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "reserves_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "getDexDebtReserves", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "reserves_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "getDexDebtReservesAdjusted", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "reserves_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "getDexLimits", + "outputs": [ + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dex_", + "type": "address" + } + ], + "name": "getDexPricesAndExchangePrices", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "lastStoredPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "centerPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "upperRange", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lowerRange", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "geometricMean", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplyToken0ExchangePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowToken0ExchangePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "supplyToken1ExchangePrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowToken1ExchangePrice", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.PricesAndExchangePrice", + "name": "pex_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "poolId_", + "type": "uint256" + } + ], + "name": "getPool", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + } + ], + "internalType": "struct Structs.Pool", + "name": "pool_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "poolId_", + "type": "uint256" + } + ], + "name": "getPoolAddress", + "outputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "getPoolConstantsView", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "dexId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "liquidity", + "type": "address" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "shift", + "type": "address" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "address", + "name": "colOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "debtOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "perfectOperationsAndOracle", + "type": "address" + } + ], + "internalType": "struct IFluidDexT1.Implementations", + "name": "implementations", + "type": "tuple" + }, + { + "internalType": "address", + "name": "deployerContract", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "supplyToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "supplyToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken1Slot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "oracleMapping", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.ConstantViews", + "name": "constantsView_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "getPoolConstantsView2", + "outputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "token0NumeratorPrecision", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0DenominatorPrecision", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1NumeratorPrecision", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1DenominatorPrecision", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.ConstantViews2", + "name": "constantsView2_", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "getPoolFee", + "outputs": [ + { + "internalType": "uint256", + "name": "fee_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "getPoolReserves", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves", + "name": "poolReserves_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "getPoolReservesAdjusted", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves", + "name": "poolReserves_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool_", + "type": "address" + } + ], + "name": "getPoolTokens", + "outputs": [ + { + "internalType": "address", + "name": "token0_", + "type": "address" + }, + { + "internalType": "address", + "name": "token1_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "pools_", + "type": "address[]" + } + ], + "name": "getPoolsReserves", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves[]", + "name": "poolsReserves_", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "pools_", + "type": "address[]" + } + ], + "name": "getPoolsReservesAdjusted", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fee", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.CollateralReserves", + "name": "collateralReserves", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "token0Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1Debt", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1RealReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token0ImaginaryReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "token1ImaginaryReserves", + "type": "uint256" + } + ], + "internalType": "struct IFluidDexT1.DebtReserves", + "name": "debtReserves", + "type": "tuple" + }, + { + "components": [ + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "withdrawableToken1", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken0", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "available", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandsTo", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "expandDuration", + "type": "uint256" + } + ], + "internalType": "struct Structs.TokenLimit", + "name": "borrowableToken1", + "type": "tuple" + } + ], + "internalType": "struct Structs.DexLimits", + "name": "limits", + "type": "tuple" + } + ], + "internalType": "struct Structs.PoolWithReserves[]", + "name": "poolsReserves_", + "type": "tuple[]" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getTotalPools", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/src/abi/permit2.json b/src/abi/permit2.json new file mode 100644 index 000000000..22201c714 --- /dev/null +++ b/src/abi/permit2.json @@ -0,0 +1,581 @@ +[ + { + "inputs": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "AllowanceExpired", + "type": "error" + }, + { "inputs": [], "name": "ExcessiveInvalidation", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "maxAmount", "type": "uint256" } + ], + "name": "InvalidAmount", + "type": "error" + }, + { "inputs": [], "name": "InvalidContractSignature", "type": "error" }, + { "inputs": [], "name": "InvalidNonce", "type": "error" }, + { "inputs": [], "name": "InvalidSignature", "type": "error" }, + { "inputs": [], "name": "InvalidSignatureLength", "type": "error" }, + { "inputs": [], "name": "InvalidSigner", "type": "error" }, + { "inputs": [], "name": "LengthMismatch", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "signatureDeadline", + "type": "uint256" + } + ], + "name": "SignatureExpired", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "spender", + "type": "address" + } + ], + "name": "Lockdown", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "newNonce", + "type": "uint48" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "oldNonce", + "type": "uint48" + } + ], + "name": "NonceInvalidation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { + "indexed": false, + "internalType": "uint48", + "name": "nonce", + "type": "uint48" + } + ], + "name": "Permit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "word", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mask", + "type": "uint256" + } + ], + "name": "UnorderedNonceInvalidation", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "address", "name": "", "type": "address" } + ], + "name": "allowance", + "outputs": [ + { "internalType": "uint160", "name": "amount", "type": "uint160" }, + { "internalType": "uint48", "name": "expiration", "type": "uint48" }, + { "internalType": "uint48", "name": "nonce", "type": "uint48" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint160", "name": "amount", "type": "uint160" }, + { "internalType": "uint48", "name": "expiration", "type": "uint48" } + ], + "name": "approve", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint48", "name": "newNonce", "type": "uint48" } + ], + "name": "invalidateNonces", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "wordPos", "type": "uint256" }, + { "internalType": "uint256", "name": "mask", "type": "uint256" } + ], + "name": "invalidateUnorderedNonces", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "internalType": "struct IAllowanceTransfer.TokenSpenderPair[]", + "name": "approvals", + "type": "tuple[]" + } + ], + "name": "lockdown", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "", "type": "address" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "nonceBitmap", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { "internalType": "uint48", "name": "nonce", "type": "uint48" } + ], + "internalType": "struct IAllowanceTransfer.PermitDetails[]", + "name": "details", + "type": "tuple[]" + }, + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "sigDeadline", + "type": "uint256" + } + ], + "internalType": "struct IAllowanceTransfer.PermitBatch", + "name": "permitBatch", + "type": "tuple" + }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { + "internalType": "uint160", + "name": "amount", + "type": "uint160" + }, + { + "internalType": "uint48", + "name": "expiration", + "type": "uint48" + }, + { "internalType": "uint48", "name": "nonce", "type": "uint48" } + ], + "internalType": "struct IAllowanceTransfer.PermitDetails", + "name": "details", + "type": "tuple" + }, + { "internalType": "address", "name": "spender", "type": "address" }, + { + "internalType": "uint256", + "name": "sigDeadline", + "type": "uint256" + } + ], + "internalType": "struct IAllowanceTransfer.PermitSingle", + "name": "permitSingle", + "type": "tuple" + }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions", + "name": "permitted", + "type": "tuple" + }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.PermitTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "to", "type": "address" }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails", + "name": "transferDetails", + "type": "tuple" + }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "permitTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions[]", + "name": "permitted", + "type": "tuple[]" + }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.PermitBatchTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "to", "type": "address" }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails[]", + "name": "transferDetails", + "type": "tuple[]" + }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "permitTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions", + "name": "permitted", + "type": "tuple" + }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.PermitTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "to", "type": "address" }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails", + "name": "transferDetails", + "type": "tuple" + }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "bytes32", "name": "witness", "type": "bytes32" }, + { + "internalType": "string", + "name": "witnessTypeString", + "type": "string" + }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "permitWitnessTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "components": [ + { "internalType": "address", "name": "token", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.TokenPermissions[]", + "name": "permitted", + "type": "tuple[]" + }, + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "internalType": "struct ISignatureTransfer.PermitBatchTransferFrom", + "name": "permit", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "to", "type": "address" }, + { + "internalType": "uint256", + "name": "requestedAmount", + "type": "uint256" + } + ], + "internalType": "struct ISignatureTransfer.SignatureTransferDetails[]", + "name": "transferDetails", + "type": "tuple[]" + }, + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "bytes32", "name": "witness", "type": "bytes32" }, + { + "internalType": "string", + "name": "witnessTypeString", + "type": "string" + }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "name": "permitWitnessTransferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint160", "name": "amount", "type": "uint160" }, + { "internalType": "address", "name": "token", "type": "address" } + ], + "internalType": "struct IAllowanceTransfer.AllowanceTransferDetails[]", + "name": "transferDetails", + "type": "tuple[]" + } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint160", "name": "amount", "type": "uint160" }, + { "internalType": "address", "name": "token", "type": "address" } + ], + "name": "transferFrom", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/usual-m-usd0/usualCollateralDao.abi.json b/src/abi/usual-m-usd0/usualCollateralDao.abi.json new file mode 100644 index 000000000..9fe20473a --- /dev/null +++ b/src/abi/usual-m-usd0/usualCollateralDao.abi.json @@ -0,0 +1,681 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "address", "name": "target", "type": "address" } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { "inputs": [], "name": "AmountIsZero", "type": "error" }, + { "inputs": [], "name": "AmountTooBig", "type": "error" }, + { "inputs": [], "name": "AmountTooLow", "type": "error" }, + { "inputs": [], "name": "ApprovalFailed", "type": "error" }, + { "inputs": [], "name": "CBRIsNull", "type": "error" }, + { "inputs": [], "name": "CBRIsTooHigh", "type": "error" }, + { "inputs": [], "name": "EnforcedPause", "type": "error" }, + { "inputs": [], "name": "ExpectedPause", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "ExpiredSignature", + "type": "error" + }, + { "inputs": [], "name": "FailedInnerCall", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "currentNonce", "type": "uint256" } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "approvalDeadline", + "type": "uint256" + }, + { "internalType": "uint256", "name": "intentDeadline", "type": "uint256" } + ], + "name": "InvalidDeadline", + "type": "error" + }, + { "inputs": [], "name": "InvalidInitialization", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "InvalidOrderAmount", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "InvalidSigner", + "type": "error" + }, + { "inputs": [], "name": "InvalidToken", "type": "error" }, + { "inputs": [], "name": "MathOverflowedMulDiv", "type": "error" }, + { "inputs": [], "name": "NoOrdersIdsProvided", "type": "error" }, + { "inputs": [], "name": "NotAuthorized", "type": "error" }, + { "inputs": [], "name": "NotInitializing", "type": "error" }, + { "inputs": [], "name": "NullContract", "type": "error" }, + { "inputs": [], "name": "RedeemFeeTooBig", "type": "error" }, + { "inputs": [], "name": "RedeemMustBePaused", "type": "error" }, + { "inputs": [], "name": "RedeemMustNotBePaused", "type": "error" }, + { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { "inputs": [], "name": "SameValue", "type": "error" }, + { "inputs": [], "name": "SwapMustBePaused", "type": "error" }, + { "inputs": [], "name": "SwapMustNotBePaused", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "cbrCoef", + "type": "uint256" + } + ], + "name": "CBRActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "CBRDeactivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenSwapped", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalAmountInTokenDecimals", + "type": "uint256" + } + ], + "name": "IntentConsumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenSwapped", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountInTokenDecimals", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountInUSD", + "type": "uint256" + } + ], + "name": "IntentMatched", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonceInvalidated", + "type": "uint256" + } + ], + "name": "NonceInvalidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "NonceThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "rwaToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRedeemed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "returnedRwaAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "stableFeeAmount", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "redeemFee", + "type": "uint256" + } + ], + "name": "RedeemFeeUpdated", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "RedeemPaused", "type": "event" }, + { + "anonymous": false, + "inputs": [], + "name": "RedeemUnPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenSwapped", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountInUSD", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "SwapPaused", "type": "event" }, + { "anonymous": false, "inputs": [], "name": "SwapUnPaused", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DaoCollateralStorageV0Location", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "coefficient", "type": "uint256" } + ], + "name": "activateCBR", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cbrCoef", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deactivateCBR", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "internalType": "bytes1", "name": "fields", "type": "bytes1" }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, + { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryContract", + "type": "address" + }, + { "internalType": "uint256", "name": "_redeemFee", "type": "uint256" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryContract", + "type": "address" + } + ], + "name": "initializeV1", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "invalidateNonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "newNonce", "type": "uint256" } + ], + "name": "invalidateUpToNonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isCBROn", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isRedeemPaused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isSwapPaused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nonceThreshold", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "orderAmountTakenCurrentNonce", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseSwap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "redeemDao", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "redeemFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "threshold", "type": "uint256" } + ], + "name": "setNonceThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_redeemFee", "type": "uint256" } + ], + "name": "setRedeemFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } + ], + "name": "swap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { + "internalType": "uint256", + "name": "amountInTokenDecimals", + "type": "uint256" + }, + { "internalType": "bool", "name": "partialMatching", "type": "bool" }, + { + "internalType": "uint256[]", + "name": "orderIdsToTake", + "type": "uint256[]" + }, + { + "components": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct Approval", + "name": "approval", + "type": "tuple" + } + ], + "name": "swapRWAtoStbc", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "orderIdsToTake", + "type": "uint256[]" + }, + { + "components": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct Approval", + "name": "approval", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { + "internalType": "uint256", + "name": "amountInTokenDecimals", + "type": "uint256" + }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + }, + { "internalType": "bool", "name": "partialMatching", "type": "bool" } + ], + "name": "swapRWAtoStbcIntent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "swapWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseSwap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/abi/usual-m-wrapped-m/usualM.abi.json b/src/abi/usual-m-wrapped-m/usualM.abi.json new file mode 100644 index 000000000..53ea08cd6 --- /dev/null +++ b/src/abi/usual-m-wrapped-m/usualM.abi.json @@ -0,0 +1,509 @@ +[ + { "type": "constructor", "inputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "DECIMALS_NUMBER", + "inputs": [], + "outputs": [{ "name": "", "type": "uint8", "internalType": "uint8" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DOMAIN_SEPARATOR", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UsualMStorageV0Location", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "blacklist", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [{ "name": "", "type": "uint8", "internalType": "uint8" }], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { "name": "fields", "type": "bytes1", "internalType": "bytes1" }, + { "name": "name", "type": "string", "internalType": "string" }, + { "name": "version", "type": "string", "internalType": "string" }, + { "name": "chainId", "type": "uint256", "internalType": "uint256" }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { "name": "salt", "type": "bytes32", "internalType": "bytes32" }, + { "name": "extensions", "type": "uint256[]", "internalType": "uint256[]" } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getWrappableAmount", + "inputs": [ + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { "name": "wrappedM_", "type": "address", "internalType": "address" }, + { + "name": "registryAccess_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isBlacklisted", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "mintCap", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "paused", + "inputs": [], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permit", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "registryAccess", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "setMintCap", + "inputs": [ + { "name": "newMintCap", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unBlacklist", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unwrap", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrap", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrapWithPermit", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrappedM", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Blacklist", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MintCapSet", + "inputs": [ + { + "name": "newMintCap", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Paused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UnBlacklist", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Unpaused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { "type": "error", "name": "Blacklisted", "inputs": [] }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [{ "name": "s", "type": "bytes32", "internalType": "bytes32" }] + }, + { + "type": "error", + "name": "ERC20InsufficientAllowance", + "inputs": [ + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "allowance", "type": "uint256", "internalType": "uint256" }, + { "name": "needed", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ERC20InsufficientBalance", + "inputs": [ + { "name": "sender", "type": "address", "internalType": "address" }, + { "name": "balance", "type": "uint256", "internalType": "uint256" }, + { "name": "needed", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidApprover", + "inputs": [ + { "name": "approver", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidReceiver", + "inputs": [ + { "name": "receiver", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSender", + "inputs": [ + { "name": "sender", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSpender", + "inputs": [ + { "name": "spender", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC2612ExpiredSignature", + "inputs": [ + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ERC2612InvalidSigner", + "inputs": [ + { "name": "signer", "type": "address", "internalType": "address" }, + { "name": "owner", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "EnforcedPause", "inputs": [] }, + { "type": "error", "name": "ExpectedPause", "inputs": [] }, + { + "type": "error", + "name": "InvalidAccountNonce", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" }, + { "name": "currentNonce", "type": "uint256", "internalType": "uint256" } + ] + }, + { "type": "error", "name": "InvalidAmount", "inputs": [] }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { "type": "error", "name": "InvalidUInt96", "inputs": [] }, + { "type": "error", "name": "MintCapExceeded", "inputs": [] }, + { "type": "error", "name": "NotAuthorized", "inputs": [] }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { "type": "error", "name": "SameValue", "inputs": [] }, + { "type": "error", "name": "ZeroAddress", "inputs": [] }, + { "type": "error", "name": "ZeroRegistryAccess", "inputs": [] }, + { "type": "error", "name": "ZeroWrappedM", "inputs": [] } +] diff --git a/src/bigint-constants.ts b/src/bigint-constants.ts index 08e714a74..33754b387 100644 --- a/src/bigint-constants.ts +++ b/src/bigint-constants.ts @@ -11,6 +11,7 @@ export const BI_MAX_INT = BigInt(MAX_INT); export const BI_MAX_UINT8 = 2n ** 8n - 1n; export const BI_MAX_UINT16 = 2n ** 16n - 1n; export const BI_MAX_UINT32 = 2n ** 32n - 1n; +export const BI_MAX_UINT48 = 2n ** 48n - 1n; export const BI_MAX_UINT64 = 2n ** 64n - 1n; export const BI_MAX_UINT96 = 2n ** 96n - 1n; export const BI_MAX_UINT128 = 2n ** 128n - 1n; diff --git a/src/config.ts b/src/config.ts index 9dc9025ec..3b905b882 100644 --- a/src/config.ts +++ b/src/config.ts @@ -17,7 +17,7 @@ type BaseConfig = { wrappedNativeTokenAddress: Address; hasEIP1559: boolean; augustusAddress: Address; - augustusV6Address?: Address; + augustusV6Address: Address; augustusRFQAddress: Address; tokenTransferProxyAddress: Address; multicallV2Address: Address; @@ -147,6 +147,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { multicallV2Address: '0xC50F4c1E81c873B2204D7eFf7069Ffec6Fbe136D', privateHttpProvider: process.env.HTTP_PROVIDER_56, augustusV6Address: '0x6a000f20005980200259b80c5102003040001068', + bebopAuthToken: process.env.API_KEY_BEBOP_AUTH_TOKEN || '', executorsAddresses: { Executor01: '0x000010036C0190E009a000d0fc3541100A07380A', Executor02: '0x00C600b30fb0400701010F4b080409018B9006E0', @@ -240,6 +241,7 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingMaxAllowedStateDelayInBlocks: 2, rpcPollingBlocksBackToTriggerUpdate: 1, forceRpcFallbackDexs: [], + swaapV2AuthToken: process.env.API_KEY_SWAAP_V2_AUTH_TOKEN || '', }, [Network.FANTOM]: { network: Network.FANTOM, @@ -375,7 +377,6 @@ const baseConfigs: { [network: number]: BaseConfig } = { PolygonZkEvmAdapter01: '0xd63B7691dD98fa89A2ea5e1604700489c585aa7B', PolygonZkEvmBuyAdapter: '0xe2137168CdA486a2555E16c597905854C84F9127', }, - rpcPollingMaxAllowedStateDelayInBlocks: 0, rpcPollingBlocksBackToTriggerUpdate: 0, hashFlowAuthToken: process.env.API_KEY_HASHFLOW_AUTH_TOKEN || '', @@ -387,6 +388,35 @@ const baseConfigs: { [network: number]: BaseConfig } = { // FIXME: Not set properly uniswapV2ExchangeRouterAddress: '', }, + [Network.GNOSIS]: { + network: Network.GNOSIS, + networkName: 'Gnosis', + isTestnet: false, + nativeTokenName: 'xDAI', + nativeTokenSymbol: 'XDAI', + wrappedNativeTokenAddress: '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', + hasEIP1559: false, + augustusAddress: '0x0000000000000000000000000000000000000000', + tokenTransferProxyAddress: '0x0000000000000000000000000000000000000000', + multicallV2Address: '0xca11bde05977b3631167028862be2a173976ca11', + privateHttpProvider: process.env.HTTP_PROVIDER_100, + adapterAddresses: {}, + augustusRFQAddress: '0x92EaD5bACf6F0E995FA46Ad8215A9b11f67ca241', + augustusV6Address: '0x6a000f20005980200259b80c5102003040001068', + executorsAddresses: { + Executor01: '0x000010036c0190e009a000d0fc3541100a07380a', + Executor02: '0x00c600b30fb0400701010f4b080409018b9006e0', + Executor03: '0xe009f00e200a090090fc70e02d70b232000c0802', + }, + rpcPollingMaxAllowedStateDelayInBlocks: 0, + rpcPollingBlocksBackToTriggerUpdate: 0, + hashFlowDisabledMMs: [], + uniswapV3EventLoggingSampleRate: 0, + rfqConfigs: {}, + forceRpcFallbackDexs: [], + uniswapV2ExchangeRouterAddress: + '0xfa39c1c670b48956eeF9fd0BbD0E81A290326330', + }, [Network.BASE]: { network: Network.BASE, networkName: 'Base', @@ -424,6 +454,35 @@ const baseConfigs: { [network: number]: BaseConfig } = { rpcPollingBlocksBackToTriggerUpdate: 3, forceRpcFallbackDexs: [], }, + [Network.SEPOLIA]: { + network: Network.SEPOLIA, + networkName: 'Sepolia', + isTestnet: false, + nativeTokenName: 'Ether', + nativeTokenSymbol: 'ETH', + wrappedNativeTokenAddress: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + hasEIP1559: true, + augustusAddress: '0x0000000000000000000000000000000000000000', + augustusRFQAddress: '0xF6322953d6bFcEACf77D90BC9a01B055249D44fE', + tokenTransferProxyAddress: '0x0000000000000000000000000000000000000000', + multicallV2Address: '0xcA11bde05977b3631167028862bE2a173976CA11', + privateHttpProvider: process.env.HTTP_PROVIDER_11155111, + augustusV6Address: '0x6a000f20005980200259b80c5102003040001068', + adapterAddresses: {}, + rfqConfigs: {}, + hashFlowDisabledMMs: [], + executorsAddresses: { + Executor01: '0x000010036c0190e009a000d0fc3541100a07380a', + Executor02: '0x00c600b30fb0400701010f4b080409018b9006e0', + Executor03: '0xe009f00e200a090090fc70e02d70b232000c0802', + }, + uniswapV2ExchangeRouterAddress: + '0x0000000000000000000000000000000000000000', + rpcPollingMaxAllowedStateDelayInBlocks: 0, + rpcPollingBlocksBackToTriggerUpdate: 0, + uniswapV3EventLoggingSampleRate: 0, + forceRpcFallbackDexs: [], + }, }; // Should not be used, except by internal test code diff --git a/src/constants.ts b/src/constants.ts index 68d570746..6ddad8bde 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -7,6 +7,9 @@ export const PORT_TEST_SERVER = process.env.TEST_PORT; export const ETHER_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE'.toLowerCase(); +// address is the same on all chains +export const PERMIT2_ADDRESS = '0x000000000022d473030f116ddee9f6b43ac78ba3'; + export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000'; export const CACHE_PREFIX = 'dl'; @@ -41,6 +44,8 @@ export enum Network { ARBITRUM = 42161, OPTIMISM = 10, BASE = 8453, + SEPOLIA = 11155111, + GNOSIS = 100, } export const SUBGRAPH_TIMEOUT = 20 * 1000; diff --git a/src/dex/aave-v3-stata/config.ts b/src/dex/aave-v3-stata/config.ts index a24cf6825..0ab9c22a2 100644 --- a/src/dex/aave-v3-stata/config.ts +++ b/src/dex/aave-v3-stata/config.ts @@ -7,7 +7,7 @@ import { AaveV3BNB, AaveV3Base, AaveV3Ethereum, - // AaveV3Gnosis, + AaveV3Gnosis, // AaveV3Metis, AaveV3Optimism, AaveV3Polygon, @@ -37,9 +37,9 @@ export const AaveV3StataConfig: DexConfigMap = { [Network.BSC]: { factoryAddress: AaveV3BNB.STATIC_A_TOKEN_FACTORY, }, - // [Network.Gnosis]: { - // factoryAddress: AaveV3Gnosis.STATIC_A_TOKEN_FACTORY, - // }, + [Network.GNOSIS]: { + factoryAddress: AaveV3Gnosis.STATIC_A_TOKEN_FACTORY, + }, // [Network.Scroll]: { // factoryAddress: AaveV3Scroll.STATIC_A_TOKEN_FACTORY, // }, diff --git a/src/dex/aave-v3/aave-v3-e2e.test.ts b/src/dex/aave-v3/aave-v3-e2e.test.ts index be24e0763..75487d09b 100644 --- a/src/dex/aave-v3/aave-v3-e2e.test.ts +++ b/src/dex/aave-v3/aave-v3-e2e.test.ts @@ -506,4 +506,73 @@ describe('AaveV3 E2E', () => { ); }); }); + + describe('AaveV3 GNOSIS', () => { + const network = Network.GNOSIS; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const pairs = [ + { + tokenSymbol: 'XDAI', + aTokenSymbol: 'aGnoWXDAI', + amount: '1000000000000000000', + }, + { + tokenSymbol: 'USDC', + aTokenSymbol: 'aGnoUSDC', + amount: '10000000', + }, + { + tokenSymbol: 'wstETH', + aTokenSymbol: 'aGnowstETH', + amount: '1000000000000000000', + }, + ]; + + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + [SwapSide.BUY, [ContractMethod.swapExactAmountOut]], + ]); + + pairs.forEach(pair => { + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(pair.aTokenSymbol + ' -> ' + pair.tokenSymbol, async () => { + await testE2E( + tokens[pair.aTokenSymbol], + tokens[pair.tokenSymbol], + holders[pair.aTokenSymbol], + pair.amount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + + it(pair.tokenSymbol + ' -> ' + pair.aTokenSymbol, async () => { + await testE2E( + tokens[pair.tokenSymbol], + tokens[pair.aTokenSymbol], + holders[pair.tokenSymbol], + pair.amount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), + ); + }); + }); }); diff --git a/src/dex/aave-v3/aave-v3.ts b/src/dex/aave-v3/aave-v3.ts index 7168467e2..80c941ddc 100644 --- a/src/dex/aave-v3/aave-v3.ts +++ b/src/dex/aave-v3/aave-v3.ts @@ -10,7 +10,12 @@ import { Logger, DexExchangeParam, } from '../../types'; -import { SwapSide, Network, NULL_ADDRESS } from '../../constants'; +import { + SwapSide, + Network, + NULL_ADDRESS, + ETHER_ADDRESS, +} from '../../constants'; import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import { getDexKeysWithNetwork, isETHAddress, getBigIntPow } from '../../utils'; import { IDex } from '../../dex/idex'; @@ -55,7 +60,7 @@ export class AaveV3 extends SimpleExchange implements IDex { } getAdapters(side: SwapSide): { name: string; index: number }[] | null { - return this.adapters[side]; + return this.adapters?.[side] ?? null; } async initializePricing(blockNumber: number): Promise { @@ -246,7 +251,8 @@ export class AaveV3 extends SimpleExchange implements IDex { exchangeData, targetExchange: swapCallee, returnAmountPos: undefined, - skipApproval: data.fromAToken, + skipApproval: + !data.fromAToken && srcToken.toLowerCase() === ETHER_ADDRESS, }; } diff --git a/src/dex/aave-v3/config.ts b/src/dex/aave-v3/config.ts index c0d058bff..046c8d7ed 100644 --- a/src/dex/aave-v3/config.ts +++ b/src/dex/aave-v3/config.ts @@ -11,6 +11,7 @@ import { AaveV3Optimism, AaveV3Polygon, AaveV3PolygonZkEvm, + AaveV3Gnosis, } from '@bgd-labs/aave-address-book'; // TODO: find vals for V3 @@ -70,6 +71,12 @@ export const Config: DexConfigMap = { poolAddress: AaveV3PolygonZkEvm.POOL, wethGatewayAddress: AaveV3PolygonZkEvm.WETH_GATEWAY, }, + [Network.GNOSIS]: { + ethGasCost: 246 * 100, + lendingGasCost: 328 * 1000, + poolAddress: AaveV3Gnosis.POOL, + wethGatewayAddress: AaveV3Gnosis.WETH_GATEWAY, + }, }, }; diff --git a/src/dex/algebra/algebra-e2e.test.ts b/src/dex/algebra/algebra-e2e.test.ts index 0482c0899..f07da8330 100644 --- a/src/dex/algebra/algebra-e2e.test.ts +++ b/src/dex/algebra/algebra-e2e.test.ts @@ -288,4 +288,26 @@ describe('Algebra', () => { nativeTokenAmount, ); }); + + describe('SwaprV3', () => { + const dexKey = 'SwaprV3'; + const network = Network.GNOSIS; + + const tokenASymbol: string = 'WXDAI'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '100000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); diff --git a/src/dex/algebra/algebra-pool-v1_1.ts b/src/dex/algebra/algebra-pool-v1_1.ts index 7eefda67f..3595b2a50 100644 --- a/src/dex/algebra/algebra-pool-v1_1.ts +++ b/src/dex/algebra/algebra-pool-v1_1.ts @@ -38,7 +38,6 @@ import { _reduceTickBitmap, _reduceTicks, } from '../uniswap-v3/contract-math/utils'; -import { Constants } from './lib/Constants'; import { Network, NULL_ADDRESS } from '../../constants'; import { TickTable } from './lib/TickTable'; import { @@ -336,7 +335,8 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber globalState: { tick }, } = _stateWithoutTicksAndTickBitmap; const currentBitmapIndex = int16( - (BigInt(tick) / Constants.TICK_SPACING) >> 8n, + (BigInt(tick) / BigInt(_stateWithoutTicksAndTickBitmap.tickSpacing)) >> + 8n, ); const buffer = this.getBitmapRangeToRequest(); @@ -633,7 +633,7 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber }; const currentTick = globalState.tick; const startTickBitmap = TickTable.position( - BigInt(currentTick) / Constants.TICK_SPACING, + BigInt(currentTick) / BigInt(_state.tickSpacing), )[0]; return { @@ -641,8 +641,8 @@ export class AlgebraEventPoolV1_1 extends StatefulEventSubscriber blockTimestamp: bigIntify(_state.blockTimestamp), globalState, liquidity: bigIntify(_state.liquidity), - tickSpacing: Constants.TICK_SPACING, - maxLiquidityPerTick: Constants.MAX_LIQUIDITY_PER_TICK, + tickSpacing: bigIntify(_state.tickSpacing), + maxLiquidityPerTick: bigIntify(_state.maxLiquidityPerTick), tickBitmap, ticks, startTickBitmap, diff --git a/src/dex/algebra/algebra-pool-v1_9_bidirectional_fee.ts b/src/dex/algebra/algebra-pool-v1_9_bidirectional_fee.ts index 5e83e5a81..81be0ba20 100644 --- a/src/dex/algebra/algebra-pool-v1_9_bidirectional_fee.ts +++ b/src/dex/algebra/algebra-pool-v1_9_bidirectional_fee.ts @@ -32,7 +32,7 @@ export class AlgebraEventPoolV1_9_bidirectional_fee extends AlgebraEventPoolV1_1 mapKey, poolInitCodeHash, poolDeployer, - false, // forceManualStateGeneration; mainly for zkEVM + true, // forceManualStateGeneration; mainly used for xLayer false, // areTicksCompressed ); diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index b18e65927..daa4bc17d 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -61,18 +61,14 @@ type PoolPairsInfo = { const PoolsRegistryHashKey = `${CACHE_PREFIX}_poolsRegistry`; -// const ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days -// const ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day +const ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days +const ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day const ALGEBRA_EFFICIENCY_FACTOR = 3; const ALGEBRA_TICK_GAS_COST = 24_000; // Ceiled const ALGEBRA_TICK_BASE_OVERHEAD = 75_000; const ALGEBRA_POOL_SEARCH_OVERHEAD = 10_000; const ALGEBRA_QUOTE_GASLIMIT = 2_000_000; -const MAX_STALE_STATE_BLOCK_AGE = { - [Network.ZKEVM]: 150, // approximately 3min -}; - type IAlgebraEventPool = | AlgebraEventPoolV1_1 | AlgebraEventPoolV1_9 @@ -168,23 +164,24 @@ export class Algebra extends SimpleExchange implements IDex { // Init listening to new pools creation await this.factory.initialize(blockNumber); - //// COMMENTING DEPRECATED LOGIC: as we now invalidate pools on creation this is not needed anymore - // if (!this.dexHelper.config.isSlave) { - // const cleanExpiredNotExistingPoolsKeys = async () => { - // const maxTimestamp = - // Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; - // await this.dexHelper.cache.zremrangebyscore( - // this.notExistingPoolSetKey, - // 0, - // maxTimestamp, - // ); - // }; - - // this.intervalTask = setInterval( - // cleanExpiredNotExistingPoolsKeys.bind(this), - // ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, - // ); - // } + if (!this.dexHelper.config.isSlave) { + const cleanExpiredNotExistingPoolsKeys = async () => { + const maxTimestamp = + Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; + await this.dexHelper.cache.zremrangebyscore( + this.notExistingPoolSetKey, + 0, + maxTimestamp, + ); + }; + + void cleanExpiredNotExistingPoolsKeys(); + + this.intervalTask = setInterval( + cleanExpiredNotExistingPoolsKeys.bind(this), + ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, + ); + } } /* @@ -239,20 +236,6 @@ export class Algebra extends SimpleExchange implements IDex { if (pool) { if (!pool.initFailed) { - if (this.network !== Network.ZKEVM) return pool; - - if ( - pool.getStaleState() === null || - (pool.getState(blockNumber) === null && - blockNumber - pool.getStateBlockNumber() > - MAX_STALE_STATE_BLOCK_AGE[this.network]) - ) { - /* reload state, on zkEVM this would most likely timeout during request life - * but would allow to rely on staleState for couple of min for next requests - */ - await pool.initialize(blockNumber, { forceRegenerate: true }); - } - return pool; } else { // if init failed then prefer to early return pool with empty state to fallback to rpc call @@ -614,37 +597,16 @@ export class Algebra extends SimpleExchange implements IDex { let state = pool.getState(blockNumber); if (state === null) { - if (this.network === Network.ZKEVM) { - if (pool.initFailed) return null; - - if ( - blockNumber - pool.getStateBlockNumber() <= - MAX_STALE_STATE_BLOCK_AGE[this.network] - ) { - this.logger.warn( - `${_srcAddress}_${_destAddress}_${pool.name}_${ - pool.poolAddress - } state fallback to latest early enough state. Current blockNumber=${blockNumber}, stateBlockNumber=${pool.getStateBlockNumber()}`, - ); - state = pool.getStaleState(); - } else { - this.logger.warn( - `${_srcAddress}_${_destAddress}_${pool.name}_${pool.poolAddress} state is unhealthy, cannot compute price (no fallback on this chain)`, - ); - return null; // never fallback as takes more time - } - } else { - const rpcPrice = await this.getPricingFromRpc( - _srcToken, - _destToken, - amounts, - side, - pool, - transferFees, - ); + const rpcPrice = await this.getPricingFromRpc( + _srcToken, + _destToken, + amounts, + side, + pool, + transferFees, + ); - return rpcPrice; - } + return rpcPrice; } if (!state) return null; diff --git a/src/dex/algebra/config.ts b/src/dex/algebra/config.ts index e3d726231..515145dd6 100644 --- a/src/dex/algebra/config.ts +++ b/src/dex/algebra/config.ts @@ -27,8 +27,7 @@ export const AlgebraConfig: DexConfigMap = { chunksCount: 3, initRetryFrequency: 30, algebraStateMulticall: '0xa6bc273A238867dD74F2bBbD5fBbA3c941C939B9', - subgraphURL: - 'https://api.studio.thegraph.com/query/44554/quickswap-v3-02/0.0.7', + subgraphURL: '3L5Y5brtgvzDoAFGaPs63xz27KdviCdzRuY12spLSBGU', uniswapMulticall: '0x61530d6E1c7A47BBB3e48e8b8EdF7569DcFeE121', deployer: '0x0d500b1d8e8ef31e21c99d1db9a6444d3adf1270', version: 'v1.1', @@ -62,6 +61,8 @@ export const AlgebraConfig: DexConfigMap = { subgraphURL: '3CA9ffebLkS3N2otXaSj8XaDDdspty75upBjKTUS79qY', uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', deployer: '0xc0d4323426c709e8d04b5b130e7f059523464a91', + // optimism fork uses v1.9-bidirectional-fee (with TickSpacing event and simple `fee` in globalState), + // not sure why v1.1 is used here version: 'v1.1', }, }, @@ -79,6 +80,25 @@ export const AlgebraConfig: DexConfigMap = { uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', deployer: '0x6dd3fb9653b10e806650f107c3b5a0a6ff974f65', version: 'v1.9', + // looks like it isn't used as we override it with dexHelper.config.data.forceRpcFallbackDexs in constructor + forceRPC: true, + }, + }, + SwaprV3: { + [Network.GNOSIS]: { + factory: '0xA0864cCA6E114013AB0e27cbd5B6f4c8947da766', + router: '0xfFB643E73f280B97809A8b41f7232AB401a04ee1', + quoter: '0xcBaD9FDf0D2814659Eb26f600EFDeAF005Eda0F7', + initHash: + '0xbce37a54eab2fcd71913a0d40723e04238970e7fc1159bfd58ad5b79531697e7', + chunksCount: 10, + initRetryFrequency: 10, + // AlgebraStateMulticall + algebraStateMulticall: '0x49C46f7f88110ccA234ef27Cd664510f7bbF5998', + subgraphURL: 'AAA1vYjxwFHzbt6qKwLHNcDSASyr1J1xVViDH8gTMFMR', + uniswapMulticall: '0x4dfa9a980efE4802E969AC33968E3d6E59B8a19e', + deployer: '0xC1b576AC6Ec749d5Ace1787bF9Ec6340908ddB47', + version: 'v1.9-bidirectional-fee', forceRPC: true, }, }, diff --git a/src/dex/algebra/lib/AlgebraMath.ts b/src/dex/algebra/lib/AlgebraMath.ts index ea9f06df7..7ed93c58f 100644 --- a/src/dex/algebra/lib/AlgebraMath.ts +++ b/src/dex/algebra/lib/AlgebraMath.ts @@ -48,14 +48,14 @@ interface PriceMovementCache { } const isPoolV1_9 = ( - poolState: PoolStateV1_1 | PoolState_v1_9, -): poolState is PoolState_v1_9 => + poolState: DeepReadonly | DeepReadonly, +): poolState is DeepReadonly => 'feeZto' in poolState.globalState && 'feeOtz' in poolState.globalState; // % START OF COPY PASTA FROM UNISWAPV3 % function _priceComputationCycles( networkId: number, - poolState: DeepReadonly, + poolState: DeepReadonly | DeepReadonly, ticksCopy: Record, state: PriceComputationState, cache: PriceComputationCache, @@ -579,7 +579,11 @@ class AlgebraMathClass { tickCount: 0, }; // swap until there is remaining input or output tokens or we reach the price limit + let iterationsCount = 0; while (true) { + iterationsCount++; + _require(iterationsCount < 100, 'Max iteration reached on AlgebraMath'); + step.stepSqrtPrice = currentPrice; //equivalent of tickTable.nextTickInTheSameRow(currentTick, zeroToOne); @@ -651,11 +655,7 @@ class AlgebraMathClass { } // check stop condition - if ( - amountRequired == 0n || - currentPrice == newSqrtPriceX96 || - currentTick === newTick // deviation from contract - ) { + if (amountRequired == 0n || currentPrice == newSqrtPriceX96) { break; } } diff --git a/src/dex/angle-transmuter/angle-transmuter-pool.ts b/src/dex/angle-transmuter/angle-transmuter-pool.ts index 31ea41699..1f36cf3a8 100644 --- a/src/dex/angle-transmuter/angle-transmuter-pool.ts +++ b/src/dex/angle-transmuter/angle-transmuter-pool.ts @@ -47,7 +47,7 @@ export class AngleTransmuterEventPool extends ComposedEventSubscriber public transmuter: Contract; public readonly angleTransmuterIface; readonly config: PoolConfig; - timer: NodeJS.Timer; + timer: NodeJS.Timeout; constructor( readonly parentName: string, diff --git a/src/dex/augustus-approvals.ts b/src/dex/augustus-approvals.ts index 81a6ea3db..fe47d1e01 100644 --- a/src/dex/augustus-approvals.ts +++ b/src/dex/augustus-approvals.ts @@ -1,11 +1,13 @@ -import { Address, ParaSwapVersion } from '@paraswap/core'; -import { CACHE_PREFIX, ETHER_ADDRESS } from '../constants'; -import { ICache, IDexHelper } from '../dex-helper'; +import { Address } from '@paraswap/core'; +import { CACHE_PREFIX, ETHER_ADDRESS, PERMIT2_ADDRESS } from '../constants'; +import { ICache } from '../dex-helper'; import { Interface } from '@ethersproject/abi'; import ERC20ABI from '../abi/erc20.json'; +import Permit2Abi from '../abi/permit2.json'; import { uint256ToBigInt } from '../lib/decoders'; import { MultiCallParams, MultiWrapper } from '../lib/multi-wrapper'; import { ConfigHelper } from '../config'; +import { BigNumber } from 'ethers'; const DEFAULT_APPROVE_CACHE_KEY_VALUE = 'true'; @@ -14,6 +16,7 @@ type ApprovalsMapping = Record; export class AugustusApprovals { erc20Interface: Interface; + permit2Interface: Interface; private cache: ICache; @@ -32,6 +35,7 @@ export class AugustusApprovals { this.augustusAddress = config.data.augustusAddress; this.augustusV6Address = config.data.augustusV6Address; this.erc20Interface = new Interface(ERC20ABI); + this.permit2Interface = new Interface(Permit2Abi); this.cache = cache; this.cacheApprovesKey = `${CACHE_PREFIX}_${this.network}_generic_approves`; @@ -41,19 +45,22 @@ export class AugustusApprovals { spender: Address, token: Address, target: Address, + permit2 = false, ): Promise { - const approvals = await this.hasApprovals(spender, [[token, target]]); + const approvals = await this.hasApprovals(spender, [ + [token, target, permit2], + ]); return approvals[0]; } async hasApprovals( spender: Address, - tokenTargetMapping: [token: Address, target: Address][], + tokenTargetMapping: [token: Address, target: Address, permit2: boolean][], ): Promise { let approvalsMapping: Record = {}; - tokenTargetMapping.forEach(([token, target]) => { - const key = this.createCacheKey(spender, token, target); + tokenTargetMapping.forEach(([token, target, permit2]) => { + const key = this.createCacheKey(spender, token, target, permit2); // set approved 'true' for ETH approvalsMapping[key] = token.toLowerCase() === ETHER_ADDRESS; }); @@ -68,7 +75,9 @@ export class AugustusApprovals { // to keep same order and length as input return tokenTargetMapping - .map(([token, target]) => this.createCacheKey(spender, token, target)) + .map(([token, target, permit2]) => + this.createCacheKey(spender, token, target, permit2), + ) .map(key => approvalsMapping[key]); } @@ -107,17 +116,37 @@ export class AugustusApprovals { spender: Address, token: Address, target: Address, + permit2: boolean, ][], ): Promise { const allowanceCalldata: MultiCallParams[] = - spenderTokenTargetMapping.map(([spender, token, target]) => ({ - target: token, - callData: this.erc20Interface.encodeFunctionData('allowance', [ - spender, - target, - ]), - decodeFunction: uint256ToBigInt, - })); + spenderTokenTargetMapping.map(([spender, token, target, permit2]) => + permit2 + ? { + target: PERMIT2_ADDRESS, + callData: this.permit2Interface.encodeFunctionData('allowance', [ + spender, + token, + target, + ]), + decodeFunction: value => { + const [amount, expiration, nonce] = + this.permit2Interface.decodeFunctionResult( + 'allowance', + value.toString(), + ) as [BigNumber, BigNumber, BigNumber]; + return amount.toBigInt(); + }, + } + : { + target: token, + callData: this.erc20Interface.encodeFunctionData('allowance', [ + spender, + target, + ]), + decodeFunction: uint256ToBigInt, + }, + ); const allowances = await this.multiWrapper.tryAggregate( false, @@ -157,14 +186,18 @@ export class AugustusApprovals { spender: Address, token: Address, target: Address, + permit2 = false, ): string { - return `${spender}_${token}_${target}`; + return `${spender}_${token}_${target}${ + permit2 ? '_permit2' : '' + }`.toLowerCase(); } private splitCacheKey( key: string, - ): [spender: Address, token: Address, target: Address] { - return key.split('_') as [Address, Address, Address]; + ): [spender: Address, token: Address, target: Address, permit2: boolean] { + const [spender, token, target, permit2] = key.split('_'); + return [spender, token, target, permit2 === 'permit2']; } private filterKeys(tokenTargetMapping: ApprovalsMapping, approved = false) { diff --git a/src/dex/balancer-v2/balancer-v2-e2e.test.ts b/src/dex/balancer-v2/balancer-v2-e2e.test.ts index 43f0062cd..491b06c0e 100644 --- a/src/dex/balancer-v2/balancer-v2-e2e.test.ts +++ b/src/dex/balancer-v2/balancer-v2-e2e.test.ts @@ -41,6 +41,7 @@ function testForNetwork( // ContractMethod.multiSwap, // ContractMethod.megaSwap, ContractMethod.swapExactAmountIn, + ContractMethod.swapExactAmountInOnBalancerV2, ], ], [ @@ -50,6 +51,7 @@ function testForNetwork( // ContractMethod.buy, // DirectMethodsV6.directBuy, ContractMethod.swapExactAmountOut, + ContractMethod.swapExactAmountOutOnBalancerV2, ], ], ]); @@ -128,6 +130,82 @@ describe('BalancerV2 E2E', () => { network, ); + describe.only('GHO -> USDT', () => { + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = + [ + [ + { + name: 'GHO', + sellAmount: '1000000000000000000000', + buyAmount: '1000000000', + }, + { + name: 'USDT', + sellAmount: '1000000000', + buyAmount: '1000000000000000000000', + }, + ], + ]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.swapExactAmountIn, + ContractMethod.swapExactAmountInOnBalancerV2, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.swapExactAmountOut, + ContractMethod.swapExactAmountOutOnBalancerV2, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: string) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod as any, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod as any, + network, + provider, + ); + }); + }); + }); + }); + }), + ); + }); + describe('Weighted Pool', () => { const sideToContractMethods = new Map([ [ @@ -1444,256 +1522,28 @@ describe('BalancerV2 E2E', () => { ); }); - // describe('BeetsFi OPTIMISM', () => { - // const dexKey = 'BeetsFi'; - // const network = Network.OPTIMISM; - // const tokens = Tokens[network]; - // const holders = Holders[network]; - // const provider = new StaticJsonRpcProvider( - // generateConfig(network).privateHttpProvider, - // network, - // ); - // const BBAUSD_OP = '0x6222ae1d2a9f6894da50aa25cb7b303497f9bebd'; - // const BBAUSDMAI_OP = '0x1f131ec1175f023ee1534b16fa8ab237c00e2381'; - // const LIDO_SHUFFLE = '0xde45f101250f2ca1c0f8adfc172576d10c12072d'; - // const YELLOW_SUBMARINE = '0x981fb05b738e981ac532a99e77170ecb4bc27aef'; + describe('BalancerV2_GNOSIS', () => { + const dexKey = 'BalancerV2'; + const network = Network.GNOSIS; - // describe('Simpleswap', () => { - // it('ETH -> TOKEN', async () => { - // await testE2E( - // tokens['ETH'], - // tokens['USDC'], - // holders['ETH'], - // '7000000000000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // ); - // }); - // it('TOKEN -> ETH', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['ETH'], - // holders['USDC'], - // '2000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // ); - // }); - // it('TOKEN -> TOKEN', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['WETH'], - // holders['USDC'], - // '20000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // ); - // }); - // it('USDC -> DAI using bbaUSD', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['DAI'], - // holders['USDC'], - // '20000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${BBAUSD_OP}`], - // ); - // }); - // it('DAI -> USDT using bbaUSD', async () => { - // await testE2E( - // tokens['DAI'], - // tokens['USDT'], - // holders['DAI'], - // '1000000000000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${BBAUSD_OP}`], - // ); - // }); - // it('USDC -> MAI through bbaUSD-MAI', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['MAI'], - // holders['USDC'], - // '20000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${BBAUSDMAI_OP}`], - // ); - // }); - // it('wstETH -> WETH through composable stable', async () => { - // await testE2E( - // tokens['wstETH'], - // tokens['WETH'], - // holders['wstETH'], - // '10000000000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${LIDO_SHUFFLE}`], - // ); - // }); - // it('wstETH -> ETH through composable stable', async () => { - // await testE2E( - // tokens['wstETH'], - // tokens['ETH'], - // holders['wstETH'], - // '10000000000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${LIDO_SHUFFLE}`], - // ); - // }); - // it('ETH -> wstETH through composable stable', async () => { - // await testE2E( - // tokens['ETH'], - // tokens['wstETH'], - // holders['ETH'], - // '10000000000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${LIDO_SHUFFLE}`], - // ); - // }); - // it('wstETH -> WBTC through boosted weighted', async () => { - // await testE2E( - // tokens['wstETH'], - // tokens['WBTC'], - // holders['wstETH'], - // '25000000000000000', //1e18 - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${YELLOW_SUBMARINE}`], - // ); - // }); - // it('USDC -> WBTC through boosted weighted', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['WBTC'], - // holders['USDC'], - // '1000000', //1e6 - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${YELLOW_SUBMARINE}`], - // ); - // }); - // it('wstETH -> USDC through boosted weighted', async () => { - // await testE2E( - // tokens['wstETH'], - // tokens['USDC'], - // holders['wstETH'], - // '1000000000000000000', //1e18 - // SwapSide.SELL, - // dexKey, - // ContractMethod.simpleSwap, - // network, - // provider, - // [`${dexKey}_${YELLOW_SUBMARINE}`], - // ); - // }); - // }); + testForNetwork( + network, + dexKey, + 'USDC', + 'USDT', + '11110010', + '21000000', + '100000000000000000', + ); - // describe('Multiswap', () => { - // it('ETH -> TOKEN', async () => { - // await testE2E( - // tokens['ETH'], - // tokens['USDC'], - // holders['ETH'], - // '7000000000000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.multiSwap, - // network, - // provider, - // ); - // }); - // it('TOKEN -> ETH', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['ETH'], - // holders['USDC'], - // '2000000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.multiSwap, - // network, - // provider, - // ); - // }); - // it('TOKEN -> TOKEN', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['WETH'], - // holders['USDC'], - // '20000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.multiSwap, - // network, - // provider, - // ); - // }); - // it('USDC -> DAI using bbaUSD', async () => { - // await testE2E( - // tokens['USDC'], - // tokens['DAI'], - // holders['USDC'], - // '20000000', - // SwapSide.SELL, - // dexKey, - // ContractMethod.multiSwap, - // network, - // provider, - // [`${dexKey}_${BBAUSD_OP}`], - // ); - // }); - // it('wstETH -> USDC through boosted weighted', async () => { - // await testE2E( - // tokens['wstETH'], - // tokens['USDC'], - // holders['wstETH'], - // '1000000000000000000', //1e18 - // SwapSide.SELL, - // dexKey, - // ContractMethod.multiSwap, - // network, - // provider, - // [`${dexKey}_${YELLOW_SUBMARINE}`], - // ); - // }); - // }); - // }); + testForNetwork( + network, + dexKey, + 'WETH', + 'WXDAI', + '1000000000000000000', + '1000000000000000000', + '1000000000000000000', + ); + }); }); diff --git a/src/dex/balancer-v2/balancer-v2.ts b/src/dex/balancer-v2/balancer-v2.ts index aadafdb19..f82937960 100644 --- a/src/dex/balancer-v2/balancer-v2.ts +++ b/src/dex/balancer-v2/balancer-v2.ts @@ -266,6 +266,7 @@ export class BalancerV2EventPool extends StatefulEventSubscriber { buySupportedPoolTypes: Set = new Set([ BalancerPoolTypes.Weighted, BalancerPoolTypes.GyroE, + BalancerPoolTypes.ComposableStable, ]); eventSupportedPoolTypes: BalancerPoolTypes[] = [ @@ -661,7 +662,7 @@ export class BalancerV2 // In memory pool state for non-event pools nonEventPoolStateCache: PoolStateCache; - eventDisabledPoolsTimer?: NodeJS.Timer; + eventDisabledPoolsTimer?: NodeJS.Timeout; eventDisabledPools: Address[] = []; constructor( @@ -789,7 +790,7 @@ export class BalancerV2 } getAdapters(side: SwapSide): { name: string; index: number }[] | null { - return this.adapters[side] ? this.adapters[side] : null; + return this.adapters?.[side] ? this.adapters[side] : null; } async getPoolIdentifiers( @@ -868,6 +869,7 @@ export class BalancerV2 this.logger.error(`getState returned null`); } const eventPoolStates = { ...(eventPoolStatesRO || {}) }; + for (const addr of this.eventDisabledPools) delete eventPoolStates[addr]; // Fetch previously cached non-event pool states diff --git a/src/dex/balancer-v2/config.ts b/src/dex/balancer-v2/config.ts index ea36a9647..8be7e7eef 100644 --- a/src/dex/balancer-v2/config.ts +++ b/src/dex/balancer-v2/config.ts @@ -25,6 +25,10 @@ export const BalancerConfig: DexConfigMap = { 'https://api.studio.thegraph.com/query/24660/balancer-base-v2/version/latest', vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', }, + [Network.GNOSIS]: { + subgraphURL: 'EJezH1Cp31QkKPaBDerhVPRWsKVZLrDfzjrLqpmv6cGg', + vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + }, }, BeetsFi: { [Network.FANTOM]: { @@ -32,7 +36,7 @@ export const BalancerConfig: DexConfigMap = { vaultAddress: '0x20dd72ed959b6147912c2e529f0a0c651c33c9ce', }, [Network.OPTIMISM]: { - subgraphURL: 'FsmdxmvBJLGjUQPxKMRtcWKzuCNpomKuMTbSbtRtggZ7', + subgraphURL: 'F5jeL2nMXZt5LU6kSway7Vi2PTUcqDbw1gMQEbrmiVdJ', vaultAddress: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', }, }, diff --git a/src/dex/balancer-v3/balancer-v3-e2e.test.ts b/src/dex/balancer-v3/balancer-v3-e2e.test.ts new file mode 100644 index 000000000..df084f902 --- /dev/null +++ b/src/dex/balancer-v3/balancer-v3-e2e.test.ts @@ -0,0 +1,299 @@ +/* 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'; + +/* + README + ====== + + This test script should add e2e tests for BalancerV3. The tests + should cover as many cases as possible. Most of the DEXes follow + the following test structure: + - DexName + - ForkName + Network + - ContractMethod + - ETH -> Token swap + - Token -> ETH swap + - Token -> Token swap + + The template already enumerates the basic structure which involves + testing simpleSwap, multiSwap, megaSwap contract methods for + ETH <> TOKEN and TOKEN <> TOKEN swaps. You should replace tokenA and + tokenB with any two highly liquid tokens on BalancerV3 for the tests + to work. If the tokens that you would like to use are not defined in + Tokens or Holders map, you can update the './tests/constants-e2e' + + Other than the standard cases that are already added by the template + it is highly recommended to add test cases which could be specific + to testing BalancerV3 (Eg. Tests based on poolType, special tokens, + etc). + + You can run this individual test script by running: + `npx jest src/dex//-e2e.test.ts` + + e2e tests use the Tenderly fork api. Please add the following to your + .env file: + TENDERLY_TOKEN=Find this under Account>Settings>Authorization. + TENDERLY_ACCOUNT_ID=Your Tenderly account name. + TENDERLY_PROJECT=Name of a Tenderly project you have created in your + dashboard. + + (This comment should be removed from the final implementation) +*/ + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, + testNative: boolean, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + // TODO: Add any direct swap contractMethod name if it exists + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + [SwapSide.BUY, [ContractMethod.swapExactAmountOut]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + if (testNative) { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + } + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + side === SwapSide.SELL ? tokenBAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + +// TODO - these tests dont currently run without full PS setup on Sepolia +describe('BalancerV3 E2E', () => { + const dexKey = 'BalancerV3'; + + describe('Sepolia', () => { + const network = Network.SEPOLIA; + + describe('Weighted Path', () => { + const tokenASymbol: string = 'bal'; + const tokenBSymbol: string = 'daiAave'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '100000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + true, + ); + }); + + describe('Stable Path', () => { + const tokenASymbol: string = 'stataUSDT'; + const tokenBSymbol: string = 'stataUSDC'; + + const tokenAAmount: string = '10000000'; + const tokenBAmount: string = '10000000'; + const nativeTokenAmount = '0'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + false, + ); + }); + + describe('Boosted Path', () => { + const tokenASymbol: string = 'usdcAave'; + const tokenBSymbol: string = 'daiAave'; + + const tokenAAmount: string = '10000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '0'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + false, + ); + }); + }); + + describe('Gnosis', () => { + const network = Network.GNOSIS; + + describe('Stable Path', () => { + const tokenASymbol: string = 'WXDAI'; + const tokenBSymbol: string = 'COW'; + + const tokenAAmount: string = '100000000000000000'; + const tokenBAmount: string = '100000000000000000'; + const nativeTokenAmount = '100000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + false, + ); + }); + + describe('Weighed Path', () => { + const tokenASymbol: string = 'sDAI'; + const tokenBSymbol: string = 'XDAI'; + + const tokenAAmount: string = '100000000000000000'; + const tokenBAmount: string = '100000000000000000'; + const nativeTokenAmount = '100000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + false, + ); + }); + + describe('Boosted Path', () => { + const tokenASymbol: string = 'waGnoWETH'; + const tokenBSymbol: string = 'waGnowstETH'; + + const tokenAAmount: string = '1000000000000000'; + const tokenBAmount: string = '1000000000000000'; + const nativeTokenAmount = '0'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + false, + ); + }); + }); + + describe('Mainnet', () => { + const network = Network.MAINNET; + + describe('Stable Path', () => { + const tokenASymbol: string = 'wUSDL'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '10000000'; + const nativeTokenAmount = '0'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + false, + ); + }); + }); +}); diff --git a/src/dex/balancer-v3/balancer-v3-events.test.ts b/src/dex/balancer-v3/balancer-v3-events.test.ts new file mode 100644 index 000000000..f6ccdbab5 --- /dev/null +++ b/src/dex/balancer-v3/balancer-v3-events.test.ts @@ -0,0 +1,223 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { BalancerV3EventPool } from './balancer-v3-pool'; +import { Network } from '../../constants'; +import { Address } from '../../types'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { PoolStateMap, StableMutableState } from './types'; +import { BalancerV3Config } from './config'; +import _ from 'lodash'; + +/* + README + ====== + + This test script adds unit tests for BalancerV3 event based + system. This is done by fetching the state on-chain before the + event block, manually pushing the block logs to the event-subscriber, + comparing the local state with on-chain state. + + Most of the logic for testing is abstracted by `testEventSubscriber`. + You need to do two things to make the tests work: + + 1. Fetch the block numbers where certain events were released. You + can modify the `./scripts/fetch-event-blocknumber.ts` to get the + block numbers for different events. Make sure to get sufficient + number of blockNumbers to cover all possible cases for the event + mutations. + + 2. Complete the implementation for fetchPoolState function. The + function should fetch the on-chain state of the event subscriber + using just the blocknumber. + + The template tests only include the test for a single event + subscriber. There can be cases where multiple event subscribers + exist for a single DEX. In such cases additional tests should be + added. + + You can run this individual test script by running: + `npx jest src/dex/balancer-v3/balancer-v3-events.test.ts` + + (This comment should be removed from the final implementation) +*/ + +jest.setTimeout(50 * 1000); + +async function fetchPoolState( + balancerV3Pools: BalancerV3EventPool, + blockNumber: number, + poolAddress: string, +): Promise { + const pools = await balancerV3Pools.generateState(blockNumber); + // Filter to pool of interest + return Object.entries(_.cloneDeep(pools) as PoolStateMap) + .filter(([address]) => { + return address.toLowerCase() === poolAddress.toLowerCase(); + }) + .reduce((acc, [address, pool]) => { + acc[address] = pool; + return acc; + }, {} as PoolStateMap); +} + +function stateCompare(state: PoolStateMap, expectedState: PoolStateMap) { + if (state['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e']) { + if ( + ( + expectedState[ + '0xd63db0b88dca565633fb8d70a70b9b8093d34a7e' + ] as StableMutableState + ).ampIsUpdating + ) { + compareAmpUpdating(state, expectedState); + } else compareAmpStopped(state, expectedState); + } else expect(state).toEqual(expectedState); +} + +function compareAmpUpdating(state: PoolStateMap, expectedState: PoolStateMap) { + // tokenRates and balancesLiveScaled18 are gradually increasing between blocks due to tokenRate (and can't be tracked via event) so will be ignored + const compare = { + ...state, + ['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e']: _.omit( + state['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e'], + ['balancesLiveScaled18', 'tokenRates'], + ), + }; + const expectedCompare = { + ...expectedState, + ['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e']: _.omit( + expectedState['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e'], + ['balancesLiveScaled18', 'tokenRates'], + ), + }; + expect(compare).toEqual(expectedCompare); +} + +function compareAmpStopped(state: PoolStateMap, expectedState: PoolStateMap) { + if (state['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e']) { + // tokenRates and balancesLiveScaled18 are gradually increasing between blocks due to tokenRate (and can't be tracked via event) so will be ignored + // In Contract ampStartTime & ampStopTime are updated to timestamp event is called. + // There doesn't appear to be a way to easily get timestamp non-async so we default to 0n which should have no effect. + const compare = { + ...state, + ['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e']: _.omit( + state['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e'], + ['balancesLiveScaled18', 'tokenRates'], + ), + }; + const expectedCompare = { + ...expectedState, + ['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e']: { + ..._.omit(expectedState['0xd63db0b88dca565633fb8d70a70b9b8093d34a7e'], [ + 'balancesLiveScaled18', + 'tokenRates', + ]), + ampStartTime: 0n, + ampStopTime: 0n, + }, + }; + expect(compare).toEqual(expectedCompare); + } else expect(state).toEqual(expectedState); +} + +// eventName -> blockNumbers +type EventMappings = Record; +type EventData = { blockNumbers: number[]; poolAddress: string[] }; + +describe('BalancerV3 EventPool', function () { + const dexKey = 'BalancerV3'; + const network = Network.SEPOLIA; + const dexHelper = new DummyDexHelper(network); + const logger = dexHelper.getLogger(dexKey); + let balancerV3Pool: BalancerV3EventPool; + + // vault -> EventMappings + // TODO once we have a new test deployment add tests for: AggregateSwapFeePercentageChanged, SwapFeePercentageChanged, PoolPausedStateChanged + const eventsToTest: Record = { + [BalancerV3Config.BalancerV3[network].vaultAddress]: { + // https://eth-sepolia.blockscout.com/tx/0xc417d38ad6e21250c9ddded37680b40f0991cfd3f8ae2d8b5800507a58d48c44 + LiquidityAdded: { + blockNumbers: [7170937], + poolAddress: ['0x2ff3b96e0057a1f25f1d62ab800554ccdb268ab8'], + }, + // https://sepolia.etherscan.io/tx/0xc1596e26d51104b9236a0debc3e1946b30b82f92b8331639ad6f6aea2ff2decc + LiquidityRemoved: { + blockNumbers: [7170957], + poolAddress: ['0x2ff3b96e0057a1f25f1d62ab800554ccdb268ab8'], + }, + // https://sepolia.etherscan.io/tx/0x78d18503c2dd4458c94ec916b10c088bb4e8b90059676d00dbcec83f763d8c0e + Swap: { + blockNumbers: [7175721], + poolAddress: ['0x2ff3b96e0057a1f25f1d62ab800554ccdb268ab8'], + }, + // 7170034, AmpUpdateStarted, https://eth-sepolia.blockscout.com/tx/0xfbe2e53d9cede1dc900100a1d6e809a89d909746ac7b8cc011e93227af8dda8b?tab=logs + // 7170069, AmpUpdateStopped, https://eth-sepolia.blockscout.com/tx/0x2ee2e1d5980013fdf1cf9c3789e0321fb598c2412db8ee8057fcfaffd1c792ab?tab=logs + VaultAuxiliary: { + blockNumbers: [7170034, 7170069], + poolAddress: [ + '0xD63dB0B88dca565633fB8d70a70b9b8093d34A7E', + '0xD63dB0B88dca565633fB8d70a70b9b8093d34A7E', + ], + }, + // https://sepolia.etherscan.io/tx/0x71f9879485f4e4cf97aa42381988ffe277f05a872d6b507cfa007cec1239a3f8#eventlog + SwapFeePercentageChanged: { + blockNumbers: [7206571], + poolAddress: ['0xe69b70a86A4e1fD33dA95693A1aE12Be1c26C8ea'], + }, + // https://sepolia.etherscan.io/tx/0xaf232ca2df59ba5fad38d74e9b54ec2ae1ad2e6abca8348f107cf3fd94c787c7#eventlog + // Should remove pool from state as its paused and no longer supports swaps + PoolPausedStateChanged: { + blockNumbers: [7206586], + poolAddress: ['0xe69b70a86A4e1fD33dA95693A1aE12Be1c26C8ea'], + }, + // https://sepolia.etherscan.io/tx/0xa23dc10bd0fbed7ffffe867766d9b0d7670ca4a0bc352669f547bc7775644349#eventlog + AggregateSwapFeePercentageChanged: { + blockNumbers: [7206701], + poolAddress: ['0xe69b70a86A4e1fD33dA95693A1aE12Be1c26C8ea'], + }, + }, + }; + + beforeEach(async () => { + balancerV3Pool = new BalancerV3EventPool( + dexKey, + network, + dexHelper, + logger, + ); + }); + + Object.entries(eventsToTest).forEach( + ([vaultAddress, events]: [string, EventMappings]) => { + describe(`Events for Vault: ${vaultAddress}`, () => { + Object.entries(events).forEach( + ([eventName, eventData]: [string, EventData]) => { + describe(`${eventName}`, () => { + eventData.blockNumbers.forEach((blockNumber: number, i) => { + it(`Pool: ${eventData.poolAddress[i]} State after ${blockNumber}`, async function () { + await testEventSubscriber( + balancerV3Pool, + balancerV3Pool.addressesSubscribed, + (_blockNumber: number) => + fetchPoolState( + balancerV3Pool, + _blockNumber, + eventData.poolAddress[i], + ), + blockNumber, + `${dexKey}_${vaultAddress}`, + dexHelper.provider, + stateCompare, + ); + }); + }); + }); + }, + ); + }); + }, + ); +}); diff --git a/src/dex/balancer-v3/balancer-v3-integration.test.ts b/src/dex/balancer-v3/balancer-v3-integration.test.ts new file mode 100644 index 000000000..0a2b71014 --- /dev/null +++ b/src/dex/balancer-v3/balancer-v3-integration.test.ts @@ -0,0 +1,1107 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface, Result } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, NULL_ADDRESS, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { BalancerV3 } from './balancer-v3'; +import { + checkPoolPrices, + checkConstantPoolPrices, + checkPoolsLiquidity, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { BalancerV3Config } from './config'; +import { BalancerV3Data, Step } from './types'; +import { Address, ExchangePrices, PoolPrices } from '../../types'; +import balancerBatchRouterAbi from '../../abi/balancer-v3/batch-router.json'; +import balancerRouterAbi from '../../abi/balancer-v3/router.json'; + +function getQuerySwapSingleTokenCalldata( + routerAddress: Address, + routerInterface: Interface, + amounts: bigint[], + step: Step, + side: SwapSide, +) { + return amounts + .filter(amount => amount !== 0n) + .map(amount => { + return { + target: routerAddress, + callData: routerInterface.encodeFunctionData( + side === SwapSide.SELL + ? `querySwapSingleTokenExactIn` + : `querySwapSingleTokenExactOut`, + [ + step.pool, + step.swapInput.tokenIn, + step.swapInput.tokenOut, + amount, + NULL_ADDRESS, + '0x', + ], + ), + }; + }); +} + +function getQuerySwapMultiTokenCalldata( + routerAddress: Address, + routerInterface: Interface, + amounts: bigint[], + steps: Step[], + side: SwapSide, +) { + const tokenIn = steps[0].swapInput.tokenIn; + const stepsNew = steps.map(s => ({ + pool: s.pool, + tokenOut: s.swapInput.tokenOut, + isBuffer: s.isBuffer, + })); + return amounts + .filter(amount => amount !== 0n) + .map(amount => { + let args: any[] = []; + if (side === SwapSide.SELL) + args = [ + { + tokenIn, + steps: stepsNew, + exactAmountIn: amount, + minAmountOut: 0n, + }, + ]; + else + args = [ + { + tokenIn, + steps: stepsNew, + exactAmountOut: amount, + maxAmountIn: 0n, + }, + ]; + return { + target: routerAddress, + callData: routerInterface.encodeFunctionData( + side === SwapSide.SELL ? `querySwapExactIn` : `querySwapExactOut`, + [args, NULL_ADDRESS, '0x'], + ), + }; + }); +} + +async function querySinglePathPrices( + network: number, + side: SwapSide, + balancerV3: BalancerV3, + blockNumber: number, + price: PoolPrices, + amounts: bigint[], +) { + const balancerRouter = new Interface(balancerRouterAbi); + const readerCallData = getQuerySwapSingleTokenCalldata( + BalancerV3Config.BalancerV3[network].balancerRouterAddress, + balancerRouter, + amounts, + price.data.steps[0], + side, + ); + + const expectedPrices = [0n]; + for (const call of readerCallData) { + try { + const result = await balancerV3.dexHelper.provider.call( + { + to: call.target, + data: call.callData, + }, + blockNumber, + ); + const parsed = balancerRouter.decodeFunctionResult( + side === SwapSide.SELL + ? `querySwapSingleTokenExactIn` + : `querySwapSingleTokenExactOut`, + result, + ); + expectedPrices.push(BigInt(parsed[0]._hex)); + } catch (error) { + console.log('Error in querySinglePathPrices', error); + expectedPrices.push(0n); + } + } + return expectedPrices; +} + +async function queryMultiPathPrices( + network: number, + side: SwapSide, + balancerV3: BalancerV3, + blockNumber: number, + price: PoolPrices, + amounts: bigint[], +) { + const balancerBatchRouter = new Interface(balancerBatchRouterAbi); + const readerCallData = getQuerySwapMultiTokenCalldata( + BalancerV3Config.BalancerV3[network].balancerBatchRouterAddress, + balancerBatchRouter, + amounts, + price.data.steps, + side, + ); + + const expectedPrices = [0n]; + for (const call of readerCallData) { + try { + const result = await balancerV3.dexHelper.provider.call( + { + to: call.target, + data: call.callData, + }, + blockNumber, + ); + const parsed = balancerBatchRouter.decodeFunctionResult( + side === SwapSide.SELL ? `querySwapExactIn` : `querySwapExactOut`, + result, + ); + expectedPrices.push(BigInt(parsed[2][0]._hex)); + } catch (error) { + console.log('Error in queryMultiPathPrices', error); + expectedPrices.push(0n); + } + } + return expectedPrices; +} + +// Note - this is currently needed because queries won't work with multicall but should be updated in future +async function checkOnChainPricingNonMulti( + network: number, + side: SwapSide, + balancerV3: BalancerV3, + blockNumber: number, + prices: ExchangePrices, + amounts: bigint[], +) { + // test match for each returned price + for (const price of prices) { + let expectedPrices: bigint[] = []; + if (price.data.steps.length === 1 && !price.data.steps[0].isBuffer) + expectedPrices = await querySinglePathPrices( + network, + side, + balancerV3, + blockNumber, + price, + amounts, + ); + else + expectedPrices = await queryMultiPathPrices( + network, + side, + balancerV3, + blockNumber, + price, + amounts, + ); + expect(price.prices).toEqual(expectedPrices); + } +} + +async function testPricingOnNetwork( + balancerV3: BalancerV3, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], +) { + const networkTokens = Tokens[network]; + + const pools = await balancerV3.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await balancerV3.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (balancerV3.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey, false); + } + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricingNonMulti( + network, + side, + balancerV3, + blockNumber, + poolPrices!, + amounts, + ); +} + +describe('BalancerV3', function () { + const dexKey = 'BalancerV3'; + let blockNumber: number; + let balancerV3: BalancerV3; + + describe('Sepolia', () => { + const network = Network.SEPOLIA; + + describe('Weighted Pool', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'bal'; + const destTokenSymbol = 'daiAave'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newBalancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (newBalancerV3.updatePoolState) { + await newBalancerV3.updatePoolState(); + } + const poolLiquidity = await newBalancerV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newBalancerV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe('Stable Pool', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'stataUSDC'; + const destTokenSymbol = 'stataUSDT'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newBalancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (newBalancerV3.updatePoolState) { + await newBalancerV3.updatePoolState(); + } + const poolLiquidity = await newBalancerV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newBalancerV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe('Boosted Path', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'usdcAave'; + const destTokenSymbol = 'usdtAave'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + // TODO 1 WEI rounding issue in maths - investigating + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newBalancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (newBalancerV3.updatePoolState) { + await newBalancerV3.updatePoolState(); + } + const poolLiquidity = await newBalancerV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newBalancerV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + }); + + describe('Gnosis', () => { + const network = Network.GNOSIS; + + describe('Weighted Pool', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'USDCe'; + const destTokenSymbol = 'sDAI'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newBalancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (newBalancerV3.updatePoolState) { + await newBalancerV3.updatePoolState(); + } + const poolLiquidity = await newBalancerV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newBalancerV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe('Stable Pool', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'WXDAI'; + const destTokenSymbol = 'COW'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newBalancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (newBalancerV3.updatePoolState) { + await newBalancerV3.updatePoolState(); + } + const poolLiquidity = await newBalancerV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newBalancerV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe('Boosted Path', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'waGnoWETH'; + const destTokenSymbol = 'waGnowstETH'; + + const amountsForSell = [ + 0n, + (1n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (2n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (3n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (4n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (5n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (6n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (7n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (8n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (9n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + (10n * BI_POWS[tokens[srcTokenSymbol].decimals]) / 1000n, + ]; + + const amountsForBuy = [ + 0n, + (1n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (2n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (3n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (4n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (5n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (6n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (7n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (8n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (9n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + (10n * BI_POWS[tokens[destTokenSymbol].decimals]) / 1000n, + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + // TODO 1 WEI rounding issue in maths - investigating + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newBalancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (newBalancerV3.updatePoolState) { + await newBalancerV3.updatePoolState(); + } + const poolLiquidity = await newBalancerV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newBalancerV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); + + describe('Buffer, Nested Rate', () => { + /* + The Gnosis pool, 0x272d6be442e30d7c87390edeb9b96f1e84cecd8d uses a rate provider that is nested. + So unwrap rate does not equal rate between aave wsteth and eth. + This particular case the rate provider accounts for growth of wsteth in terms of weth and the additional aave yield. + This highlighted that rateProvider can not be used for buffer wrap/unwrap which instead should use erc4626 rate. + */ + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'wstETH'; + const destTokenSymbol = 'waGnowstETH'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 100n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 100n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + }); + }); + + describe('Mainnet', () => { + const network = Network.MAINNET; + describe('Token/Underlying With Different Decimals', () => { + /* + Mainnet Pool, 0x5dd88b3aa3143173eb26552923922bdf33f50949 has an ERC4626 token with 18 decimals that uses a 6 decimal underlying. + Note for maths: Instead of manually adding support for each ERC4626 implementation (e.g. stata with Ray maths) we always use an + 18 decimal scaled rate and do 18 decimal maths to convert. We may end up loosing 100% accuracy but thats deemed acceptable. + */ + describe.only('Buffer wrap 6decimal>18decimal', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'USDC'; + const destTokenSymbol = 'steakUSDC'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + }); + describe('Buffer unwrap 18decimal>6decimal', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'steakUSDC'; + const destTokenSymbol = 'USDC'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + }); + describe('Full boosted path', () => { + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'wUSDL'; + const destTokenSymbol = 'USDC'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + balancerV3 = new BalancerV3(network, dexKey, dexHelper); + if (balancerV3.initializePricing) { + await balancerV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + balancerV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + ); + }); + }); + }); + }); +}); + +// Add back once multicall queries are working +/* +function decodeQuerySwapSingleTokenResult(results: Result, side: SwapSide) { + const balancerRouter = new Interface(balancerRouterAbi); + return results.map(result => { + const parsed = balancerRouter.decodeFunctionResult( + side === SwapSide.SELL + ? `querySwapSingleTokenExactIn` + : `querySwapSingleTokenExactOut`, + result, + ); + return BigInt(parsed[0]._hex); + }); +} + +async function checkOnChainPricing( + network: number, + side: SwapSide, + balancerV3: BalancerV3, + blockNumber: number, + prices: ExchangePrices, + amounts: bigint[], +) { + // test match for each returned price + for (const price of prices) { + const readerCallData = getQuerySwapSingleTokenCalldata( + network, + amounts, + price.data.steps[0], + side, + ); + const readerResult = ( + await balancerV3.dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + const expectedPrices = [0n].concat( + decodeQuerySwapSingleTokenResult(readerResult, side), + ); + expect(price.prices).toEqual(expectedPrices); + } +} + */ diff --git a/src/dex/balancer-v3/balancer-v3-pool.ts b/src/dex/balancer-v3/balancer-v3-pool.ts new file mode 100644 index 000000000..783520f7b --- /dev/null +++ b/src/dex/balancer-v3/balancer-v3-pool.ts @@ -0,0 +1,702 @@ +import _ from 'lodash'; +import { Interface, defaultAbiCoder } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import { Log, Logger } from '../../types'; +import { catchParseLogError } from '../../utils'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { + PoolState, + PoolStateMap, + StableMutableState, + Step, + TokenInfo, +} from './types'; +import { getPoolsApi } from './getPoolsApi'; +import vaultExtensionAbi_V3 from '../../abi/balancer-v3/vault-extension.json'; +import { + decodeErc4626MultiCallData, + decodeThrowError, + getErc4626MultiCallData, + getOnChainState, +} from './getOnChainState'; +import { BalancerV3Config } from './config'; +import { SwapKind, Vault } from '@balancer-labs/balancer-maths'; +import { + ampUpdateStartedEvent, + ampUpdateStoppedEvent, + getAmplificationParameter, + isStableMutableState, +} from './stablePool'; +import { BI_POWS } from '../../bigint-constants'; + +export const WAD = BI_POWS[18]; + +export class BalancerV3EventPool extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => DeepReadonly | null; + } = {}; + + logDecoder: (log: Log) => any; + + addressesSubscribed: string[]; + + interfaces: { + [name: string]: Interface; + }; + + vault: Vault; + + constructor( + readonly parentName: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + ) { + super( + parentName, + BalancerV3Config.BalancerV3[network].vaultAddress, + dexHelper, + logger, + ); + + this.interfaces = { + ['VAULT']: new Interface(vaultExtensionAbi_V3), + ['STABLE']: new Interface([ + 'function getAmplificationParameter() external view returns (uint256 value, bool isUpdating, uint256 precision)', + 'function getAmplificationState() external view returns (tuple(uint64 startValue, uint64 endValue, uint32 startTime, uint32 endTime) amplificationState, uint256 precision)', + ]), + ['ERC4626']: new Interface([ + 'function convertToAssets(uint256 shares) external view returns (uint256 assets)', + ]), + }; + + this.logDecoder = (log: Log) => this.interfaces['VAULT'].parseLog(log); + this.addressesSubscribed = [ + BalancerV3Config.BalancerV3[network].vaultAddress, + ]; + + // Add handlers + this.handlers['LiquidityAdded'] = this.liquidityAddedEvent.bind(this); + this.handlers['LiquidityRemoved'] = this.liquidityRemovedEvent.bind(this); + this.handlers['Swap'] = this.swapEvent.bind(this); + this.handlers['VaultAuxiliary'] = this.vaultAuxiliaryEvent.bind(this); + this.handlers['AggregateSwapFeePercentageChanged'] = + this.poolAggregateSwapFeePercentageEvent.bind(this); + this.handlers['SwapFeePercentageChanged'] = + this.poolSwapFeePercentageChangedEvent.bind(this); + this.handlers['PoolPausedStateChanged'] = + this.poolPausedStateChanged.bind(this); + + // replicates V3 maths with fees, pool and hook logic + this.vault = new Vault(); + } + + /** + * 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, + ): DeepReadonly | null { + try { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + return this.handlers[event.name](event, state, log); + } + } 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 block = await this.dexHelper.provider.getBlock(blockNumber); + const apiPoolStateMap = await getPoolsApi(this.network, block.timestamp); + const allOnChainPools = await getOnChainState( + this.network, + apiPoolStateMap, + this.dexHelper, + this.interfaces, + blockNumber, + ); + + // Filter out all paused pools + const filteredPools = Object.entries(allOnChainPools) + .filter(([address, pool]) => { + return !pool.isPoolPaused; + }) + .reduce((acc, [address, pool]) => { + acc[address] = pool; + return acc; + }, {} as PoolStateMap); + + return filteredPools; + } + + async getUpdatedPoolState( + existingPoolState: DeepReadonly, + ): Promise | null> { + // Get all latest pools from API + const apiPoolStateMap = await getPoolsApi(this.network); + + // Filter out pools that already exist in existing state + const newApiPools = Object.entries(apiPoolStateMap).reduce( + (acc, [address, pool]) => { + if (!existingPoolState[address]) { + acc[address] = pool; + } + return acc; + }, + {} as typeof apiPoolStateMap, + ); + + // If no new pools return + if (Object.keys(newApiPools).length === 0) { + return null; + } + + // Only get on-chain state for new pools + const newOnChainPools = await getOnChainState( + this.network, + newApiPools, + this.dexHelper, + this.interfaces, + ); + + // Filter out pools with hooks and paused pools from new state + // TODO this won't be necessary once API has this filter option + const filteredNewPools = Object.entries(newOnChainPools) + .filter(([_, pool]) => !(pool.hasHook || pool.isPoolPaused)) + .reduce((acc, [address, pool]) => { + acc[address] = pool; + return acc; + }, {} as PoolStateMap); + + // Merge existing pools with new pools + return { + ...existingPoolState, + ...filteredNewPools, + }; + } + + liquidityAddedEvent( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + const newState = _.cloneDeep(state) as PoolStateMap; + for ( + let i = 0; + i < newState[poolAddress].balancesLiveScaled18.length; + i++ + ) { + newState[poolAddress].balancesLiveScaled18[i] += this.toScaled18( + BigInt(event.args.amountsAddedRaw[i]), + newState[poolAddress].scalingFactors[i], + ); + } + newState[poolAddress].totalSupply = BigInt(event.args.totalSupply); + + return newState; + } + + liquidityRemovedEvent( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + const newState = _.cloneDeep(state) as PoolStateMap; + for ( + let i = 0; + i < newState[poolAddress].balancesLiveScaled18.length; + i++ + ) { + newState[poolAddress].balancesLiveScaled18[i] -= this.toScaled18( + BigInt(event.args.amountsRemovedRaw[i]), + newState[poolAddress].scalingFactors[i], + ); + } + newState[poolAddress].totalSupply = BigInt(event.args.totalSupply); + + return newState; + } + + swapEvent( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + const newState = _.cloneDeep(state) as PoolStateMap; + const tokenInIndex = newState[poolAddress].tokens.findIndex( + address => address.toLowerCase() === event.args.tokenIn.toLowerCase(), + ); + const tokenOutIndex = newState[poolAddress].tokens.findIndex( + address => address.toLowerCase() === event.args.tokenOut.toLowerCase(), + ); + if (tokenInIndex === -1 || tokenOutIndex === -1) { + this.logger.error(`swapEvent - token index not found in pool state`); + return null; + } + newState[poolAddress].balancesLiveScaled18[tokenInIndex] += this.toScaled18( + BigInt(event.args.amountIn), + newState[poolAddress].scalingFactors[tokenInIndex], + ); + newState[poolAddress].balancesLiveScaled18[tokenOutIndex] -= + this.toScaled18( + BigInt(event.args.amountOut), + newState[poolAddress].scalingFactors[tokenOutIndex], + ); + + return newState; + } + + vaultAuxiliaryEvent( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + // In SC Pools can use this event to emit event data from the Vault. + // Allows us to track pool specific events using only the Vault subscription. + // https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVaultExtension.sol + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + + const newState = _.cloneDeep(state) as PoolStateMap; + switch (event.args.eventKey) { + case 'AmpUpdateStarted': + ampUpdateStartedEvent(newState[poolAddress], event.args.eventData); + return newState; + case 'AmpUpdateStopped': + ampUpdateStoppedEvent(newState[poolAddress], event.args.eventData); + return newState; + default: + return null; + } + } + + poolAggregateSwapFeePercentageEvent( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + const newState = _.cloneDeep(state) as PoolStateMap; + newState[poolAddress].aggregateSwapFee = BigInt( + event.args.aggregateSwapFeePercentage, + ); + return newState; + } + + poolSwapFeePercentageChangedEvent( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + const newState = _.cloneDeep(state) as PoolStateMap; + newState[poolAddress].swapFee = BigInt(event.args.swapFeePercentage); + return newState; + } + + poolPausedStateChanged( + event: any, + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + // Unpaused pools will be added with correct state during updateStatePools + if (event.args.paused === false) return null; + const poolAddress = event.args.pool.toLowerCase(); + // Vault will send events from all pools, some of which are not officially supported by Balancer + if (!state[poolAddress]) { + return null; + } + // Remove paused pool from state as it can't be swapped against + const newState = _.cloneDeep(state) as PoolStateMap; + delete newState[poolAddress]; + return newState; + } + + getMaxSwapAmount( + pool: PoolState, + tokenIn: TokenInfo, + tokenOut: TokenInfo, + swapKind: SwapKind, + ): bigint { + // Find the maximum swap amount the pool will support + const maxSwapAmount = this.vault.getMaxSwapAmount( + { + swapKind, + balancesLiveScaled18: pool.balancesLiveScaled18, + tokenRates: pool.tokenRates, + scalingFactors: pool.scalingFactors, + indexIn: tokenIn.index, + indexOut: tokenOut.index, + }, + pool, + ); + return maxSwapAmount; + } + + getSwapResult( + steps: Step[], + amountRaw: bigint, + swapKind: SwapKind, + timestamp: number, + ): bigint { + if (amountRaw === 0n) return 0n; + + // A GivenOut needs to use steps in reverse during calculation + const indices = + swapKind === SwapKind.GivenIn + ? steps.keys() + : Array.from(steps.keys()).reverse(); + + let amount = amountRaw; + let outputAmountRaw = 0n; + for (const i of indices) { + const step = steps[i]; + // If its a Stable Pool with an updating Amp factor calculate current Amp value + if ( + step.poolState.poolType === 'STABLE' && + isStableMutableState(step.poolState) + ) { + if (step.poolState.ampIsUpdating) { + step.poolState.amp = getAmplificationParameter( + step.poolState.ampStartValue, + step.poolState.ampEndValue, + step.poolState.ampStartTime, + step.poolState.ampStopTime, + BigInt(timestamp), + ); + } + } + outputAmountRaw = this.vault.swap( + { + ...step.swapInput, + amountRaw: amount, + swapKind, + }, + step.poolState, + ); + // Next step uses output from previous step as input + amount = outputAmountRaw; + } + return outputAmountRaw; + } + + /** + * Retrieves any new pools via API/multicall and adds to state + */ + async updateStatePools(): Promise { + const blockNumber = await this.dexHelper.provider.getBlockNumber(); + // We just want the current saved state + const currentState = this.getStaleState() || {}; + const updatedPoolState = await this.getUpdatedPoolState(currentState); + if (updatedPoolState) this.setState(updatedPoolState, blockNumber); + } + + /** + * Uses multicall to get onchain token rate for each pool then updates pool state + */ + async updateStatePoolRates(): Promise { + // Get existing state + const poolState = _.cloneDeep(this.getStaleState()) as PoolStateMap; + if (!poolState) return; + + // Fetch onchain pool rates + const poolRates = await this.getPoolRates(poolState); + + // Update each pools rate + poolRates.forEach(({ poolAddress, tokenRates, erc4626Rates }, i) => { + poolState[poolAddress].tokenRates = tokenRates; + poolState[poolAddress].erc4626Rates = erc4626Rates; + }); + + // Update state + const blockNumber = await this.dexHelper.provider.getBlockNumber(); + this.setState(poolState, blockNumber); + } + + private async getPoolRates(poolState: PoolStateMap) { + const erc4626MultiCallData = getErc4626MultiCallData( + this.interfaces['ERC4626'], + poolState, + ); + + const poolAddresses = Object.keys(poolState); + // For each pool make the getPoolTokenRates call + const poolsMultiCallData = poolAddresses.map(address => { + return { + target: BalancerV3Config.BalancerV3[this.network].vaultAddress, + callData: this.interfaces['VAULT'].encodeFunctionData( + 'getPoolTokenRates', + [address], + ), + }; + }); + // 500 is an arbitrary number chosen based on the blockGasLimit + const slicedMultiCallData = _.chunk( + [...erc4626MultiCallData, ...poolsMultiCallData], + 500, + ); + + // Make the multicall + const multicallDataResult = ( + await Promise.all( + slicedMultiCallData.map(async _multiCallData => + this.dexHelper.multiContract.methods + .tryAggregate(false, _multiCallData) + .call({}), + ), + ) + ).flat(); + + const dataResultErc4626 = multicallDataResult.slice( + 0, + erc4626MultiCallData.length, + ); + const dataResultPools = multicallDataResult.slice( + erc4626MultiCallData.length, + ); + + const tokensWithRates = decodeErc4626MultiCallData( + this.interfaces['ERC4626'], + erc4626MultiCallData, + dataResultErc4626, + ); + + return poolAddresses.map((address, i) => { + const tokenRateResult = decodeThrowError( + this.interfaces['VAULT'], + 'getPoolTokenRates', + dataResultPools[i], + address, + ); + return { + poolAddress: address, + tokenRates: tokenRateResult.tokenRates.map((r: string) => BigInt(r)), + erc4626Rates: poolState[address].tokens.map(t => { + if (!tokensWithRates[t]) return null; + return tokensWithRates[t]; + }), + }; + }); + } + + // If a token is "boosted" it can be auto wrapped/unwrapped by Vault, e.g. aDAI<>DAI + // mainToken is the actual token the pool would contain, e.g. in a bbausd type setup it would be aDAI/aUSDC/aUSDT + // underlyingToken would be the unwrapped, e.g. DAI/USDC/USDT + // need rate info to calculate wrap/unwrap + getTokenInfo(poolState: PoolState, tokenAddress: string): TokenInfo | null { + // Check in main tokens + let tokenIndex = poolState.tokens.findIndex( + address => address.toLowerCase() === tokenAddress.toLowerCase(), + ); + if (tokenIndex !== -1) { + return { + isBoosted: false, + mainToken: tokenAddress, + underlyingToken: null, + index: tokenIndex, + rate: poolState.tokenRates[tokenIndex], + }; + } + + // Check in underlying tokens if available + if (poolState.tokensUnderlying) { + tokenIndex = poolState.tokensUnderlying.findIndex( + address => + address && address.toLowerCase() === tokenAddress.toLowerCase(), + ); + if (tokenIndex !== -1) { + if (poolState.erc4626Rates[tokenIndex] === null) { + this.logger.error( + `missing erc4626 token rate ${poolState.tokens[tokenIndex]}`, + ); + return null; + } + return { + isBoosted: true, + mainToken: poolState.tokens[tokenIndex], + underlyingToken: tokenAddress, + index: tokenIndex, + rate: poolState.erc4626Rates[tokenIndex]!, + }; + } + } + + // Token not found + this.logger.error(`getTokenInfo token not found`); + return null; + } + + /** + * Prepares all the step data required to simulate maths and construct swap transaction. + * Balancer V3 has the concepts of Boosted Pools and ERC4626 Liquidity Buffers. + * These enable highly capital efficient pools and gas efficient swaps. + * To swap via a buffer we must provide the correct ""steps" to the router transaction. + * Wrap: e.g. USDC>aUSDC + * Unwrap: e.g. aUSDC>USDC + * A full swap between USDC>DAI for an example bbausd pool consisting of aDAI/aUSDC/aUSDT would look like: + * USDC[wrap-buffer]aUSDC[swap-pool]aDAI[unwrap-buffer]USDC + * See docs for further info: + * https://docs-v3.balancer.fi/concepts/explore-available-balancer-pools/boosted-pool.html + * https://docs-v3.balancer.fi/concepts/vault/buffer.html + */ + getSteps(pool: PoolState, tokenIn: TokenInfo, tokenOut: TokenInfo): Step[] { + if (tokenIn.isBoosted && tokenOut.isBoosted) { + return [ + // Wrap tokenIn underlying to main token + this.getWrapStep(tokenIn), + // Swap main > main + this.getSwapStep(pool, tokenIn, tokenOut), + // Unwrap tokenOut main to underlying token + this.getUnwrapStep(tokenOut), + ]; + } else if (tokenIn.isBoosted) { + if ( + tokenIn.mainToken.toLowerCase() === tokenOut.mainToken.toLowerCase() + ) { + // wrap, token > erc4626 + // tokenIn is boosted, e.g. isn't pool token and must be wrapped + return [this.getWrapStep(tokenIn)]; + } + return [ + // Wrap tokenIn underlying to main token + this.getWrapStep(tokenIn), + // Swap main > main + this.getSwapStep(pool, tokenIn, tokenOut), + ]; + } else if (tokenOut.isBoosted) { + if ( + tokenIn.mainToken.toLowerCase() === tokenOut.mainToken.toLowerCase() + ) { + // unwrap, stata > token + // token out is boosted, e.g. isn't pool token + return [this.getUnwrapStep(tokenOut)]; + } + return [ + // Swap main > main + this.getSwapStep(pool, tokenIn, tokenOut), + // Unwrap tokenOut main to underlying token + this.getUnwrapStep(tokenOut), + ]; + } else { + return [ + // Swap main > main + this.getSwapStep(pool, tokenIn, tokenOut), + ]; + } + } + + getWrapStep(token: TokenInfo): Step { + if (!token.underlyingToken) + throw new Error( + `Buffer wrap: token has no underlying. ${token.mainToken}`, + ); + // Vault expects pool to be the ERC4626 wrapped token, e.g. aUSDC + return { + pool: token.mainToken, + isBuffer: true, + swapInput: { + tokenIn: token.underlyingToken, + tokenOut: token.mainToken, + }, + poolState: { + poolType: 'Buffer', + rate: token.rate, + poolAddress: token.mainToken, + tokens: [token.mainToken, token.underlyingToken], // staticToken & underlying + }, + }; + } + + getUnwrapStep(token: TokenInfo): Step { + if (!token.underlyingToken) + throw new Error( + `Buffer unwrap: token has no underlying. ${token.mainToken}`, + ); + // Vault expects pool to be the ERC4626 wrapped token, e.g. aUSDC + return { + pool: token.mainToken, + isBuffer: true, + swapInput: { + tokenIn: token.mainToken, + tokenOut: token.underlyingToken, + }, + poolState: { + poolType: 'Buffer', + // TODO: for ERC4626 fetch the wrap/unwrap rate + rate: token.rate, + poolAddress: token.mainToken, + tokens: [token.mainToken, token.underlyingToken], // staticToken & underlying + }, + }; + } + + getSwapStep(pool: PoolState, tokenIn: TokenInfo, tokenOut: TokenInfo): Step { + // A normal swap between two tokens in a pool + return { + pool: pool.poolAddress, + isBuffer: false, + swapInput: { + tokenIn: tokenIn.mainToken, + tokenOut: tokenOut.mainToken, + }, + poolState: pool, + }; + } + + toScaled18(amount: bigint, scalingFactor: bigint): bigint { + return (amount * scalingFactor * WAD) / WAD; + } +} diff --git a/src/dex/balancer-v3/balancer-v3.ts b/src/dex/balancer-v3/balancer-v3.ts new file mode 100644 index 000000000..fb1ecfbb3 --- /dev/null +++ b/src/dex/balancer-v3/balancer-v3.ts @@ -0,0 +1,614 @@ +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + PoolLiquidity, + Logger, + DexExchangeParam, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getBigIntPow, getDexKeysWithNetwork, isETHAddress } from '../../utils'; +import { IDex } from '../../dex/idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { BalancerV3Data, PoolState, PoolStateMap } from './types'; +import { SimpleExchange } from '../simple-exchange'; +import { BalancerV3Config } from './config'; +import { BalancerV3EventPool } from './balancer-v3-pool'; +import { NumberAsString } from '@paraswap/core'; +import { SwapKind } from '@balancer-labs/balancer-maths'; +import { Interface } from '@ethersproject/abi'; +import { extractReturnAmountPosition } from '../../executor/utils'; +import { getTopPoolsApi } from './getTopPoolsApi'; +import balancerRouterAbi from '../../abi/balancer-v3/router.json'; +import balancerBatchRouterAbi from '../../abi/balancer-v3/batch-router.json'; +import { getGasCost } from './getGasCost'; +import { Block } from '@ethersproject/abstract-provider'; + +const MAX_UINT256 = + '115792089237316195423570985008687907853269984665640564039457584007913129639935'; +const POOL_UPDATE_TTL = 5 * 60; // 5mins +const RATE_UPDATE_TTL = 1 * 60; // 1min + +type DeepMutable = { + -readonly [P in keyof T]: T[P] extends object ? DeepMutable : T[P]; +}; + +export class BalancerV3 extends SimpleExchange implements IDex { + protected eventPools: BalancerV3EventPool; + + readonly hasConstantPriceLargeAmounts = false; + // Vault can handle native + readonly needWrapNative = false; + + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(BalancerV3Config); + + logger: Logger; + balancerRouter: Interface; + balancerBatchRouter: Interface; + updateNewPoolsTimer?: NodeJS.Timeout; + updateRatesTimer?: NodeJS.Timeout; + + latestBlock?: Block; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + this.eventPools = new BalancerV3EventPool( + dexKey, + network, + dexHelper, + this.logger, + ); + this.balancerRouter = new Interface(balancerRouterAbi); + this.balancerBatchRouter = new Interface(balancerBatchRouterAbi); + } + + // Initialize pricing is called once in the start of + // pricing service. It is intended to setup the integration + // for pricing requests. It is optional for a DEX to + // implement this function + async initializePricing(blockNumber: number) { + await this.eventPools.initialize(blockNumber); + + // This will periodically query API and add any new pools to pool state + if (!this.updateNewPoolsTimer) { + this.updateNewPoolsTimer = setInterval(async () => { + try { + await this.updatePoolState(); + } catch (e) { + this.logger.error(`${this.dexKey}: Failed to update pool state:`, e); + } + }, POOL_UPDATE_TTL * 1000); + } + + // This will periodically refresh tokenRates with onchain state + if (!this.updateRatesTimer) { + this.updateRatesTimer = setInterval(async () => { + try { + await this.updateStatePoolRates(); + } catch (e) { + this.logger.error(`${this.dexKey}: Failed to update pool rates:`, e); + } + }, RATE_UPDATE_TTL * 1000); + } + } + + getAdapters(side: SwapSide): null { + return null; + } + + // Returns list of pool identifiers that can be used + // for a given swap. poolIdentifiers must be unique + // across DEXes. It is recommended to use + // ${dexKey}_${poolAddress} as a poolIdentifier + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + const _from = this.dexHelper.config.wrapETH(srcToken); + const _to = this.dexHelper.config.wrapETH(destToken); + const poolState = this.eventPools.getState(blockNumber); + if (poolState === null) return []; + return this.findPoolAddressesWithTokens( + poolState, + _from.address.toLowerCase(), + _to.address.toLowerCase(), + ); + } + + async getBlock(blockNumber: number): Promise { + if (this.latestBlock && this.latestBlock.number === blockNumber) { + return this.latestBlock; + } + + const block = await this.dexHelper.provider.getBlock(blockNumber); + this.latestBlock = block; + return block; + } + + findPoolAddressesWithTokens( + pools: DeepReadonly, + tokenA: string, + tokenB: string, + ): string[] { + return Object.entries(pools) + .filter(([, poolState]) => { + return this.hasTokens(poolState, [tokenA, tokenB]); + }) + .map(([address]) => address); + } + + /** + * Filter pools that have tokens from/to and are in limitPool list + * @param pools + * @param from + * @param to + * @param limitPools + * @returns Array of PoolState + */ + filterPools( + pools: DeepReadonly, + from: string, + to: string, + limitPools?: string[], + ): PoolState[] { + return Object.entries(pools) + .filter(([address, poolState]) => { + const hasRequiredTokens = this.hasTokens(poolState, [from, to]); + const isAllowedPool = !limitPools || limitPools.includes(address); + return hasRequiredTokens && isAllowedPool; + }) + .map(([_, poolState]) => poolState as DeepMutable); + } + + hasTokens(pool: DeepReadonly, tokens: string[]): boolean { + return tokens.every( + token => + pool.tokens.includes(token) || pool.tokensUnderlying.includes(token), + ); + } + + // Returns pool prices for amounts. + // If limitPools is defined only pools in limitPools + // should be used. If limitPools is undefined then + // any pools can be used. + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + try { + const _from = this.dexHelper.config.wrapETH(srcToken); + const _to = this.dexHelper.config.wrapETH(destToken); + if (_from.address === _to.address) { + return null; + } + + // This is used to get block timestamp which is needed to calculate Amp if it is updating + const block = await this.getBlock(blockNumber); + + // get up to date pools and state + const allPoolState = this.eventPools.getState(blockNumber); + if (allPoolState === null) { + this.logger.error(`getState returned null`); + return null; + } + + // filter for pools with tokens and to only use limit pools + const allowedPools = this.filterPools( + allPoolState, + _from.address.toLowerCase(), + _to.address.toLowerCase(), + limitPools, + ); + + if (!allowedPools.length) return null; + + const swapKind = + side === SwapSide.SELL ? SwapKind.GivenIn : SwapKind.GivenOut; + const tokenIn = _from.address; + const tokenOut = _to.address; + + // Gets the single unit amount based off token decimals, e.g. for USDC its 1e6 + const unitAmount = getBigIntPow( + (side === SwapSide.SELL ? _from : _to).decimals, + ); + + const poolPrices: ExchangePrices = []; + // For each pool we calculate swap result using balancer maths + for (let i = 0; i < allowedPools.length; i++) { + const pool = { + ...allowedPools[i], + }; + + const tokenInInfo = this.eventPools.getTokenInfo(pool, tokenIn); + const tokenOutInfo = this.eventPools.getTokenInfo(pool, tokenOut); + if (!tokenInInfo || !tokenOutInfo) { + continue; + } + + const steps = this.eventPools.getSteps(pool, tokenInInfo, tokenOutInfo); + + try { + // This is the max amount the pool can swap + const maxSwapAmount = this.eventPools.getMaxSwapAmount( + pool, + tokenInInfo, + tokenOutInfo, + swapKind, + ); + + let unit = 0n; + if (unitAmount < maxSwapAmount) + unit = this.eventPools.getSwapResult( + steps, + unitAmount, + swapKind, + block.timestamp, + ); + + const poolExchangePrice: PoolPrices = { + prices: new Array(amounts.length).fill(0n), + unit, + data: { + steps: steps, + }, + exchange: this.dexKey, + gasCost: getGasCost(steps), + poolAddresses: [pool.poolAddress], + poolIdentifier: `${this.dexKey}_${pool.poolAddress}`, + }; + + for (let j = 0; j < amounts.length; j++) { + if (amounts[j] < maxSwapAmount) { + // Uses balancer maths to calculate swap + poolExchangePrice.prices[j] = this.eventPools.getSwapResult( + steps, + amounts[j], + swapKind, + block.timestamp, + ); + } + } + poolPrices.push(poolExchangePrice); + } catch (err) { + this.logger.error( + `error fetching prices for pool: ${pool.poolAddress}`, + ); + this.logger.error(err); + } + } + + return poolPrices; + } catch (err) {} + return null; + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost( + poolPrices: PoolPrices, + ): number | number[] { + if ( + poolPrices.data.steps.length === 1 && + !poolPrices.data.steps[0].isBuffer + ) { + return ( + CALLDATA_GAS_COST.DEX_OVERHEAD + + // pool + CALLDATA_GAS_COST.ADDRESS + + // tokenIn + CALLDATA_GAS_COST.ADDRESS + + // tokenOut + CALLDATA_GAS_COST.ADDRESS + + // exactAmountOut + CALLDATA_GAS_COST.AMOUNT + + // maxAmountIn + CALLDATA_GAS_COST.AMOUNT + + // deadline + CALLDATA_GAS_COST.TIMESTAMP + + // wethIsEth + CALLDATA_GAS_COST.BOOL + + // userData + CALLDATA_GAS_COST.FULL_WORD + ); + } + + return ( + CALLDATA_GAS_COST.DEX_OVERHEAD + + CALLDATA_GAS_COST.LENGTH_LARGE + + // ParentStruct header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> paths[] header + CALLDATA_GAS_COST.OFFSET_LARGE + + // ParentStruct -> paths[] + CALLDATA_GAS_COST.LENGTH_SMALL + + // ParentStruct -> paths header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> paths -> exactAmountIn + CALLDATA_GAS_COST.AMOUNT + + // ParentStruct -> paths -> minAmountOut + CALLDATA_GAS_COST.AMOUNT + + // ParentStruct -> paths -> tokenIn + CALLDATA_GAS_COST.ADDRESS + + poolPrices.data.steps.reduce(step => { + return ( + // ParentStruct -> paths -> step header + CALLDATA_GAS_COST.OFFSET_SMALL + + // ParentStruct -> paths -> step -> isBuffer + CALLDATA_GAS_COST.BOOL + + // ParentStruct -> paths -> step -> pool + CALLDATA_GAS_COST.ADDRESS + + // ParentStruct -> paths -> step -> tokenOut + CALLDATA_GAS_COST.ADDRESS + ); + }, 0) + + // deadline + CALLDATA_GAS_COST.TIMESTAMP + + // wethIsEth + CALLDATA_GAS_COST.BOOL + + // userData + CALLDATA_GAS_COST.FULL_WORD + ); + } + + // Not used for V6 + getAdapterParam(): AdapterExchangeParam { + return { + targetExchange: '0x', + payload: '0x', + networkFee: '0', + }; + } + + getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: BalancerV3Data, + side: SwapSide, + ): DexExchangeParam { + if (side === SwapSide.SELL) { + return this.getExactInParam(srcToken, destToken, srcAmount, data); + } else { + return this.getExactOutParam( + srcToken, + destToken, + srcAmount, + destAmount, + data, + ); + } + } + + getExactInParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + data: BalancerV3Data, + ): DexExchangeParam { + if (data.steps.length === 1 && !data.steps[0].isBuffer) { + const exchangeData = this.balancerRouter.encodeFunctionData( + 'swapSingleTokenExactIn', + [ + data.steps[0].pool, + this.dexHelper.config.wrapETH(srcToken), + this.dexHelper.config.wrapETH(destToken), + srcAmount, + '0', // This should be limit for min amount out. Assume this is set elsewhere via Paraswap contract. + MAX_UINT256, // Deadline + isETHAddress(srcToken) || isETHAddress(destToken), + '0x', + ], + ); + return { + sendEthButSupportsInsertFromAmount: true, + permit2Approval: true, + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: false, + exchangeData, + // This router handles single swaps + targetExchange: + BalancerV3Config.BalancerV3[this.network].balancerRouterAddress, + returnAmountPos: extractReturnAmountPosition( + this.balancerRouter, + 'swapSingleTokenExactIn', + ), + }; + } else { + // for each step: + // if tokenIn == pool router uses removeLiquidity SINGLE_TOKEN_EXACT_IN + // if tokenOut == pool router uses addLiquidity UNBALANCED + const exchangeData = this.balancerBatchRouter.encodeFunctionData( + 'swapExactIn', + [ + [ + { + tokenIn: this.dexHelper.config.wrapETH(srcToken), + steps: data.steps.map(step => ({ + pool: step.pool, + tokenOut: step.swapInput.tokenOut, + isBuffer: step.isBuffer, + })), + exactAmountIn: srcAmount, + minAmountOut: '0', // This should be limit for min amount out. Assume this is set elsewhere via Paraswap contract. + }, + ], + MAX_UINT256, // Deadline + isETHAddress(srcToken) || isETHAddress(destToken), + '0x', + ], + ); + + return { + sendEthButSupportsInsertFromAmount: true, + permit2Approval: true, + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: false, + exchangeData, + // This router handles batch swaps + targetExchange: + BalancerV3Config.BalancerV3[this.network].balancerBatchRouterAddress, + returnAmountPos: undefined, + }; + } + } + + getExactOutParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: BalancerV3Data, + ): DexExchangeParam { + if (data.steps.length === 1 && !data.steps[0].isBuffer) { + const exchangeData = this.balancerRouter.encodeFunctionData( + 'swapSingleTokenExactOut', + [ + data.steps[0].pool, + this.dexHelper.config.wrapETH(srcToken), + this.dexHelper.config.wrapETH(destToken), + destAmount, + MAX_UINT256, // This should be limit for max amount in. Assume this is set elsewhere via Paraswap contract. + MAX_UINT256, // Deadline + isETHAddress(srcToken) || isETHAddress(destToken), + '0x', + ], + ); + + return { + sendEthButSupportsInsertFromAmount: true, + permit2Approval: true, + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: false, + exchangeData, + // Single swaps are submitted via Balancer Router + targetExchange: + BalancerV3Config.BalancerV3[this.network].balancerRouterAddress, + returnAmountPos: undefined, + }; + } else { + // for each step: + // if tokenIn == pool use removeLiquidity SINGLE_TOKEN_EXACT_OUT + // if tokenOut == pool use addLiquidity SINGLE_TOKEN_EXACT_OUT + const exchangeData = this.balancerBatchRouter.encodeFunctionData( + 'swapExactOut', + [ + [ + { + tokenIn: this.dexHelper.config.wrapETH(srcToken), + steps: data.steps.map(step => ({ + pool: step.pool, + tokenOut: step.swapInput.tokenOut, + isBuffer: step.isBuffer, + })), + exactAmountOut: destAmount, + maxAmountIn: srcAmount, + }, + ], + MAX_UINT256, // Deadline + isETHAddress(srcToken) || isETHAddress(destToken), + '0x', + ], + ); + + return { + sendEthButSupportsInsertFromAmount: true, + permit2Approval: true, + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: false, + exchangeData, + // This router handles batch swaps + targetExchange: + BalancerV3Config.BalancerV3[this.network].balancerBatchRouterAddress, + returnAmountPos: undefined, + }; + } + } + + /** + * Uses multicall to get onchain token rate for each pool then updates pool state + */ + async updateStatePoolRates(): Promise { + await this.eventPools.updateStatePoolRates(); + } + + // 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 { + await this.eventPools.updateStatePools(); + } + + // Returns list of top pools based on liquidity. Max + // limit number pools should be returned. + async getTopPoolsForToken( + tokenAddress: Address, + count: number, + ): Promise { + const poolsWithToken = Object.entries(this.eventPools.getStaleState() || {}) + .filter(([, poolState]) => { + return this.hasTokens(poolState, [tokenAddress.toLowerCase()]); + }) + .map(([address]) => address); + + const topPools = await getTopPoolsApi(this.network, poolsWithToken, count); + + return topPools.map(pool => { + const tokens = pool.poolTokens + .filter(t => t.address !== tokenAddress) + .map(t => ({ + address: t.address, + decimals: t.decimals, + })); + + const underlyingTokens = pool.poolTokens + .map(t => t.underlyingToken) + .filter(t => !!t) + .filter(t => t?.address !== tokenAddress) as Token[]; + + return { + exchange: this.dexKey, + address: pool.address, + liquidityUSD: parseFloat(pool.dynamicData.totalLiquidity), + connectorTokens: tokens.concat(underlyingTokens), + }; + }); + } + + releaseResources(): AsyncOrSync { + if (this.updateNewPoolsTimer) { + clearInterval(this.updateNewPoolsTimer); + this.updateNewPoolsTimer = undefined; + this.logger.info( + `${this.dexKey}: cleared updateNewPoolsTimer before shutting down`, + ); + } + if (this.updateRatesTimer) { + clearInterval(this.updateRatesTimer); + this.updateRatesTimer = undefined; + this.logger.info( + `${this.dexKey}: cleared updateRatesTimer before shutting down`, + ); + } + } +} diff --git a/src/dex/balancer-v3/config.ts b/src/dex/balancer-v3/config.ts new file mode 100644 index 000000000..7df1ad9ea --- /dev/null +++ b/src/dex/balancer-v3/config.ts @@ -0,0 +1,42 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +// These map to the Balancer API poolType. Only Balancer supported pools will be added +export enum SUPPORTED_POOLS { + WEIGHTED = 'WEIGHTED', + STABLE = 'STABLE', +} + +export const disabledPoolIds: Record> = { + BalancerV3: { + [Network.GNOSIS]: [], + }, +}; + +// Balancer API - aggregatorSpecific query serves all useful static pool data +export const apiUrl = 'https://test-api-v3.balancer.fi/'; + +// TODO Full config added after V3 release +export const BalancerV3Config: DexConfigMap = { + BalancerV3: { + [Network.SEPOLIA]: { + vaultAddress: '0xbA1333333333a1BA1108E8412f11850A5C319bA9', + apiNetworkName: 'SEPOLIA', + balancerRouterAddress: '0x0BF61f706105EA44694f2e92986bD01C39930280', + balancerBatchRouterAddress: '0xC85b652685567C1B074e8c0D4389f83a2E458b1C', + }, + [Network.GNOSIS]: { + vaultAddress: '0xbA1333333333a1BA1108E8412f11850A5C319bA9', + apiNetworkName: 'GNOSIS', + balancerRouterAddress: '0x84813aA3e079A665C0B80F944427eE83cBA63617', + balancerBatchRouterAddress: '0xe2fa4e1d17725e72dcdAfe943Ecf45dF4B9E285b', + }, + [Network.MAINNET]: { + vaultAddress: '0xbA1333333333a1BA1108E8412f11850A5C319bA9', + apiNetworkName: 'MAINNET', + balancerRouterAddress: '0x5C6fb490BDFD3246EB0bB062c168DeCAF4bD9FDd', + balancerBatchRouterAddress: '0x136f1EFcC3f8f88516B9E94110D56FDBfB1778d1', + }, + }, +}; diff --git a/src/dex/balancer-v3/getGasCost.ts b/src/dex/balancer-v3/getGasCost.ts new file mode 100644 index 000000000..1dc97b786 --- /dev/null +++ b/src/dex/balancer-v3/getGasCost.ts @@ -0,0 +1,37 @@ +import { STABLE_GAS_COST } from './stablePool'; +import { Step } from './types'; +import { WEIGHTED_GAS_COST } from './weightedPool'; + +// https://sepolia.etherscan.io/tx/0x8c2c5ec7fc2855ed2ffab3467ee434f4e374e0ecf791e8d2b93c8d74e3f5b1fe +// 0x7ec61d6dcf0ea412327c30f013e60578c0ff4d83c334a95fb3a68739860e6f59 +// 0xb45331fe60091bdf4ba4c201b7fdfefc1ca8087fa1b6bc28877ef84d167809f4 +const FULL_BOOSTED_SWAP_GAS_COST = 283070; +// 0xb47bcae19ba4693d18f2148073d0d469ff59223e90d6f75eb25e06b0063f1556 +// 0x4730b0c1dce820f14747698446865364b98d96d3ed926e8e74bbead6482b8f8b +const PARTIAL_BOOSTED_SWAP_GAS_COST = 259815; +// 0xef9037f992645a9ecf26ddbdf65690a71acbffce2cc68445c869bb9707cb706a +// 0x77aa06350df079a077147f1051b10e0612542eb3aff3ae1b3d4004341cb64690 +const BUFFER_WRAP_UNWRAP_GAS_COST = 155921; + +export function getGasCost(steps: Step[]): number { + if (steps.length === 2) { + // Partial boosted/buffer swap: + // token[wrap]wrappedToken[swap]wrappedToken or + // wrappedToken[swap]wrappedToken[unwrap]token + return PARTIAL_BOOSTED_SWAP_GAS_COST; + } else if (steps.length === 3) { + // Full boosted/buffer swap: token[wrap]wrappedToken[swap]wrappedToken[unwrap]token + return FULL_BOOSTED_SWAP_GAS_COST; + } else { + switch (steps[0].poolState.poolType) { + case 'WEIGHTED': + return WEIGHTED_GAS_COST; + case 'STABLE': + return STABLE_GAS_COST; + case 'BUFFER': + return BUFFER_WRAP_UNWRAP_GAS_COST; + default: + return WEIGHTED_GAS_COST; + } + } +} diff --git a/src/dex/balancer-v3/getOnChainState.ts b/src/dex/balancer-v3/getOnChainState.ts new file mode 100644 index 000000000..025936621 --- /dev/null +++ b/src/dex/balancer-v3/getOnChainState.ts @@ -0,0 +1,400 @@ +import _ from 'lodash'; +import { + ImmutablePoolStateMap, + CommonMutableState, + PoolStateMap, + StableMutableState, +} from './types'; +import { BalancerV3Config } from './config'; +import { Interface, Result } from '@ethersproject/abi'; +import { IDexHelper } from '../../dex-helper'; +import { WAD } from './balancer-v3-pool'; + +interface callData { + target: string; + callData: string; +} + +// Encoding & Decoding for onchain calls to fetch mutable pool data +// Each supported pool type should have its own specific calls if needed +const poolOnChain: Record< + string, + { + count: number; + encode: ( + network: number, + contractInterface: Interface, + address: string, + ) => callData[]; + decode: ( + contractInterface: Interface, + poolAddress: string, + data: any, + startIndex: number, + ) => {} | CommonMutableState | StableMutableState; + } +> = { + ['COMMON']: { + count: 6, + ['encode']: ( + network: number, + contractInterface: Interface, + address: string, + ): callData[] => { + return [ + { + target: BalancerV3Config.BalancerV3[network].vaultAddress, + callData: contractInterface.encodeFunctionData('getPoolTokenRates', [ + address, + ]), + }, + { + target: BalancerV3Config.BalancerV3[network].vaultAddress, + callData: contractInterface.encodeFunctionData( + 'getCurrentLiveBalances', + [address], + ), + }, + { + target: BalancerV3Config.BalancerV3[network].vaultAddress, + callData: contractInterface.encodeFunctionData('getPoolConfig', [ + address, + ]), + }, + { + target: BalancerV3Config.BalancerV3[network].vaultAddress, + callData: contractInterface.encodeFunctionData('totalSupply', [ + address, + ]), + }, + { + target: BalancerV3Config.BalancerV3[network].vaultAddress, + callData: contractInterface.encodeFunctionData('isPoolPaused', [ + address, + ]), + }, + { + target: BalancerV3Config.BalancerV3[network].vaultAddress, + callData: contractInterface.encodeFunctionData('getHooksConfig', [ + address, + ]), + }, + ]; + }, + ['decode']: ( + contractInterface: Interface, + poolAddress: string, + data: any, + startIndex: number, + ): Omit => { + const resultTokenRates = decodeThrowError( + contractInterface, + 'getPoolTokenRates', + data[startIndex++], + poolAddress, + ); + if (!resultTokenRates) + throw new Error( + `Failed to get result for getPoolTokenRates for ${poolAddress}`, + ); + const resultLiveBalances = decodeThrowError( + contractInterface, + 'getCurrentLiveBalances', + data[startIndex++], + poolAddress, + ); + if (!resultLiveBalances) + throw new Error( + `Failed to get result for getCurrentLiveBalances for ${poolAddress}`, + ); + const resultGetPoolConfig = decodeThrowError( + contractInterface, + 'getPoolConfig', + data[startIndex++], + poolAddress, + ); + if (!resultGetPoolConfig) + throw new Error( + `Failed to get result for getPoolConfig for ${poolAddress}`, + ); + const resultTotalSupply = decodeThrowError( + contractInterface, + 'totalSupply', + data[startIndex++], + poolAddress, + ); + if (!resultTotalSupply) + throw new Error( + `Failed to get result for totalSupply for ${poolAddress}`, + ); + const resultIsPoolPaused = decodeThrowError( + contractInterface, + 'isPoolPaused', + data[startIndex++], + poolAddress, + ); + if (!resultIsPoolPaused) + throw new Error( + `Failed to get result for isPoolPaused for ${poolAddress}`, + ); + const resultHooksConfig = decodeThrowError( + contractInterface, + 'getHooksConfig', + data[startIndex++], + poolAddress, + ); + if (!resultHooksConfig) + throw new Error( + `Failed to get result for resultHooksConfig for ${poolAddress}`, + ); + return { + tokenRates: resultTokenRates.tokenRates.map((r: string) => BigInt(r)), + balancesLiveScaled18: resultLiveBalances.balancesLiveScaled18.map( + (b: string) => BigInt(b), + ), + swapFee: BigInt(resultGetPoolConfig[0].staticSwapFeePercentage), + aggregateSwapFee: BigInt( + resultGetPoolConfig[0].aggregateSwapFeePercentage, + ), + totalSupply: BigInt(resultTotalSupply[0]), + scalingFactors: resultTokenRates.decimalScalingFactors.map( + (r: string) => BigInt(r), + ), + isPoolPaused: resultIsPoolPaused[0], + hasHook: + resultHooksConfig[0].hooksContract !== + '0x0000000000000000000000000000000000000000', + }; + }, + }, + ['WEIGHTED']: { + count: 0, + ['encode']: ( + network: number, + contractInterface: Interface, + address: string, + ): callData[] => { + return []; + }, + ['decode']: ( + contractInterface: Interface, + poolAddress: string, + data: any, + startIndex: number, + ) => { + return {}; + }, + }, + ['STABLE']: { + count: 2, + ['encode']: ( + network: number, + contractInterface: Interface, + address: string, + ): callData[] => { + return [ + { + target: address, + callData: contractInterface.encodeFunctionData( + 'getAmplificationParameter', + ), + }, + { + target: address, + callData: contractInterface.encodeFunctionData( + 'getAmplificationState', + ), + }, + ]; + }, + ['decode']: ( + contractInterface: Interface, + poolAddress: string, + data: any, + startIndex: number, + ): StableMutableState => { + const resultAmp = decodeThrowError( + contractInterface, + 'getAmplificationParameter', + data[startIndex++], + poolAddress, + ); + if (!resultAmp) + throw new Error( + `Failed to get result for getAmplificationParameter for ${poolAddress}`, + ); + const resultAmpState = decodeThrowError( + contractInterface, + 'getAmplificationState', + data[startIndex++], + poolAddress, + ); + if (!resultAmpState) + throw new Error( + `Failed to get result for getAmplificationState for ${poolAddress}`, + ); + + return { + amp: resultAmp[0].toBigInt(), + ampIsUpdating: !!resultAmp[1], + ampStartValue: resultAmpState[0][0].toBigInt(), + ampEndValue: resultAmpState[0][1].toBigInt(), + ampStartTime: BigInt(resultAmpState[0][2]), + ampStopTime: BigInt(resultAmpState[0][3]), + }; + }, + }, +}; + +export function decodeThrowError( + contractInterface: Interface, + functionName: string, + resultEntry: { success: boolean; returnData: any }, + poolAddress: string, +): Result { + if (!resultEntry.success) + throw new Error(`Failed to execute ${functionName} for ${poolAddress}`); + return contractInterface.decodeFunctionResult( + functionName, + resultEntry.returnData, + ); +} + +export function getErc4626MultiCallData( + erc4626Interface: Interface, + immutablePoolStateMap: ImmutablePoolStateMap, +): callData[] { + // We want to query rate for each unique ERC4626 token + const uniqueErc4626Tokens = Array.from( + new Set( + Object.values(immutablePoolStateMap).flatMap(pool => + pool.tokens.filter((_, index) => pool.tokensUnderlying[index] !== null), + ), + ), + ); + + // query result for 1e18 (this maintains correct scaling for different token decimals in maths) + const erc4626MultiCallData: callData[] = uniqueErc4626Tokens.map(token => { + return { + target: token, + callData: erc4626Interface.encodeFunctionData('convertToAssets', [WAD]), + }; + }); + return erc4626MultiCallData; +} + +export function decodeErc4626MultiCallData( + erc4626Interface: Interface, + erc4626MultiCallData: callData[], + dataResultErc4626: any[], +) { + return Object.fromEntries( + erc4626MultiCallData.map((multiCallData, i) => { + const rate = decodeThrowError( + erc4626Interface, + 'convertToAssets', + dataResultErc4626[i], + multiCallData.target, + ); + if (!rate) + throw new Error( + `Failed to get result for convertToAssets for ${multiCallData.target}`, + ); + + return [multiCallData.target, BigInt(rate[0])]; + }), + ); +} + +// Any data from API will be immutable. Mutable data such as balances, etc will be fetched via onchain/event state. +export async function getOnChainState( + network: number, + immutablePoolStateMap: ImmutablePoolStateMap, + dexHelper: IDexHelper, + interfaces: { + [name: string]: Interface; + }, + blockNumber?: number, +): Promise { + const erc4626MultiCallData = getErc4626MultiCallData( + interfaces['ERC4626'], + immutablePoolStateMap, + ); + + // query pool specific onchain data, e.g. totalSupply, etc + const poolsMultiCallData = Object.entries(immutablePoolStateMap) + .map(([address, pool]) => { + return [ + ...poolOnChain['COMMON'].encode(network, interfaces['VAULT'], address), + ...poolOnChain[pool.poolType].encode( + network, + interfaces[pool.poolType], + address, + ), + ]; + }) + .flat(); + + // 500 is an arbitrary number chosen based on the blockGasLimit + const slicedMultiCallData = _.chunk( + [...erc4626MultiCallData, ...poolsMultiCallData], + 500, + ); + + const multicallDataResult = ( + await Promise.all( + slicedMultiCallData.map(async _multiCallData => + dexHelper.multiContract.methods + .tryAggregate(false, _multiCallData) + .call({}, blockNumber), + ), + ) + ).flat(); + + const dataResultErc4626 = multicallDataResult.slice( + 0, + erc4626MultiCallData.length, + ); + const dataResultPools = multicallDataResult.slice( + erc4626MultiCallData.length, + ); + + const tokensWithRates = decodeErc4626MultiCallData( + interfaces['ERC4626'], + erc4626MultiCallData, + dataResultErc4626, + ); + + let i = 0; + const poolStateMap = Object.fromEntries( + Object.entries(immutablePoolStateMap).map(([address, pool]) => { + const commonMutableData = poolOnChain['COMMON'].decode( + interfaces['VAULT'], + address, + dataResultPools, + i, + ) as CommonMutableState; + i = i + poolOnChain['COMMON'].count; + const poolMutableData = poolOnChain[pool.poolType].decode( + interfaces[pool.poolType], + address, + dataResultPools, + i, + ); + i = i + poolOnChain[pool.poolType].count; + return [ + address, + { + ...pool, + ...commonMutableData, + ...poolMutableData, + erc4626Rates: pool.tokens.map(t => { + if (!tokensWithRates[t]) return null; + return tokensWithRates[t]; + }), + }, + ]; + }), + ); + return poolStateMap; +} diff --git a/src/dex/balancer-v3/getPoolsApi.ts b/src/dex/balancer-v3/getPoolsApi.ts new file mode 100644 index 000000000..4056bf802 --- /dev/null +++ b/src/dex/balancer-v3/getPoolsApi.ts @@ -0,0 +1,129 @@ +import axios from 'axios'; +import { + apiUrl, + BalancerV3Config, + disabledPoolIds, + SUPPORTED_POOLS, +} from './config'; +import { CommonImmutablePoolState, ImmutablePoolStateMap } from './types'; +import { parseUnits } from 'ethers/lib/utils'; + +interface PoolToken { + address: string; + weight: string | null; + isErc4626: boolean; + underlyingToken: { + address: string; + } | null; +} + +interface Pool { + id: string; + type: string; + poolTokens: PoolToken[]; +} + +interface QueryResponse { + data: { + poolGetAggregatorPools: Pool[]; + }; +} + +function createQuery( + networkId: number, + poolTypes: SUPPORTED_POOLS[], + timestamp?: number, +): string { + const poolTypesString = poolTypes.map(type => `${type}`).join(', '); + const networkString = BalancerV3Config.BalancerV3[networkId].apiNetworkName; + const disabledPoolIdsString = disabledPoolIds.BalancerV3[networkId] + ?.map(p => `"${p}"`) + .join(', '); + + // Build the where clause conditionally + const whereClause = { + chainIn: networkString, + protocolVersionIn: 3, + hasHook: false, + poolTypeIn: `[${poolTypesString}]`, + ...(timestamp && { createTime: `{lt: ${timestamp}}` }), + ...(disabledPoolIdsString && { idNotIn: `[${disabledPoolIdsString}]` }), + }; + + // Convert where clause to string, filtering out undefined values + const whereString = Object.entries(whereClause) + .map(([key, value]) => `${key}: ${value}`) + .join(', '); + return ` + query MyQuery { + poolGetAggregatorPools( + where: {${whereString}} + ) { + id + type + poolTokens { + address + weight + isErc4626 + underlyingToken { + address + } + } + } + } + `; +} + +function toImmutablePoolStateMap(pools: Pool[]): ImmutablePoolStateMap { + return pools.reduce((map, pool) => { + const immutablePoolState: CommonImmutablePoolState = { + poolAddress: pool.id, + tokens: pool.poolTokens.map(t => t.address), + tokensUnderlying: pool.poolTokens.map(t => + t.underlyingToken ? t.underlyingToken.address : null, + ), + weights: pool.poolTokens.map(t => + t.weight ? parseUnits(t.weight, 18).toBigInt() : 0n, + ), + poolType: pool.type, + // TODO add scalingFactors once API provides them + // scalingFactors: pool.poolTokens.map(t => parseUnits('1', 18).toBigInt()), + // TODO Hook support will be added in future PR + hookType: undefined, + }; + + map[pool.id] = immutablePoolState; + return map; + }, {} as ImmutablePoolStateMap); +} + +// Any data from API will be immutable. Mutable data such as balances, etc will be fetched via onchain/event state. +export async function getPoolsApi( + network: number, + timestamp?: number, +): Promise { + try { + const query = createQuery( + network, + Object.values(SUPPORTED_POOLS), + timestamp, + ); + const response = await axios.post( + apiUrl, + { + query, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + const pools = response.data.data.poolGetAggregatorPools; + return toImmutablePoolStateMap(pools); + } catch (error) { + // console.error('Error executing GraphQL query:', error); + throw error; + } +} diff --git a/src/dex/balancer-v3/getTopPoolsApi.ts b/src/dex/balancer-v3/getTopPoolsApi.ts new file mode 100644 index 000000000..26db2d0ba --- /dev/null +++ b/src/dex/balancer-v3/getTopPoolsApi.ts @@ -0,0 +1,101 @@ +import axios from 'axios'; +import { apiUrl, BalancerV3Config, disabledPoolIds } from './config'; + +interface PoolToken { + address: string; + decimals: number; + underlyingToken?: { + address: string; + decimals: number; + }; +} + +export interface Pool { + address: string; + poolTokens: PoolToken[]; + dynamicData: { + totalLiquidity: string; + }; +} + +interface QueryResponse { + data: { + poolGetAggregatorPools: Pool[]; + }; +} + +function createQuery( + networkId: number, + poolsFilter: string[], + count: number, +): string { + const disabledPoolIdsString = disabledPoolIds.BalancerV3[networkId] + ?.map(p => `"${p}"`) + .join(', '); + + const networkString = BalancerV3Config.BalancerV3[networkId].apiNetworkName; + const poolIdString = poolsFilter.map(a => `"${a}"`).join(', '); + // Build the where clause conditionally + const whereClause = { + chainIn: networkString, + protocolVersionIn: 3, + hasHook: false, + idIn: `[${poolIdString}]`, + ...(disabledPoolIdsString && { idNotIn: `[${disabledPoolIdsString}]` }), + }; + + // Convert where clause to string, filtering out undefined values + const whereString = Object.entries(whereClause) + .map(([key, value]) => `${key}: ${value}`) + .join(', '); + return ` + query MyQuery { + poolGetAggregatorPools( + where: {${whereString}} + first: ${count} + orderBy: totalLiquidity + orderDirection: desc + ) { + address + poolTokens { + address + decimals + underlyingToken { + address + decimals + } + } + dynamicData { + totalLiquidity + } + } + } + `; +} + +export async function getTopPoolsApi( + networkId: number, + poolsFilter: string[], + count: number, +): Promise { + try { + const query = createQuery(networkId, poolsFilter, count); + const response = await axios.post( + apiUrl, + { + query, + }, + { + headers: { + 'Content-Type': 'application/json', + }, + }, + ); + + const pools = response.data.data.poolGetAggregatorPools; + return pools; + } catch (error) { + // console.error('Error executing GraphQL query:', error); + throw error; + } +} diff --git a/src/dex/balancer-v3/optimizer.ts b/src/dex/balancer-v3/optimizer.ts new file mode 100644 index 000000000..3925a25a6 --- /dev/null +++ b/src/dex/balancer-v3/optimizer.ts @@ -0,0 +1,66 @@ +import { UnoptimizedRate, OptimalSwap } from '../../types'; +import _ from 'lodash'; + +export function balancerV3Merge(or: UnoptimizedRate): UnoptimizedRate { + const fixRoute = (rawRate: OptimalSwap[]): OptimalSwap[] => { + let lastExchange: false | OptimalSwap = false; + + let optimizedRate = new Array(); + + rawRate.forEach((s: OptimalSwap) => { + if ( + s.swapExchanges.length !== 1 || + s.swapExchanges[0].exchange.toLowerCase() !== 'balancerv3' + ) { + lastExchange = false; + optimizedRate.push(s); + return; + } + + if ( + lastExchange && + lastExchange.swapExchanges[0].exchange.toLowerCase() === + s.swapExchanges[0].exchange.toLowerCase() && + _.last( + lastExchange.swapExchanges[0].data.steps, + )!.swapInput.tokenOut.toLowerCase() === + s.swapExchanges[0].data.steps[0].swapInput.tokenIn.toLowerCase() + ) { + lastExchange.swapExchanges[0].data.steps = + lastExchange.swapExchanges[0].data.steps.concat( + s.swapExchanges[0].data.steps, + ); + + lastExchange.swapExchanges[0].poolAddresses = + lastExchange.swapExchanges[0].poolAddresses!.concat( + s.swapExchanges[0].poolAddresses!, + ); + + lastExchange.swapExchanges[0].data.gasUSD = ( + parseFloat(lastExchange.swapExchanges[0].data.gasUSD) + + parseFloat(s.swapExchanges[0].data.gasUSD) + ).toFixed(6); + + lastExchange.destToken = s.destToken; + lastExchange.destDecimals = s.destDecimals; + + lastExchange.swapExchanges[0].destAmount = + s.swapExchanges[0].destAmount; + + return; + } + + lastExchange = _.cloneDeep(s); + optimizedRate.push(lastExchange); + }); + + return optimizedRate; + }; + + or.bestRoute = or.bestRoute.map(r => ({ + ...r, + swaps: fixRoute(r.swaps), + })); + + return or; +} diff --git a/src/dex/balancer-v3/stablePool.ts b/src/dex/balancer-v3/stablePool.ts new file mode 100644 index 000000000..1800d38b2 --- /dev/null +++ b/src/dex/balancer-v3/stablePool.ts @@ -0,0 +1,78 @@ +import { defaultAbiCoder } from '@ethersproject/abi'; +import { PoolState } from '@balancer-labs/balancer-maths'; +import { StableMutableState } from './types'; + +// TODO - Update with more accurate +export const STABLE_GAS_COST = 155000; + +export function isStableMutableState( + poolState: any, +): poolState is StableMutableState { + return ( + poolState && + typeof poolState === 'object' && + 'amp' in poolState && + 'ampIsUpdating' in poolState && + 'ampStartValue' in poolState && + 'ampEndValue' in poolState && + 'ampStartTime' in poolState && + 'ampStopTime' in poolState + ); +} + +// https://github.com/balancer/balancer-v3-monorepo/blob/009f2793abda248b150ccd15c1db25930c96ca82/pkg/pool-stable/contracts/StablePool.sol#L274 +export function getAmplificationParameter( + startValue: bigint, + endValue: bigint, + startTime: bigint, + endTime: bigint, + timestamp: bigint, +): bigint { + let value: bigint; + if (timestamp < endTime) { + if (endValue > startValue) { + value = + startValue + + ((endValue - startValue) * (timestamp - startTime)) / + (endTime - startTime); + } else { + value = + startValue - + ((startValue - endValue) * (timestamp - startTime)) / + (endTime - startTime); + } + } else { + value = endValue; + } + return value; +} + +export function ampUpdateStartedEvent(poolState: PoolState, eventData: any) { + // abi.encode(currentValueUint64, endValueUint64, startTimeUint32, endTimeUint32) + const decodedParams = defaultAbiCoder.decode( + ['uint64', 'uint64', 'uint32', 'uint32'], + eventData, + ); + if (isStableMutableState(poolState)) { + if (decodedParams[3] > decodedParams[2]) poolState.ampIsUpdating = true; + poolState.ampStartValue = decodedParams[0].toBigInt(); + poolState.ampEndValue = decodedParams[1].toBigInt(); + poolState.ampStartTime = BigInt(decodedParams[2]); + poolState.ampStopTime = BigInt(decodedParams[3]); + } else throw new Error("Can't update amp on non-stable pool"); +} + +export function ampUpdateStoppedEvent(poolState: PoolState, eventData: any) { + // abi.encode(currentValue) + const decodedParams = defaultAbiCoder.decode(['uint256'], eventData); + if (isStableMutableState(poolState)) { + poolState.ampIsUpdating = false; + poolState.amp = decodedParams[0].toBigInt(); + poolState.ampStartValue = decodedParams[0].toBigInt(); + poolState.ampEndValue = decodedParams[0].toBigInt(); + // In Contract these are update to timestamp event is called. + // There doesn't appear to be a way to easily get timestamp non-async so default to 0n which should have no effect + poolState.ampStartTime = BigInt(0n); + poolState.ampStopTime = BigInt(0n); + } else throw new Error("Can't update amp on non-stable pool"); +} diff --git a/src/dex/balancer-v3/types.ts b/src/dex/balancer-v3/types.ts new file mode 100644 index 000000000..f573c48dc --- /dev/null +++ b/src/dex/balancer-v3/types.ts @@ -0,0 +1,87 @@ +import { BufferState } from '@balancer-labs/balancer-maths'; +import { Address } from '../../types'; + +// Immutable data types available on all pools (Available from API) +export type CommonImmutablePoolState = { + poolAddress: string; + poolType: string; + // For boosted pools tokens is the actual pool token wrapped, e.g. aUSDC/aDAI + tokens: string[]; + // For boosted pools underlying is the unwrapped token, e.g. USDC/DAI + tokensUnderlying: (string | null)[]; + weights: bigint[]; + // TODO re-introduce this once added to API + // scalingFactors: bigint[]; + hookType: string | undefined; +}; + +// Mutable data types available on all pools (Available via onchain calls/events) +export interface CommonMutableState { + tokenRates: bigint[]; + erc4626Rates: (bigint | null)[]; + balancesLiveScaled18: bigint[]; + swapFee: bigint; + aggregateSwapFee: bigint; + totalSupply: bigint; + isPoolPaused: boolean; + // TODO remove this once API provides it + scalingFactors: bigint[]; + // TODO remove this once API provides it + hasHook: boolean; +} + +type CommonPoolState = CommonImmutablePoolState & CommonMutableState; + +export type PoolState = + | CommonPoolState + | (CommonPoolState & StableMutableState); + +// Stable Pool specific mutable data +export interface StableMutableState { + amp: bigint; + ampIsUpdating: boolean; + ampStartValue: bigint; + ampEndValue: bigint; + ampStartTime: bigint; + ampStopTime: bigint; +} + +export type PoolStateMap = { + [address: string]: PoolState; +}; + +export type ImmutablePoolStateMap = { + [address: string]: CommonImmutablePoolState; +}; + +export type Step = { + pool: Address; + isBuffer: boolean; + swapInput: { + tokenIn: Address; + tokenOut: Address; + }; + poolState: PoolState | BufferState; +}; + +export type BalancerV3Data = { + steps: Step[]; +}; + +export type DexParams = { + // Used to map network > API Name, e.g. 11155111>SEPOLIA + apiNetworkName: string; + vaultAddress: string; + // This router handles single swaps + // https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IRouter.sol + balancerRouterAddress: string; + balancerBatchRouterAddress: string; +}; + +export type TokenInfo = { + isBoosted: boolean; + underlyingToken: string | null; + mainToken: string; + index: number; + rate: bigint; +}; diff --git a/src/dex/balancer-v3/weightedPool.ts b/src/dex/balancer-v3/weightedPool.ts new file mode 100644 index 000000000..bbb30c753 --- /dev/null +++ b/src/dex/balancer-v3/weightedPool.ts @@ -0,0 +1,2 @@ +// https://sepolia.etherscan.io/tx/0xe07958ff341aab57ab96a10347b65979b5b041f6408336d7e160e59bd02f9a40 +export const WEIGHTED_GAS_COST = 154000; diff --git a/src/dex/bebop/bebop-e2e.test.ts b/src/dex/bebop/bebop-e2e.test.ts index b11ff0cd5..e56cf7648 100644 --- a/src/dex/bebop/bebop-e2e.test.ts +++ b/src/dex/bebop/bebop-e2e.test.ts @@ -192,4 +192,25 @@ describe('Bebop E2E', () => { nativeTokenAmount, ); }); + + describe('BSC', () => { + const network = Network.BSC; + + const tokenASymbol: string = 'USDT'; + const tokenBSymbol: string = 'ETH'; + + const tokenAAmount: string = '100000000000000000000'; + const tokenBAmount: string = '100000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); diff --git a/src/dex/bebop/bebop.json b/src/dex/bebop/bebop.json new file mode 100644 index 000000000..856e71c9d --- /dev/null +++ b/src/dex/bebop/bebop.json @@ -0,0 +1,72 @@ +{ + "options": { + "syntax": "proto3" + }, + "nested": { + "bebop": { + "nested": { + "PriceUpdate": { + "oneofs": { + "_base": { + "oneof": ["base"] + }, + "_quote": { + "oneof": ["quote"] + }, + "_lastUpdateTs": { + "oneof": ["lastUpdateTs"] + } + }, + "fields": { + "base": { + "type": "bytes", + "id": 1, + "options": { + "proto3_optional": true + } + }, + "quote": { + "type": "bytes", + "id": 2, + "options": { + "proto3_optional": true + } + }, + "lastUpdateTs": { + "type": "uint64", + "id": 3, + "options": { + "proto3_optional": true + } + }, + "bids": { + "rule": "repeated", + "type": "float", + "id": 4, + "options": { + "packed": true + } + }, + "asks": { + "rule": "repeated", + "type": "float", + "id": 5, + "options": { + "packed": true + } + } + } + }, + "BebopPricingUpdate": { + "fields": { + "pairs": { + "rule": "repeated", + "type": "PriceUpdate", + "id": 1 + } + } + } + } + } + } +} diff --git a/src/dex/bebop/bebop.proto b/src/dex/bebop/bebop.proto new file mode 100644 index 000000000..8a7e59c0d --- /dev/null +++ b/src/dex/bebop/bebop.proto @@ -0,0 +1,15 @@ +syntax = "proto3"; + +package bebop; + +message PriceUpdate { + optional bytes base = 1; + optional bytes quote = 2; + optional uint64 last_update_ts = 3; + repeated float bids = 4 [packed=true]; + repeated float asks = 5 [packed=true]; +} + +message BebopPricingUpdate { + repeated PriceUpdate pairs = 1; +} \ No newline at end of file diff --git a/src/dex/bebop/bebop.ts b/src/dex/bebop/bebop.ts index 35200c226..fe69a4f19 100644 --- a/src/dex/bebop/bebop.ts +++ b/src/dex/bebop/bebop.ts @@ -120,7 +120,7 @@ export class Bebop extends SimpleExchange implements IDex { pricesReqParams: { url: BEBOP_WS_API_URL + - `/pmm/${BebopConfig['Bebop'][network].chainName}/v3/pricing`, + `/pmm/${BebopConfig['Bebop'][network].chainName}/v3/pricing?format=protobuf`, headers: { name: BEBOP_AUTH_NAME, authorization: this.bebopAuthToken, @@ -723,7 +723,7 @@ export class Bebop extends SimpleExchange implements IDex { } } - if (side == SwapSide.SELL) { + if (side === SwapSide.SELL) { const requiredAmount = BigInt(optimalSwapExchange.destAmount); const quoteAmount = BigInt( response.buyTokens[utils.getAddress(destToken.address)].amount, diff --git a/src/dex/bebop/config.ts b/src/dex/bebop/config.ts index dbd50a503..faa012088 100644 --- a/src/dex/bebop/config.ts +++ b/src/dex/bebop/config.ts @@ -33,5 +33,10 @@ export const BebopConfig: DexConfigMap = { chainName: 'optimism', middleTokens: ['0x7F5c764cBc14f9669B88837ca1490cCa17c31607'], }, + [Network.BSC]: { + settlementAddress: '0xbbbbbBB520d69a9775E85b458C58c648259FAD5F', + chainName: 'bsc', + middleTokens: ['0x55d398326f99059fF775485246999027B3197955'], + }, }, }; diff --git a/src/dex/bebop/constants.ts b/src/dex/bebop/constants.ts index b446d8c7c..847c67dba 100644 --- a/src/dex/bebop/constants.ts +++ b/src/dex/bebop/constants.ts @@ -6,7 +6,7 @@ export const BEBOP_API_URL = 'https://api.bebop.xyz'; export const BEBOP_WS_API_URL = 'wss://api.bebop.xyz'; export const BEBOP_GAS_COST = 120_000; export const BEBOP_AUTH_NAME = 'paraswap'; -export const BEBOP_QUOTE_TIMEOUT_MS = 2000; +export const BEBOP_QUOTE_TIMEOUT_MS = 3000; export const BEBOP_ERRORS_CACHE_KEY = 'errors'; export const BEBOP_RESTRICTED_CACHE_KEY = 'restricted'; // Restrict for BEBOP_RESTRICT_TTL_S if an error occured >= BEBOP_RESTRICT_COUNT_THRESHOLD times within BEBOP_RESTRICT_CHECK_INTERVAL_S interval diff --git a/src/dex/bebop/rate-fetcher.ts b/src/dex/bebop/rate-fetcher.ts index 0ad328d4e..37de21969 100644 --- a/src/dex/bebop/rate-fetcher.ts +++ b/src/dex/bebop/rate-fetcher.ts @@ -1,15 +1,30 @@ import { ETHER_ADDRESS, Network } from '../../constants'; import { IDexHelper } from '../../dex-helper'; import { Fetcher } from '../../lib/fetcher/fetcher'; -import { validateAndCast } from '../../lib/validators'; +import { validateAndCast, ValidationError } from '../../lib/validators'; import { Logger, Token } from '../../types'; import { + BebopLevel, + BebopPair, BebopPricingResponse, BebopRateFetcherConfig, BebopTokensResponse, } from './types'; -import { pricesResponseValidator, tokensResponseValidator } from './validators'; +import { + BebopPricingUpdate, + pricesResponseValidator, + tokensResponseValidator, +} from './validators'; import { WebSocketFetcher } from '../../lib/fetcher/wsFetcher'; +import { utils } from 'ethers'; + +export function levels_from_flat_array(values: number[]): BebopLevel[] { + const levels: BebopLevel[] = []; + for (let i = 0; i < values.length; i += 2) { + levels.push([values[i], values[i + 1]]); + } + return levels; +} export class RateFetcher { private pricesFetcher: WebSocketFetcher; @@ -35,10 +50,16 @@ export class RateFetcher { info: { requestOptions: config.rateConfig.pricesReqParams, caster: (data: unknown) => { - return validateAndCast( - data, - pricesResponseValidator, - ); + const dataBuffer = data as any; + const invalid = BebopPricingUpdate.verify(dataBuffer); + if (invalid) { + throw new ValidationError(invalid); + } + const update = BebopPricingUpdate.decode(dataBuffer); + const updateObject = BebopPricingUpdate.toObject(update, { + longs: Number, + }); + return this.parsePricingUpdate(updateObject); }, }, handler: this.handlePricesResponse.bind(this), @@ -68,6 +89,30 @@ export class RateFetcher { ); } + parsePricingUpdate(updateObject: any): BebopPricingResponse { + const pricingResponse: BebopPricingResponse = {}; + if (!updateObject.pairs || !updateObject.pairs.length) { + this.logger.warn('Update message did not include pairs', updateObject); + return pricingResponse; + } + for (const pairBook of updateObject.pairs) { + const pair = + utils.getAddress('0x' + pairBook.base.toString('hex')) + + '/' + + utils.getAddress('0x' + pairBook.quote.toString('hex')); + const lastUpdateTs = pairBook.lastUpdateTs; + const bids = pairBook.bids ? levels_from_flat_array(pairBook.bids) : []; + const asks = pairBook.asks ? levels_from_flat_array(pairBook.asks) : []; + const bebopPair: BebopPair = { + bids, + asks, + last_update_ts: lastUpdateTs, + }; + pricingResponse[pair] = bebopPair; + } + return pricingResponse; + } + start() { this.pricesFetcher.startPolling(); this.tokensFetcher.startPolling(); diff --git a/src/dex/bebop/validators.ts b/src/dex/bebop/validators.ts index 43db07320..bef694dc2 100644 --- a/src/dex/bebop/validators.ts +++ b/src/dex/bebop/validators.ts @@ -1,4 +1,6 @@ import joi from 'joi'; +import protobuf from 'protobufjs'; +import JSONDescriptor from './bebop.json'; const levelValidator = joi.array().items(joi.number()).length(2); @@ -27,3 +29,11 @@ export const tokensResponseValidator = joi.object({ export const blacklistResponseValidator = joi.object({ blacklist: joi.array().items(joi.string().min(1)).required(), }); + +// Original .proto def +// const root = protobuf.loadSync(__dirname + '/bebop.proto'); +// Use .json to not change build step to include .proto files +// console.log(JSON.stringify(root.toJSON(), null, 4)); + +const root = protobuf.Root.fromJSON(JSONDescriptor); +export const BebopPricingUpdate = root.lookupType('bebop.BebopPricingUpdate'); diff --git a/src/dex/cables/cables-e2e.test.ts b/src/dex/cables/cables-e2e.test.ts new file mode 100644 index 000000000..f1cdf62dd --- /dev/null +++ b/src/dex/cables/cables-e2e.test.ts @@ -0,0 +1,207 @@ +import dotenv from 'dotenv'; +dotenv.config(); +import { testE2E } from '../../../tests/utils-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +const sleepMs: number = 10000; +const slippage: number = 100; + +describe('Cables E2E', () => { + const dexKey = 'Cables'; + + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + [SwapSide.BUY, [ContractMethod.swapExactAmountOut]], + ]); + + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'USDC', + sellAmount: '500000', + buyAmount: '700000', + }, + { + name: 'USDT', + sellAmount: '600000', + buyAmount: '850000', + }, + ], + [ + { + name: 'WETH', + sellAmount: '100000000000000000', + buyAmount: '200000000000000000', + }, + { + name: 'USDT', + sellAmount: '6000000', + buyAmount: '8000000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + side === SwapSide.SELL + ? tokens[pair[0].name] + : tokens[pair[1].name], + side === SwapSide.SELL + ? tokens[pair[1].name] + : tokens[pair[0].name], + side === SwapSide.SELL + ? holders[pair[0].name] + : holders[pair[1].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + sleepMs, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + side === SwapSide.SELL + ? tokens[pair[1].name] + : tokens[pair[0].name], + side === SwapSide.SELL + ? tokens[pair[0].name] + : tokens[pair[1].name], + side === SwapSide.SELL + ? holders[pair[1].name] + : holders[pair[0].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + sleepMs, + ); + }); + }); + }); + }); + }), + ); + }); + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'USDC', + sellAmount: '1000000', + buyAmount: '700000', + }, + { + name: 'USDT', + sellAmount: '1000000', + buyAmount: '850000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + side === SwapSide.SELL + ? tokens[pair[0].name] + : tokens[pair[1].name], + side === SwapSide.SELL + ? tokens[pair[1].name] + : tokens[pair[0].name], + side === SwapSide.SELL + ? holders[pair[0].name] + : holders[pair[1].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + sleepMs, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + side === SwapSide.SELL + ? tokens[pair[1].name] + : tokens[pair[0].name], + side === SwapSide.SELL + ? tokens[pair[0].name] + : tokens[pair[1].name], + side === SwapSide.SELL + ? holders[pair[1].name] + : holders[pair[0].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + sleepMs, + ); + }); + }); + }); + }); + }), + ); + }); +}); diff --git a/src/dex/cables/cables-integration.test.ts b/src/dex/cables/cables-integration.test.ts new file mode 100644 index 000000000..62dc90c58 --- /dev/null +++ b/src/dex/cables/cables-integration.test.ts @@ -0,0 +1,391 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { Cables } from './cables'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, + sleep, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +async function testPricingOnNetwork( + cables: Cables, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], +) { + const networkTokens = Tokens[network]; + + const pools = await cables.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await cables.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (cables.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } +} + +describe('Cables', function () { + const dexKey = 'Cables'; + let blockNumber: number; + let cables: Cables; + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + const tokenASymbol = 'USDC'; + const tokenBSymbol = 'USDT'; + + const amountsForTokenA = [ + 0n, + 1n * BI_POWS[tokens[tokenASymbol].decimals], + 2n * BI_POWS[tokens[tokenASymbol].decimals], + 3n * BI_POWS[tokens[tokenASymbol].decimals], + 4n * BI_POWS[tokens[tokenASymbol].decimals], + 5n * BI_POWS[tokens[tokenASymbol].decimals], + 6n * BI_POWS[tokens[tokenASymbol].decimals], + 7n * BI_POWS[tokens[tokenASymbol].decimals], + 8n * BI_POWS[tokens[tokenASymbol].decimals], + 9n * BI_POWS[tokens[tokenASymbol].decimals], + 10n * BI_POWS[tokens[tokenASymbol].decimals], + ]; + + const amountsForTokenB = [ + 0n, + 1n * BI_POWS[tokens[tokenBSymbol].decimals], + 2n * BI_POWS[tokens[tokenBSymbol].decimals], + 3n * BI_POWS[tokens[tokenBSymbol].decimals], + 4n * BI_POWS[tokens[tokenBSymbol].decimals], + 5n * BI_POWS[tokens[tokenBSymbol].decimals], + 6n * BI_POWS[tokens[tokenBSymbol].decimals], + 7n * BI_POWS[tokens[tokenBSymbol].decimals], + 8n * BI_POWS[tokens[tokenBSymbol].decimals], + 9n * BI_POWS[tokens[tokenBSymbol].decimals], + 10n * BI_POWS[tokens[tokenBSymbol].decimals], + ]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + cables = new Cables(network, dexKey, dexHelper); + await cables.initializePricing(blockNumber); + await sleep(5000); + }); + + afterEach(async () => { + if (cables.releaseResources) cables.releaseResources(); + await sleep(5000); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.SELL, + amountsForTokenA, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.BUY, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.SELL, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.BUY, + amountsForTokenA, + ); + }); + + it.skip('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const cables = new Cables(network, dexKey, dexHelper); + const poolLiquidity = await cables.getTopPoolsForToken( + tokens[tokenASymbol].address, + 10, + ); + console.log( + `${tokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + console.log( + `${tokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!cables.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + }); + + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + const tokenASymbol = 'USDC'; + const tokenBSymbol = 'USDT'; + + const amountsForTokenA = [ + 0n, + 1n * BI_POWS[tokens[tokenASymbol].decimals], + 2n * BI_POWS[tokens[tokenASymbol].decimals], + 3n * BI_POWS[tokens[tokenASymbol].decimals], + 4n * BI_POWS[tokens[tokenASymbol].decimals], + 5n * BI_POWS[tokens[tokenASymbol].decimals], + 6n * BI_POWS[tokens[tokenASymbol].decimals], + 7n * BI_POWS[tokens[tokenASymbol].decimals], + 8n * BI_POWS[tokens[tokenASymbol].decimals], + 9n * BI_POWS[tokens[tokenASymbol].decimals], + 10n * BI_POWS[tokens[tokenASymbol].decimals], + ]; + + const amountsForTokenB = [ + 0n, + 1n * BI_POWS[tokens[tokenBSymbol].decimals], + 2n * BI_POWS[tokens[tokenBSymbol].decimals], + 3n * BI_POWS[tokens[tokenBSymbol].decimals], + 4n * BI_POWS[tokens[tokenBSymbol].decimals], + 5n * BI_POWS[tokens[tokenBSymbol].decimals], + 6n * BI_POWS[tokens[tokenBSymbol].decimals], + 7n * BI_POWS[tokens[tokenBSymbol].decimals], + 8n * BI_POWS[tokens[tokenBSymbol].decimals], + 9n * BI_POWS[tokens[tokenBSymbol].decimals], + 10n * BI_POWS[tokens[tokenBSymbol].decimals], + ]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + cables = new Cables(network, dexKey, dexHelper); + await cables.initializePricing(blockNumber); + await sleep(5000); + }); + + afterEach(async () => { + if (cables.releaseResources) cables.releaseResources(); + await sleep(5000); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.SELL, + amountsForTokenA, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenASymbol} ${tokenBSymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.BUY, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume SELL ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.SELL, + amountsForTokenB, + ); + }); + + it(`getPoolIdentifiers and getPricesVolume BUY ${tokenBSymbol} ${tokenASymbol}`, async function () { + await testPricingOnNetwork( + cables, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.BUY, + amountsForTokenA, + ); + }); + + describe.skip('getTopPoolsForToken', () => { + it('USDC getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const tokenSymbol = 'USDC'; + const cables = new Cables(network, dexKey, dexHelper); + const poolLiquidity = await cables.getTopPoolsForToken( + tokens[tokenSymbol].address, + 10, + ); + console.log( + `${tokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!cables.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + + it('WETH getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const tokenSymbol = 'WETH'; + const cables = new Cables(network, dexKey, dexHelper); + const poolLiquidity = await cables.getTopPoolsForToken( + tokens[tokenSymbol].address, + 10, + ); + console.log( + `${tokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!cables.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + + it('ETH getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const tokenSymbol = 'ETH'; + const cables = new Cables(network, dexKey, dexHelper); + const poolLiquidity = await cables.getTopPoolsForToken( + tokens[tokenSymbol].address, + 10, + ); + console.log( + `${tokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!cables.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + + it('ARB getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const tokenSymbol = 'ARB'; + const cables = new Cables(network, dexKey, dexHelper); + const poolLiquidity = await cables.getTopPoolsForToken( + tokens[tokenSymbol].address, + 10, + ); + console.log( + `${tokenASymbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + if (!cables.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][tokenASymbol].address, + dexKey, + ); + } + }); + }); + }); +}); diff --git a/src/dex/cables/cables.ts b/src/dex/cables/cables.ts new file mode 100644 index 000000000..eb14b3f8e --- /dev/null +++ b/src/dex/cables/cables.ts @@ -0,0 +1,823 @@ +import { + Address, + NumberAsString, + OptimalSwapExchange, + SwapSide, +} from '@paraswap/core'; +import { assert, AsyncOrSync } from 'ts-essentials'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { ETHER_ADDRESS, Network, NULL_ADDRESS } from '../../constants'; +import { IDexHelper } from '../../dex-helper'; +import { + AdapterExchangeParam, + DexExchangeParam, + ExchangePrices, + ExchangeTxInfo, + Logger, + PoolLiquidity, + PoolPrices, + PreprocessTransactionOptions, + Token, + TransferFeeParams, +} from '../../types'; +import { getDexKeysWithNetwork, Utils } from '../../utils'; +import { IDex } from '../idex'; +import { SimpleExchange } from '../simple-exchange'; +import { CablesConfig } from './config'; +import { + CABLES_API_BLACKLIST_POLLING_INTERVAL_MS, + CABLES_API_PAIRS_POLLING_INTERVAL_MS, + CABLES_API_PRICES_POLLING_INTERVAL_MS, + CABLES_API_TOKENS_POLLING_INTERVAL_MS, + CABLES_API_URL, + CABLES_BLACKLIST_CACHES_TTL_S, + CABLES_ERRORS_CACHE_KEY, + CABLES_FIRM_QUOTE_TIMEOUT_MS, + CABLES_GAS_COST, + CABLES_PAIRS_CACHES_TTL_S, + CABLES_PRICES_CACHES_TTL_S, + CABLES_RESTRICT_CHECK_INTERVAL_MS, + CABLES_RESTRICT_COUNT_THRESHOLD, + CABLES_RESTRICT_TTL_S, + CABLES_RESTRICTED_CACHE_KEY, + CABLES_TOKENS_CACHES_TTL_S, +} from './constants'; +import { CablesRateFetcher } from './rate-fetcher'; +import { + CablesData, + CablesRFQResponse, + RestrictData, + SlippageError, +} from './types'; +import mainnetRFQAbi from '../../abi/cables/CablesMainnetRFQ.json'; +import { Interface } from 'ethers/lib/utils'; +import BigNumber from 'bignumber.js'; +import { ethers } from 'ethers'; +import { BI_MAX_UINT256 } from '../../bigint-constants'; +import _ from 'lodash'; +import { BebopData } from '../bebop/types'; + +export class Cables extends SimpleExchange implements IDex { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(CablesConfig); + + readonly isStatePollingDex = true; + + private rateFetcher: CablesRateFetcher; + + logger: Logger; + private tokensMap: { [address: string]: Token } = {}; + + hasConstantPriceLargeAmounts: boolean = false; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + readonly mainnetRFQAddress: string = CablesConfig['Cables'][network] + .mainnetRFQAddress, + protected rfqInterface = new Interface(mainnetRFQAbi), + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + + this.rateFetcher = new CablesRateFetcher( + this.dexHelper, + this.dexKey, + this.network, + this.logger, + { + rateConfig: { + pairsReqParams: { + url: CABLES_API_URL + '/pairs', + }, + pricesReqParams: { + url: CABLES_API_URL + '/prices', + }, + blacklistReqParams: { + url: CABLES_API_URL + '/blacklist', + }, + tokensReqParams: { + url: CABLES_API_URL + '/tokens', + }, + + pricesIntervalMs: CABLES_API_PRICES_POLLING_INTERVAL_MS, + pricesCacheTTLSecs: CABLES_PRICES_CACHES_TTL_S, + pricesCacheKey: 'prices', + + pairsIntervalMs: CABLES_API_PAIRS_POLLING_INTERVAL_MS, + pairsCacheTTLSecs: CABLES_PAIRS_CACHES_TTL_S, + pairsCacheKey: 'pairs', + + tokensIntervalMs: CABLES_API_TOKENS_POLLING_INTERVAL_MS, + tokensCacheTTLSecs: CABLES_TOKENS_CACHES_TTL_S, + tokensCacheKey: 'tokens', + + blacklistIntervalMs: CABLES_API_BLACKLIST_POLLING_INTERVAL_MS, + blacklistCacheTTLSecs: CABLES_BLACKLIST_CACHES_TTL_S, + blacklistCacheKey: 'blacklist', + }, + }, + ); + } + + async preProcessTransaction?( + optimalSwapExchange: OptimalSwapExchange, + srcToken: Token, + destToken: Token, + side: SwapSide, + options: PreprocessTransactionOptions, + ): Promise<[OptimalSwapExchange, ExchangeTxInfo]> { + if (await this.isBlacklisted(options.txOrigin)) { + this.logger.warn( + `${this.dexKey}-${this.network}: blacklisted TX Origin address '${options.txOrigin}' trying to build a transaction. Bailing...`, + ); + throw new Error( + `${this.dexKey}-${ + this.network + }: user=${options.txOrigin.toLowerCase()} is blacklisted`, + ); + } + + if (BigInt(optimalSwapExchange.srcAmount) === 0n) { + throw new Error('getFirmRate failed with srcAmount === 0'); + } + + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + const swapIdentifier = `${this.dexKey}_${normalizedSrcToken.address}_${normalizedDestToken.address}_${side}`; + + try { + let makerToken = normalizedDestToken; + let takerToken = normalizedSrcToken; + + const isSell = side === SwapSide.SELL; + const isBuy = side === SwapSide.BUY; + + const rfqParams = { + makerAsset: ethers.utils.getAddress(makerToken.address), + takerAsset: ethers.utils.getAddress(takerToken.address), + ...(isBuy && { makerAmount: optimalSwapExchange.destAmount }), + ...(isSell && { takerAmount: optimalSwapExchange.srcAmount }), + userAddress: options.executionContractAddress, + chainId: String(this.network), + }; + + const rfq: CablesRFQResponse = await this.dexHelper.httpRequest.post( + `${CABLES_API_URL}/quote`, + rfqParams, + CABLES_FIRM_QUOTE_TIMEOUT_MS, + ); + + if (!rfq) { + throw new Error( + 'Failed to fetch RFQ' + + swapIdentifier + + JSON.stringify(rfq + 'params' + rfqParams), + ); + } + + const { order } = rfq; + + assert( + order.makerAsset.toLowerCase() === makerToken.address, + `QuoteData makerAsset=${order.makerAsset} is different from Paraswap makerAsset=${makerToken.address}`, + ); + assert( + order.takerAsset.toLowerCase() === takerToken.address, + `QuoteData takerAsset=${order.takerAsset} is different from Paraswap takerAsset=${takerToken.address}`, + ); + if (isSell) { + assert( + order.takerAmount === optimalSwapExchange.srcAmount, + `QuoteData takerAmount=${order.takerAmount} is different from Paraswap srcAmount=${optimalSwapExchange.srcAmount}`, + ); + } else { + assert( + order.makerAmount === optimalSwapExchange.destAmount, + `QuoteData makerAmount=${order.makerAmount} is different from Paraswap destAmount=${optimalSwapExchange.destAmount}`, + ); + } + + const expiryAsBigInt = BigInt(order.expiry); + const minDeadline = expiryAsBigInt > 0 ? expiryAsBigInt : BI_MAX_UINT256; + + if (side === SwapSide.BUY) { + const requiredAmount = BigInt(optimalSwapExchange.srcAmount); + const quoteAmount = BigInt(order.takerAmount); + const requiredAmountWithSlippage = new BigNumber( + requiredAmount.toString(), + ) + .multipliedBy(options.slippageFactor) + .toFixed(0); + if (quoteAmount > BigInt(requiredAmountWithSlippage)) { + throw new SlippageError( + `Slipped, factor: ${quoteAmount.toString()} > ${requiredAmountWithSlippage}`, + ); + } + } else { + const requiredAmount = BigInt(optimalSwapExchange.destAmount); + const quoteAmount = BigInt(order.makerAmount); + const requiredAmountWithSlippage = new BigNumber( + requiredAmount.toString(), + ) + .multipliedBy(options.slippageFactor) + .toFixed(0); + if (quoteAmount < BigInt(requiredAmountWithSlippage)) { + throw new SlippageError( + `Slipped, factor: ${ + options.slippageFactor + } ${quoteAmount.toString()} < ${requiredAmountWithSlippage}`, + ); + } + } + + return [ + { + ...optimalSwapExchange, + data: { + quoteData: order, + }, + }, + { deadline: minDeadline }, + ]; + } catch (e: any) { + const message = `${this.dexKey}-${this.network}: ${e}`; + this.logger.error(message); + if (!e?.isSlippageError) { + this.restrict(); + } + throw new Error(message); + } + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: BebopData, + side: SwapSide, + ): AdapterExchangeParam { + return { + targetExchange: this.mainnetRFQAddress, + payload: '0x', + networkFee: '0', + }; + } + + getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: CablesData, + side: SwapSide, + ): DexExchangeParam { + const { quoteData } = data; + + assert( + quoteData !== undefined, + `${this.dexKey}-${this.network}: quoteData undefined`, + ); + + const swapFunction = 'partialSwap'; + const swapFunctionParams = [ + [ + quoteData.nonceAndMeta, + quoteData.expiry, + quoteData.makerAsset, + quoteData.takerAsset, + quoteData.maker, + quoteData.taker, + quoteData.makerAmount, + quoteData.takerAmount, + ], + quoteData.signature, + // might be overwritten on Executors + quoteData.takerAmount, + ]; + + const exchangeData = this.rfqInterface.encodeFunctionData( + swapFunction, + swapFunctionParams, + ); + + const fromAmount = ethers.utils.defaultAbiCoder.encode( + ['uint256'], + [quoteData.takerAmount], + ); + + const filledAmountIndex = exchangeData + .replace('0x', '') + .lastIndexOf(fromAmount.replace('0x', '')); + + const filledAmountPos = + (filledAmountIndex !== -1 ? filledAmountIndex : exchangeData.length) / 2; + + return { + exchangeData, + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: false, + targetExchange: this.mainnetRFQAddress, + returnAmountPos: undefined, + insertFromAmountPos: filledAmountPos, + }; + } + + normalizeToken(token: Token): Token { + return { + ...token, + address: this.normalizeTokenAddress(token.address), + }; + } + + normalizeTokenAddress(address: Address): Address { + return address.toLowerCase(); + } + + getTokenFromAddress(address: Address): Token { + return this.tokensMap[this.normalizeAddress(address)]; + } + + getPoolIdentifier(srcAddress: Address, destAddress: Address) { + return `${this.dexKey}_${srcAddress}_${destAddress}`.toLowerCase(); + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (!srcToken || !destToken) { + return []; + } + + if (srcToken.address.toLowerCase() === destToken.address.toLowerCase()) { + return []; + } + + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + + const pairData = await this.getPairData( + normalizedSrcToken, + normalizedDestToken, + ); + + if (!pairData) { + return []; + } + + return [ + this.getPoolIdentifier( + normalizedSrcToken.address, + normalizedDestToken.address, + ), + ]; + } + + calculateOrderPrice( + amounts: bigint[], + orderbook: string[][], + baseToken: Token, + quoteToken: Token, + isInputQuote: boolean, + ) { + let result = []; + + for (let i = 0; i < amounts.length; i++) { + let amt = amounts[i]; + if (amt === 0n) { + result.push(amt); + continue; + } + + let decimals = baseToken.decimals; + let out_decimals = quoteToken.decimals; + + let price = this.calculatePriceSwap( + orderbook, + Number(amt) / 10 ** decimals, + isInputQuote, + ); + result.push(BigInt(Math.round(price * 10 ** out_decimals))); + } + return result; + } + + calculatePriceSwap( + prices: string[][], + requiredQty: number, + qtyMode: Boolean, + ) { + let sumBaseQty = 0; + let sumQuoteQty = 0; + const selectedRows: string[][] = []; + + const isBase = qtyMode; + const isQuote = !qtyMode; + + for (const [price, volume] of prices) { + if (isBase) { + if (sumBaseQty >= requiredQty) { + break; + } + } + + if (isQuote) { + if (sumQuoteQty >= requiredQty) { + break; + } + } + + let currentBaseQty = Number(volume); + let currentQuoteQty = Number(volume) * Number(price); + + const overQty = isBase + ? currentBaseQty + sumBaseQty > requiredQty + : currentQuoteQty + sumQuoteQty > requiredQty; + + if (overQty) { + if (isBase) { + currentBaseQty = requiredQty - sumBaseQty; + currentQuoteQty = currentBaseQty * Number(price); + } + + if (isQuote) { + currentQuoteQty = requiredQty - sumQuoteQty; + currentBaseQty = + currentQuoteQty * + new BigNumber(1).dividedBy(new BigNumber(price)).toNumber(); + } + } + + sumBaseQty += currentBaseQty; + sumQuoteQty += currentQuoteQty; + selectedRows.push([price, currentBaseQty.toString()]); + } + + const vSumBase = selectedRows.reduce((sum: number, [price, volume]) => { + return sum + Number(price) * Number(volume); + }, 0); + + const price = new BigNumber(vSumBase) + .dividedBy(new BigNumber(sumBaseQty)) + .toNumber(); + + if (isBase) { + return requiredQty / price; + } else { + return requiredQty * price; + } + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + transferFees?: TransferFeeParams, + isFirstSwap?: boolean, + ): Promise | null> { + try { + const normalizedSrcToken = this.normalizeToken(srcToken); + const normalizedDestToken = this.normalizeToken(destToken); + // If: same token, return null + if ( + normalizedSrcToken.address.toLowerCase() === + normalizedDestToken.address.toLowerCase() + ) { + return null; + } + + const isRestricted = await this.isRestricted(); + if (isRestricted) { + return null; + } + + await this.setTokensMap(); + + normalizedSrcToken.symbol = + this.tokensMap[normalizedSrcToken.address].symbol; + normalizedDestToken.symbol = + this.tokensMap[normalizedDestToken.address!].symbol; + + // ---------- Pools ---------- + let pools = + limitPools || + (await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber)); + if (pools.length === 0) return null; + + // ---------- Prices ---------- + const priceMap = await this.getCachedPrices(); + + if (!priceMap) return null; + + let isInputQuote = false; + let pairKey = `${normalizedSrcToken.symbol}/${normalizedDestToken.symbol}`; + const pairsKeys = Object.keys(priceMap); + + if (!pairsKeys.includes(pairKey)) { + // Revert + isInputQuote = true; + pairKey = `${normalizedDestToken.symbol}/${normalizedSrcToken.symbol}`; + if (!pairsKeys.includes(pairKey)) { + return null; + } + } + + /** + * Orderbook + */ + const priceData = priceMap[pairKey]; + + let orderbook: any[] = []; + if (side === SwapSide.BUY) { + orderbook = priceData.asks; + } else { + orderbook = priceData.bids; + } + if (orderbook?.length === 0) { + throw new Error(`Empty orderbook for ${pairKey}`); + } + + const prices = this.calculateOrderPrice( + amounts, + orderbook, + side === SwapSide.SELL ? srcToken : destToken, + side === SwapSide.SELL ? destToken : srcToken, + side === SwapSide.SELL ? isInputQuote : !isInputQuote, + ); + + const result = [ + { + prices: prices, + unit: BigInt(normalizedDestToken.decimals), + exchange: this.dexKey, + gasCost: CABLES_GAS_COST, + poolAddresses: [this.mainnetRFQAddress], + data: {}, + }, + ]; + + return result; + } catch (e: unknown) { + this.logger.error( + `Error in getPricesVolume`, + { + srcToken: srcToken.address || srcToken.symbol, + destToken: destToken.address || destToken.symbol, + side, + }, + e, + ); + return null; + } + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return ( + CALLDATA_GAS_COST.DEX_OVERHEAD + + // addresses: makerAsset, takerAsset, maker, taker + CALLDATA_GAS_COST.ADDRESS * 4 + + // uint256: expiry + CALLDATA_GAS_COST.wordNonZeroBytes(16) + + // uint256: nonceAndMeta, makerAmount, takerAmount + CALLDATA_GAS_COST.AMOUNT * 3 + + // bytes: _signature (65 bytes) + CALLDATA_GAS_COST.FULL_WORD * 2 + + CALLDATA_GAS_COST.OFFSET_SMALL + ); + } + + async initializePricing(blockNumber: number): Promise { + if (!this.dexHelper.config.isSlave) { + this.rateFetcher.start(); + } + return; + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return null; + } + + releaseResources?(): AsyncOrSync { + if (!this.dexHelper.config.isSlave && this.rateFetcher) { + this.rateFetcher.stop(); + } + } + + normalizeAddress(address: string): string { + return address.toLowerCase() === ETHER_ADDRESS + ? NULL_ADDRESS + : address.toLowerCase(); + } + + async setTokensMap() { + const tokens = await this.getCachedTokens(); + + if (tokens) { + this.tokensMap = Object.keys(tokens).reduce((acc, key) => { + //@ts-ignore + acc[tokens[key].address.toLowerCase()] = tokens[key]; + return acc; + }, {}); + } + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + return []; + } + + /** + * CACHED UTILS + */ + async getCachedTokens(): Promise { + const cachedTokens = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.rateFetcher.tokensCacheKey, + ); + + return cachedTokens ? JSON.parse(cachedTokens) : {}; + } + + async getCachedPairs(): Promise { + const cachedPairs = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.rateFetcher.pairsCacheKey, + ); + + return cachedPairs ? JSON.parse(cachedPairs) : {}; + } + + async getCachedPrices(): Promise { + const cachedPrices = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.rateFetcher.pricesCacheKey, + ); + + return cachedPrices ? JSON.parse(cachedPrices) : {}; + } + + async getCachedTokensAddr(): Promise { + const tokens = await this.getCachedTokens(); + const tokensAddr: Record = {}; + for (const key of Object.keys(tokens)) { + tokensAddr[tokens[key].symbol.toLowerCase()] = tokens[key].address; + } + return tokensAddr; + } + + getPairString(baseToken: Token, quoteToken: Token): string { + return `${baseToken.symbol}/${quoteToken.symbol}`.toLowerCase(); + } + + // Function to find a key by address + private findKeyByAddress = ( + jsonData: Record, + targetAddress: string, + ): string | undefined => { + const entries = Object.entries(jsonData); + const foundEntry = entries.find( + ([_, value]) => + value.address.toLowerCase() === targetAddress.toLowerCase(), + ); + return foundEntry ? foundEntry[0] : undefined; + }; + + async getPairData(srcToken: Token, destToken: Token): Promise { + if (srcToken.address === destToken.address) { + return null; + } + + const cachedTokens = await this.getCachedTokens(); + + srcToken.symbol = this.findKeyByAddress(cachedTokens, srcToken.address); + destToken.symbol = this.findKeyByAddress(cachedTokens, destToken.address); + + const cachedPairs = await this.getCachedPairs(); + + const potentialPairs = [ + { + base: srcToken.symbol, + quote: destToken.symbol, + identifier: this.getPairString(srcToken, destToken), + isSrcBase: true, + }, + { + base: destToken.symbol, + quote: srcToken.symbol, + identifier: this.getPairString(destToken, srcToken), + isSrcBase: false, + }, + ]; + + for (const pair of potentialPairs) { + if (pair.identifier in cachedPairs) { + const pairData = cachedPairs[pair.identifier]; + pairData.isSrcBase = pair.isSrcBase; + return pairData; + } + } + return null; + } + + async isBlacklisted(txOrigin: Address): Promise { + const cachedBlacklist = await this.dexHelper.cache.get( + this.dexKey, + this.network, + this.rateFetcher.blacklistCacheKey, + ); + + if (cachedBlacklist) { + const blacklist = JSON.parse(cachedBlacklist) as string[]; + return blacklist.includes(txOrigin.toLowerCase()); + } + + return false; + } + + async isRestricted(): Promise { + const result = await this.dexHelper.cache.get( + this.dexKey, + this.network, + CABLES_RESTRICTED_CACHE_KEY, + ); + + return result === 'true'; + } + + async restrict() { + const errorsDataRaw = await this.dexHelper.cache.get( + this.dexKey, + this.network, + CABLES_ERRORS_CACHE_KEY, + ); + + const errorsData: RestrictData = Utils.Parse(errorsDataRaw); + const ERRORS_TTL_S = Math.floor(CABLES_RESTRICT_CHECK_INTERVAL_MS / 1000); + + if ( + !errorsData || + errorsData?.addedDatetimeMs + CABLES_RESTRICT_CHECK_INTERVAL_MS < + Date.now() + ) { + this.logger.warn( + `${this.dexKey}-${this.network}: First encounter of error OR error ocurred outside of threshold, setting up counter`, + ); + const data: RestrictData = { + count: 1, + addedDatetimeMs: Date.now(), + }; + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + CABLES_ERRORS_CACHE_KEY, + ERRORS_TTL_S, + Utils.Serialize(data), + ); + return; + } else { + if (errorsData.count + 1 >= CABLES_RESTRICT_COUNT_THRESHOLD) { + this.logger.warn( + `${this.dexKey}-${this.network}: Restricting due to error count=${ + errorsData.count + 1 + } within ${CABLES_RESTRICT_CHECK_INTERVAL_MS / 1000 / 60} minutes`, + ); + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + CABLES_RESTRICTED_CACHE_KEY, + CABLES_RESTRICT_TTL_S, + 'true', + ); + } else { + this.logger.warn( + `${this.dexKey}-${this.network}: Error count increased`, + ); + const data: RestrictData = { + count: errorsData.count + 1, + addedDatetimeMs: errorsData.addedDatetimeMs, + }; + await this.dexHelper.cache.setex( + this.dexKey, + this.network, + CABLES_RESTRICTED_CACHE_KEY, + ERRORS_TTL_S, + Utils.Serialize(data), + ); + } + } + } +} diff --git a/src/dex/cables/config.ts b/src/dex/cables/config.ts new file mode 100644 index 000000000..07c4ee343 --- /dev/null +++ b/src/dex/cables/config.ts @@ -0,0 +1,13 @@ +import { Network } from '../../constants'; +import { DexConfigMap } from '../../types'; + +export const CablesConfig: DexConfigMap<{ mainnetRFQAddress: string }> = { + Cables: { + [Network.AVALANCHE]: { + mainnetRFQAddress: '0xfA12DCB2e1FD72bD92E8255Db6A781b2c76adC20', + }, + [Network.ARBITRUM]: { + mainnetRFQAddress: '0xfA12DCB2e1FD72bD92E8255Db6A781b2c76adC20', + }, + }, +}; diff --git a/src/dex/cables/constants.ts b/src/dex/cables/constants.ts new file mode 100644 index 000000000..cd2ec378d --- /dev/null +++ b/src/dex/cables/constants.ts @@ -0,0 +1,33 @@ +import BigNumber from 'bignumber.js'; + +/** + * Cables + */ +export const CABLES_API_URL = + 'https://cables-evm-rfq-service.cryptosrvc.com/v1'; + +export const CABLES_PRICES_CACHES_TTL_S = 10; +export const CABLES_API_PRICES_POLLING_INTERVAL_MS = 2000; // 2 sec + +export const CABLES_PAIRS_CACHES_TTL_S = 12; +export const CABLES_API_PAIRS_POLLING_INTERVAL_MS = 10000; // 10 sec + +export const CABLES_BLACKLIST_CACHES_TTL_S = 60; +export const CABLES_API_BLACKLIST_POLLING_INTERVAL_MS = 30000; // 30 sec + +export const CABLES_TOKENS_CACHES_TTL_S = 60; +export const CABLES_API_TOKENS_POLLING_INTERVAL_MS = 30000; // 30 sec + +export const CABLES_FIRM_QUOTE_TIMEOUT_MS = 2000; + +export const CABLES_RESTRICTED_CACHE_KEY = 'restricted'; + +export const CABLES_ERRORS_CACHE_KEY = 'errors'; + +export const CABLES_RESTRICT_CHECK_INTERVAL_MS = 1000 * 60 * 3; // 3 min + +export const CABLES_RESTRICT_COUNT_THRESHOLD = 3; + +export const CABLES_RESTRICT_TTL_S = 10 * 60; // 10 min + +export const CABLES_GAS_COST = 120_000; diff --git a/src/dex/cables/rate-fetcher.ts b/src/dex/cables/rate-fetcher.ts new file mode 100644 index 000000000..3df0b0f83 --- /dev/null +++ b/src/dex/cables/rate-fetcher.ts @@ -0,0 +1,212 @@ +import { Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper'; +import { Fetcher } from '../../lib/fetcher/fetcher'; +import { validateAndCast } from '../../lib/validators'; +import { Logger, Token } from '../../types'; +import { PairData } from '../cables/types'; +import { + CablesBlacklistResponse, + CablesPairsResponse, + CablesPricesResponse, + CablesRateFetcherConfig, + CablesTokensResponse, +} from './types'; +import { + blacklistResponseValidator, + pairsResponseValidator, + pricesResponseValidator, + tokensResponseValidator, +} from './validators'; + +export class CablesRateFetcher { + public tokensFetcher: Fetcher; + public tokensCacheKey: string; + public tokensCacheTTL: number; + + public pairsFetcher: Fetcher; + public pairsCacheKey: string; + public pairsCacheTTL: number; + + public pricesFetcher: Fetcher; + public pricesCacheKey: string; + public pricesCacheTTL: number; + + public blacklistFetcher: Fetcher; + public blacklistCacheKey: string; + public blacklistCacheTTL: number; + + constructor( + private dexHelper: IDexHelper, + private dexKey: string, + private network: Network, + private logger: Logger, + config: CablesRateFetcherConfig, + ) { + this.tokensCacheKey = config.rateConfig.tokensCacheKey; + this.tokensCacheTTL = config.rateConfig.tokensCacheTTLSecs; + + this.pairsCacheKey = config.rateConfig.pairsCacheKey; + this.pairsCacheTTL = config.rateConfig.pairsCacheTTLSecs; + + this.pricesCacheKey = config.rateConfig.pricesCacheKey; + this.pricesCacheTTL = config.rateConfig.pricesCacheTTLSecs; + + this.blacklistCacheKey = config.rateConfig.blacklistCacheKey; + this.blacklistCacheTTL = config.rateConfig.blacklistCacheTTLSecs; + + this.pairsFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.pairsReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + pairsResponseValidator, + ); + }, + }, + handler: this.handlePairsResponse.bind(this), + }, + config.rateConfig.pairsIntervalMs, + logger, + ); + + this.pricesFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.pricesReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + pricesResponseValidator, + ); + }, + }, + handler: this.handlePricesResponse.bind(this), + }, + config.rateConfig.pricesIntervalMs, + logger, + ); + + this.blacklistFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.blacklistReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + blacklistResponseValidator, + ); + }, + }, + handler: this.handleBlacklistResponse.bind(this), + }, + config.rateConfig.blacklistIntervalMs, + logger, + ); + + this.tokensFetcher = new Fetcher( + dexHelper.httpRequest, + { + info: { + requestOptions: config.rateConfig.tokensReqParams, + caster: (data: unknown) => { + return validateAndCast( + data, + tokensResponseValidator, + ); + }, + }, + handler: this.handleTokensResponse.bind(this), + }, + config.rateConfig.tokensIntervalMs, + logger, + ); + } + + /** + * Utils + */ + start() { + this.pairsFetcher.startPolling(); + this.pricesFetcher.startPolling(); + this.blacklistFetcher.startPolling(); + this.tokensFetcher.startPolling(); + } + stop() { + this.pairsFetcher.stopPolling(); + this.pricesFetcher.stopPolling(); + this.blacklistFetcher.stopPolling(); + this.tokensFetcher.stopPolling(); + } + + private handlePairsResponse(res: CablesPairsResponse): void { + const networkId = String(this.network); + const pairs = res.pairs[networkId]; + + let normalized_pairs: { [token: string]: PairData } = {}; + Object.keys(pairs).forEach(key => { + normalized_pairs[key.toLowerCase()] = pairs[key]; + }); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.pairsCacheKey, + this.pairsCacheTTL, + JSON.stringify(normalized_pairs), + ); + } + + private handlePricesResponse(res: CablesPricesResponse): void { + const networkId = String(this.network); + const prices = res.prices[networkId]; + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.pricesCacheKey, + this.pricesCacheTTL, + JSON.stringify(prices), + ); + } + + private handleBlacklistResponse(res: CablesBlacklistResponse): void { + const { blacklist } = res; + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.blacklistCacheKey, + this.blacklistCacheTTL, + JSON.stringify(blacklist.map(item => item.toLowerCase())), + ); + } + + // Convert addresses to lowercase + private normalizeAddressesToLowerCase = ( + jsonData: Record, + ) => { + Object.keys(jsonData).forEach(key => { + jsonData[key].address = jsonData[key].address.toLowerCase(); + }); + return jsonData; + }; + + private async handleTokensResponse(res: CablesTokensResponse): Promise { + const networkId = String(this.network); + const tokens = res.tokens[networkId]; + + const normalizedTokens = this.normalizeAddressesToLowerCase(tokens); + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + this.tokensCacheKey, + this.tokensCacheTTL, + JSON.stringify(normalizedTokens), + ); + } +} diff --git a/src/dex/cables/types.ts b/src/dex/cables/types.ts new file mode 100644 index 000000000..b8f5d14de --- /dev/null +++ b/src/dex/cables/types.ts @@ -0,0 +1,113 @@ +import { RequestHeaders } from '../../dex-helper'; +import { Token } from '../../types'; +import { Method } from '../../dex-helper/irequest-wrapper'; +import { AugustusRFQOrderData } from '../augustus-rfq'; + +export type CablesRFQResponse = { + order: AugustusRFQOrderData; + signature: string; +}; + +export type CablesData = { + quoteData?: AugustusRFQOrderData; +}; +/** + * Types + */ +export type PairData = { + base: string; + quote: string; + liquidityUSD: number; +}; + +type PriceAndAmount = [string, string]; + +type PriceData = { + bids: PriceAndAmount[]; + asks: PriceAndAmount[]; +}; + +type PriceDataMap = { + [network: string]: { + [pair: string]: PriceData; + }; +}; + +type TokenDataMap = { + [network: string]: { + [token: string]: Token; + }; +}; + +type PairsDataMap = { + [network: string]: { + [token: string]: PairData; + }; +}; + +/** + * Responses + */ +export type CablesPricesResponse = { + prices: PriceDataMap; +}; +export type CablesBlacklistResponse = { + blacklist: string[]; +}; +export type CablesTokensResponse = { + tokens: TokenDataMap; +}; +export type CablesPairsResponse = { + pairs: PairsDataMap; +}; + +/** + * Rate Fetcher + */ +export type CablesRateFetcherConfig = { + rateConfig: { + pairsReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + pricesReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + blacklistReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + tokensReqParams: { + url: string; + headers?: RequestHeaders; + params?: any; + }; + pairsIntervalMs: number; + pricesIntervalMs: number; + blacklistIntervalMs: number; + tokensIntervalMs: number; + + pairsCacheKey: string; + pricesCacheKey: string; + blacklistCacheKey: string; + tokensCacheKey: string; + + blacklistCacheTTLSecs: number; + pairsCacheTTLSecs: number; + pricesCacheTTLSecs: number; + tokensCacheTTLSecs: number; + }; +}; + +export type RestrictData = { + count: number; + addedDatetimeMs: number; +} | null; + +export class SlippageError extends Error { + isSlippageError = true; +} diff --git a/src/dex/cables/validators.test.ts b/src/dex/cables/validators.test.ts new file mode 100644 index 000000000..abc36cf82 --- /dev/null +++ b/src/dex/cables/validators.test.ts @@ -0,0 +1,151 @@ +import Joi from 'joi'; +import { + pairsResponseValidator, + pricesResponseValidator, + tokensResponseValidator, + blacklistResponseValidator, +} from './validators'; // + +describe('Validation Schemas', () => { + describe('pairsResponseValidator', () => { + it('should validate correct pairs response', () => { + const validData = { + pairs: { '43114': { 'USDC/USDT': { base: 'USDC', quote: 'USDT' } } }, + }; + const { error } = pairsResponseValidator.validate(validData); + expect(error).toBeUndefined(); + }); + + it('should invalidate incorrect pairs response', () => { + const invalidData = { pairs: { '43114': 'USDC/USDT' } }; + const { error } = pairsResponseValidator.validate(invalidData); + expect(error).toBeDefined(); + }); + }); + + describe('pricesResponseValidator', () => { + it('should validate correct prices response', () => { + const validData = { + prices: { + '43114': { + 'USDC/USDT': { + bids: [ + ['0.9996', '244305.9'], + ['0.9995', '236021.6'], + ], + asks: [ + ['0.9996', '244305.9'], + ['0.9995', '236021.6'], + ], + }, + }, + }, + }; + const { error } = pricesResponseValidator.validate(validData); + expect(error).toBeUndefined(); + }); + + it('should invalidate incorrect prices response', () => { + const invalidData = { + prices: { + chain1: { + bids: [ + ['1000'], // invalid entry length + ], + asks: [['1010', '1']], + }, + }, + }; + const { error } = pricesResponseValidator.validate(invalidData); + expect(error).toBeDefined(); + }); + }); + + describe('tokensResponseValidator', () => { + it('should validate correct tokens response', () => { + const validData = { + tokens: { + '43114': { + AVAX: { + symbol: 'AVAX', + decimals: 18, + name: 'AVAX', + address: '0x0000000000000000000000000000000000000000', + }, + WAVAX: { + symbol: 'WAVAX', + decimals: 18, + name: 'WAVAX', + address: '0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7', + }, + 'WETH.e': { + symbol: 'WETH.e', + decimals: 18, + name: 'WETH.e', + address: '0x49D5c2BdFfac6CE2BFdB6640F4F80f226bc10bAB', + }, + USDT: { + symbol: 'USDT', + decimals: 6, + name: 'USDT', + address: '0x9702230A8Ea53601f5cD2dc00fDBc13d4dF4A8c7', + }, + USDC: { + symbol: 'USDC', + decimals: 6, + name: 'USDC', + address: '0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E', + }, + 'USDC.e': { + symbol: 'USDC.e', + decimals: 6, + name: 'USDC.e', + address: '0xA7D7079b0FEaD91F3e65f86E8915Cb59c1a4C664', + }, + }, + }, + }; + const { error } = tokensResponseValidator.validate(validData); + expect(error).toBeUndefined(); + }); + + it('should invalidate incorrect tokens response', () => { + const invalidData = { + tokens: { + chain1: { + ETH: { + symbol: '', // invalid value + name: 'Ethereum', + description: 'A popular cryptocurrency', + address: '0x...', + decimals: 18, + type: 'ERC20', + }, + }, + }, + }; + const { error } = tokensResponseValidator.validate(invalidData); + expect(error).toBeDefined(); + }); + }); + + describe('blacklistResponseValidator', () => { + it('should validate correct blacklist response', () => { + const validData = { + blacklist: ['0xAddress1', '0xAddress2'], + }; + const { error } = blacklistResponseValidator.validate(validData); + expect(error).toBeUndefined(); + }); + + it('should invalidate incorrect blacklist response', () => { + const invalidData = { + blacklist: [ + '', // invalid value + ], + }; + const { error } = blacklistResponseValidator.validate(invalidData); + expect(error).toBeDefined(); + }); + }); +}); diff --git a/src/dex/cables/validators.ts b/src/dex/cables/validators.ts new file mode 100644 index 000000000..d95c65341 --- /dev/null +++ b/src/dex/cables/validators.ts @@ -0,0 +1,61 @@ +import joi from 'joi'; + +const pairValidator = joi.object({ + base: joi.string().min(1), + quote: joi.string().min(1), + liquidityUSD: joi.number().min(0), + baseAddress: joi.string().min(1), + quoteAddress: joi.string().min(1), + baseDecimals: joi.number().min(0), + quoteDecimals: joi.number().min(0), +}); + +const pairMap = joi.object().pattern( + joi.string(), // Pair name ETH/USDT + pairValidator, +); + +export const pairsResponseValidator = joi.object({ + pairs: joi.object().pattern( + joi.string(), // chain id + pairMap, + ), +}); + +const orderbookEntry = joi.array().items(joi.string().min(1)).length(2); + +const orderbookValidator = joi.object({ + bids: joi.array().items(orderbookEntry), + asks: joi.array().items(orderbookEntry), +}); + +const chainDataSchema = joi.object().pattern( + joi.string(), // pair name USDC/USDT + orderbookValidator, +); + +export const pricesResponseValidator = joi.object({ + prices: joi.object().pattern( + joi.string(), // chain id + chainDataSchema, + ), +}); + +const tokenValidator = joi.object({ + symbol: joi.string().min(1), + name: joi.string().min(1), + description: joi.string().min(1), + address: joi.string().min(1), + decimals: joi.number().min(0), + type: joi.string().min(1), +}); + +const chainTokens = joi.object().pattern(joi.string(), tokenValidator); + +export const tokensResponseValidator = joi.object({ + tokens: joi.object().pattern(joi.string(), chainTokens), +}); + +export const blacklistResponseValidator = joi.object({ + blacklist: joi.array().items(joi.string().min(1)), +}); diff --git a/src/dex/curve-v1-factory/constants.ts b/src/dex/curve-v1-factory/constants.ts index ccedadfba..bc7db3de4 100644 --- a/src/dex/curve-v1-factory/constants.ts +++ b/src/dex/curve-v1-factory/constants.ts @@ -29,6 +29,7 @@ export const NETWORK_ID_TO_NAME: Record = { [Network.ARBITRUM]: 'arbitrum', [Network.OPTIMISM]: 'optimism', [Network.BASE]: 'base', + [Network.GNOSIS]: 'xdai', }; // They are hardcoded in factory contract. If factory is changing, must be diff --git a/src/dex/curve-v1-factory/curve-v1-factory.ts b/src/dex/curve-v1-factory/curve-v1-factory.ts index 8082c8b46..1e1fad10b 100644 --- a/src/dex/curve-v1-factory/curve-v1-factory.ts +++ b/src/dex/curve-v1-factory/curve-v1-factory.ts @@ -605,7 +605,8 @@ export class CurveV1Factory const allResultsFromFactory = ( await this.dexHelper.multiWrapper.tryAggregate< string[] | number[] | string - >(true, callDataFromFactoryPools) + // decrease batch size to 450 to avoid 'out-of-limit' error on gnosis + >(true, callDataFromFactoryPools, undefined, 450) ).map(r => r.returnData); const resultsFromFactory = allResultsFromFactory.slice( diff --git a/src/dex/curve-v1-stable-ng/config.ts b/src/dex/curve-v1-stable-ng/config.ts index 76334d128..5a517d17e 100644 --- a/src/dex/curve-v1-stable-ng/config.ts +++ b/src/dex/curve-v1-stable-ng/config.ts @@ -192,6 +192,34 @@ const CurveV1StableNgConfig: DexConfigMap = { }, customPools: {}, }, + [Network.GNOSIS]: { + factories: [ + { + address: '0xbc0797015fcfc47d9c1856639cae50d0e69fbee8', + isStableNg: true, + }, + ], + router: '0x0dcded3545d565ba3b19e683431381007245d983', // https://github.com/curvefi/curve-router-ng + stateUpdatePeriodMs: 5 * 1000, + disabledPools: new Set([]), + disabledImplementations: new Set([]), + factoryPoolImplementations: { + // TODO-gnosis: check if there's no missing implementations + '0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a': { + name: ImplementationNames.FACTORY_STABLE_NG, + address: '0x3d6cb2f6dcf47cdd9c13e4e3beae9af041d8796a', + liquidityApiSlug: '/factory-stable-ng', + isStoreRateSupported: true, + }, + '0xc1b393efef38140662b91441c6710aa704973228': { + name: ImplementationNames.FACTORY_STABLE_NG, + address: '0xc1b393efef38140662b91441c6710aa704973228', + liquidityApiSlug: '/factory-stable-ng', + isStoreRateSupported: true, + }, + }, + customPools: {}, + }, }, }; diff --git a/src/dex/curve-v1-stable-ng/curve-v1-stable-ng-e2e.test.ts b/src/dex/curve-v1-stable-ng/curve-v1-stable-ng-e2e.test.ts index 5cc02e4ef..cfea4d743 100644 --- a/src/dex/curve-v1-stable-ng/curve-v1-stable-ng-e2e.test.ts +++ b/src/dex/curve-v1-stable-ng/curve-v1-stable-ng-e2e.test.ts @@ -196,4 +196,24 @@ describe('CurveV1StableNG E2E', () => { sidesToContractMethods, ); }); + + describe('Gnosis', () => { + const network = Network.GNOSIS; + + const tokenASymbol: string = 'sDAI'; + const tokenBSymbol: string = 'crvUSD'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + sidesToContractMethods, + ); + }); }); diff --git a/src/dex/curve-v1/config.ts b/src/dex/curve-v1/config.ts index f87777fc1..f00de2e85 100644 --- a/src/dex/curve-v1/config.ts +++ b/src/dex/curve-v1/config.ts @@ -1115,6 +1115,42 @@ export const CurveV1Config: DexConfigMap = { }, }, }, + [Network.GNOSIS]: { + baseTokens: { + '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d': { + address: '0xe91d153e0b41518a2ce8dd3d7944fa863463a97d', + decimals: 18, + reasonableVolume: 1000000000000000000000n, + }, + '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83': { + address: '0xddafbb505ad214d7b80b1f830fccc89b60fb7a83', + decimals: 6, + reasonableVolume: 1000000000n, + }, + '0x4ecaba5870353805a9f068101a40e0f32ed605c6': { + address: '0x4ecaba5870353805a9f068101a40e0f32ed605c6', + decimals: 6, + reasonableVolume: 1000000000n, + }, + }, + eventSupportedPools: [], + pools: { + '3pool': { + underlying: [], + coins: [ + '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', + '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', + '0x4ECaBa5870353805a9F068101A40E0f32ed605C6', + ], + address: '0x7f90122BF0700F9E7e1F688fe926940E8839F353', + name: '3pool (x3CRV)', + type: 1, + version: 3, + isLending: false, + isMetapool: false, + }, + }, + }, }, }; diff --git a/src/dex/curve-v1/curve-v1-e2e.test.ts b/src/dex/curve-v1/curve-v1-e2e.test.ts index d5a6efe43..b5a0b4f29 100644 --- a/src/dex/curve-v1/curve-v1-e2e.test.ts +++ b/src/dex/curve-v1/curve-v1-e2e.test.ts @@ -338,6 +338,75 @@ describe('CurveV1 E2E', () => { ); }); }); + + describe('GNOSIS', () => { + const network = Network.GNOSIS; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokensToTest = [ + [ + { + symbol: 'WXDAI', + amount: (10 ** 18).toString(), + }, + { + symbol: 'USDC', + amount: (10 ** 6).toString(), + }, + ], + [ + { + symbol: 'WXDAI', + amount: (10 ** 18).toString(), + }, + { + symbol: 'USDT', + amount: (10 ** 6).toString(), + }, + ], + [ + { + symbol: 'USDC', + amount: (10 ** 6).toString(), + }, + { + symbol: 'USDT', + amount: (10 ** 6).toString(), + }, + ], + ]; + + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + tokensToTest.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].symbol} -> ${pair[1].symbol}`, async () => { + await testE2E( + tokens[pair[0].symbol], + tokens[pair[1].symbol], + holders[pair[0].symbol], + side === SwapSide.SELL ? pair[0].amount : pair[1].amount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); }); describe('Acryptos E2E', () => { diff --git a/src/dex/curve-v1/curve-v1.ts b/src/dex/curve-v1/curve-v1.ts index 98d574d54..2af608b36 100644 --- a/src/dex/curve-v1/curve-v1.ts +++ b/src/dex/curve-v1/curve-v1.ts @@ -861,7 +861,7 @@ export class CurveV1 } getAdapters(side: SwapSide): { name: string; index: number }[] | null { - return this.adapters[side]; + return this.adapters?.[side] ?? null; } static getDirectFunctionName(): string[] { diff --git a/src/dex/dexalot/dexalot.ts b/src/dex/dexalot/dexalot.ts index c27f7147a..6fda285df 100644 --- a/src/dex/dexalot/dexalot.ts +++ b/src/dex/dexalot/dexalot.ts @@ -593,6 +593,7 @@ export class Dexalot extends SimpleExchange implements IDex { .multipliedBy(10000) .toFixed(0) : options.slippageFactor.minus(1).multipliedBy(10000).toFixed(0); + const rfqParams = { makerAsset: ethers.utils.getAddress(makerToken.address), takerAsset: ethers.utils.getAddress(takerToken.address), diff --git a/src/dex/fluid-dex/config.ts b/src/dex/fluid-dex/config.ts new file mode 100644 index 000000000..48b14862e --- /dev/null +++ b/src/dex/fluid-dex/config.ts @@ -0,0 +1,20 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network } from '../../constants'; + +export const FluidDexConfig: DexConfigMap = { + FluidDex: { + [Network.MAINNET]: { + commonAddresses: { + liquidityProxy: '0x52aa899454998be5b000ad077a46bbe360f4e497', + resolver: '0x45f4ad57e300da55c33dea579a40fcee000d7b94', + dexFactory: '0x91716C4EDA1Fb55e84Bf8b4c7085f84285c19085', + }, + }, + }, +}; + +// Uniswap takes total gas of 125k = 21k base gas & 104k swap (this is when user has token balance) +// Fluid takes total gas of 175k = 21k base gas & 154k swap (this is when user has token balance), +// with ETH swaps costing less (because no WETH conversion) +export const FLUID_DEX_GAS_COST = 154_000; diff --git a/src/dex/fluid-dex/constants.ts b/src/dex/fluid-dex/constants.ts new file mode 100644 index 000000000..da6d99534 --- /dev/null +++ b/src/dex/fluid-dex/constants.ts @@ -0,0 +1 @@ +export const MIN_SWAP_LIQUIDITY = 10n ** 4n; diff --git a/src/dex/fluid-dex/fluid-dex-e2e.test.ts b/src/dex/fluid-dex/fluid-dex-e2e.test.ts new file mode 100644 index 000000000..333aed44d --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-e2e.test.ts @@ -0,0 +1,321 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { generateConfig } from '../../config'; +import { CollateralReserves, DebtReserves, DexLimits } from './types'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { FluidDex } from './fluid-dex'; + +/* + README + ====== + + This test script should add e2e tests for FluidDex. The tests + should cover as many cases as possible. Most of the DEXes follow + the following test structure: + - DexName + - ForkName + Network + - ContractMethod + - ETH -> Token swap + - Token -> ETH swap + - Token -> Token swap + + The template already enumerates the basic structure which involves + testing simpleSwap, multiSwap, megaSwap contract methods for + ETH <> TOKEN and TOKEN <> TOKEN swaps. You should replace tokenA and + tokenB with any two highly liquid tokens on FluidDex for the tests + to work. If the tokens that you would like to use are not defined in + Tokens or Holders map, you can update the './tests/constants-e2e' + + Other than the standard cases that are already added by the template + it is highly recommended to add test cases which could be specific + to testing FluidDex (Eg. Tests based on poolType, special tokens, + etc). + + You can run this individual test script by running: + `npx jest src/dex//-e2e.test.ts` + + e2e tests use the Tenderly fork api. Please add the following to your + .env file: + TENDERLY_TOKEN=Find this under Account>Settings>Authorization. + TENDERLY_ACCOUNT_ID=Your Tenderly account name. + TENDERLY_PROJECT=Name of a Tenderly project you have created in your + dashboard. + + (This comment should be removed from the final implementation) +*/ + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + [SwapSide.BUY, [ContractMethod.swapExactAmountOut]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: string) => { + describe(`${contractMethod}`, () => { + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + tokenBAmount, + side, + dexKey, + contractMethod as ContractMethod, + network, + provider, + ); + }); + it(`${tokenBSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[tokenBSymbol], + tokens[tokenASymbol], + holders[tokenBSymbol], + tokenBAmount, + side, + dexKey, + contractMethod as ContractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + +describe('FluidDex E2E', () => { + const dexKey = 'FluidDex'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + describe('ETH -> INST', () => { + const tokenASymbol: string = 'ETH'; + const tokenBSymbol: string = 'INST'; + + const tokenAAmount: string = '100000000000000'; + const tokenBAmount: string = '100000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + + describe('ETH -> wstETH', () => { + const tokenASymbol: string = 'wstETH'; + const tokenBSymbol: string = 'ETH'; + + const tokenAAmount: string = '100000000000000'; + const tokenBAmount: string = '100000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + + describe('USDC -> USDT', () => { + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '10000'; + const tokenBAmount: string = '1000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + }); +}); + +function NewColReservesOne(): CollateralReserves { + return { + token0RealReserves: BigInt(20000000006000000), + token1RealReserves: BigInt(20000000000500000), + token0ImaginaryReserves: BigInt(389736659726997981), + token1ImaginaryReserves: BigInt(389736659619871949), + }; +} + +function NewDebtReservesOne(): DebtReserves { + return { + token0Debt: BigInt(1e18), + token1Debt: BigInt(1e18), + token0RealReserves: BigInt(9486832995556050), + token1RealReserves: BigInt(9486832993079885), + token0ImaginaryReserves: BigInt(184868330099560759), + token1ImaginaryReserves: BigInt(184868330048879109), + }; +} + +const limitsTight: DexLimits = { + withdrawableToken0: { + available: 456740438880263n, + expandsTo: 711907234052361388866n, + expandsDuration: 600n, + }, + withdrawableToken1: { + available: 825179383432029n, + expandsTo: 711907234052361388866n, + expandsDuration: 600n, + }, + borrowableToken0: { + available: 941825058374170n, + expandsTo: 711907234052361388866n, + expandsDuration: 600n, + }, + borrowableToken1: { + available: 941825058374170n, + expandsTo: 711907234052361388866n, + expandsDuration: 600n, + }, +}; + +const limitsWide: DexLimits = { + withdrawableToken0: { + available: BigInt(34242332879776515083099999), + expandsTo: BigInt(34242332879776515083099999), + expandsDuration: 0n, + }, + withdrawableToken1: { + available: BigInt(34242332879776515083099999), + expandsTo: BigInt(34242332879776515083099999), + expandsDuration: 22n, + }, + borrowableToken0: { + available: BigInt(34242332879776515083099999), + expandsTo: BigInt(34242332879776515083099999), + expandsDuration: 0n, + }, + borrowableToken1: { + available: BigInt(34242332879776515083099999), + expandsTo: BigInt(34242332879776515083099999), + expandsDuration: 308n, + }, +}; + +const ErrInsufficientBorrowable = new Error('insufficient borrowable'); +const ErrInsufficientMaxPrice = new Error('insufficient max price'); +const ErrInsufficientReserve = new Error('insufficient reserve'); + +describe('TestPoolSimulator_SwapInLimits', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + const dexKey = 'FluidDex'; + const fluidDex = new FluidDex(network, dexKey, dexHelper); + + it('when limits hit', () => { + let outAmt; + try { + outAmt = fluidDex.swapInAdjusted( + true, + BigInt(1e15), + NewColReservesOne(), + NewDebtReservesOne(), + 100n, + 18, + limitsTight, + Math.floor(Date.now() / 1000) - 10, + ); + expect(outAmt).toEqual(0n); + } catch (err: any) { + expect(err.message).toEqual(ErrInsufficientBorrowable.message); + } + }); + + it('when expanded', () => { + const outAmt = fluidDex.swapInAdjusted( + true, + BigInt(1e15), + NewColReservesOne(), + NewDebtReservesOne(), + 100n, + 18, + limitsTight, + Math.floor(Date.now() / 1000) - 6000, + ); + console.log('outAmt: ', outAmt); + expect(outAmt?.toString()).toEqual('998262697204710'); + }); + + it('when price diff hit', () => { + let outAmt; + try { + outAmt = fluidDex.swapInAdjusted( + true, + BigInt(3e16), + NewColReservesOne(), + NewDebtReservesOne(), + 100n, + 18, + limitsWide, + Math.floor(Date.now() / 1000) - 10, + ); + expect(outAmt).toEqual(0n); + } catch (err: any) { + expect(err.message).toEqual(ErrInsufficientMaxPrice.message); + } + }); + + it('when reserves limit is hit', () => { + let outAmt; + try { + outAmt = fluidDex.swapInAdjusted( + true, + BigInt(5e16), + NewColReservesOne(), + NewDebtReservesOne(), + 100n, + 18, + limitsWide, + Math.floor(Date.now() / 1000) - 10, + ); + expect(outAmt).toEqual(0n); + } catch (err: any) { + expect(err.message).toEqual(ErrInsufficientReserve.message); + } + }); +}); diff --git a/src/dex/fluid-dex/fluid-dex-events.test.ts b/src/dex/fluid-dex/fluid-dex-events.test.ts new file mode 100644 index 000000000..f5bad501d --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-events.test.ts @@ -0,0 +1,179 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Network } from '../../constants'; +import { Address } from '../../types'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { FluidDexLiquidityProxyState } from './types'; +import { FluidDexConfig } from './config'; +import { FluidDexLiquidityProxy } from './fluid-dex-liquidity-proxy'; +import { FluidDexFactory } from './fluid-dex-factory'; +import { FluidDexEventPool } from './fluid-dex-pool'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; + +jest.setTimeout(50 * 1000); + +async function fetchState( + statefulEventSubscriber: StatefulEventSubscriber, + blockNumber: number, +): Promise { + return statefulEventSubscriber.generateState(blockNumber); +} + +// eventName -> blockNumbers +type EventMappings = Record; + +describe('FluidDex EventPool Mainnet', function () { + const dexKey = 'FluidDex'; + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + const logger = dexHelper.getLogger(dexKey); + + const commonAddresses = FluidDexConfig.FluidDex[network].commonAddresses; + + describe('LiquidityProxy Events', () => { + let liquidityProxy: FluidDexLiquidityProxy; + + // poolAddress -> EventMappings + const eventsToTest: Record = { + '0x52aa899454998be5b000ad077a46bbe360f4e497': { + LogOperate: [ + 21190399, 21190405, 21190420, 21190452, 21190454, 21190465, 21190506, + ], + }, + }; + + Object.entries(eventsToTest).forEach( + ([poolAddress, events]: [string, EventMappings]) => { + describe(`Events for ${poolAddress}`, () => { + beforeEach(() => { + liquidityProxy = new FluidDexLiquidityProxy( + dexKey, + commonAddresses, + network, + dexHelper, + logger, + ); + }); + Object.entries(events).forEach( + ([eventName, blockNumbers]: [string, number[]]) => { + describe(`${eventName}`, () => { + blockNumbers.forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async function () { + await testEventSubscriber( + liquidityProxy, + liquidityProxy.addressesSubscribed, + (_blockNumber: number) => + fetchState(liquidityProxy, _blockNumber), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }, + ); + }); + }, + ); + }); + + describe('Factory events', () => { + let dexFactory: FluidDexFactory; + + const eventsToTest: Record = { + '0x91716C4EDA1Fb55e84Bf8b4c7085f84285c19085': { + LogDexDeployed: [21199929], + }, + }; + + Object.entries(eventsToTest).forEach( + ([poolAddress, events]: [string, EventMappings]) => { + describe(`Events for ${poolAddress}`, () => { + beforeEach(() => { + dexFactory = new FluidDexFactory( + dexKey, + commonAddresses, + network, + dexHelper, + logger, + pools => { + console.log('POOLS: ', pools); + }, + ); + }); + Object.entries(events).forEach( + ([eventName, blockNumbers]: [string, number[]]) => { + describe(`${eventName}`, () => { + blockNumbers.forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async function () { + await testEventSubscriber( + dexFactory, + dexFactory.addressesSubscribed, + (_blockNumber: number) => + dexFactory.generateState(blockNumber), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }, + ); + }); + }, + ); + }); + + describe('Pool events', () => { + let dexPool: FluidDexEventPool; + + const eventsToTest: Record = { + '0x8710039D5de6840EdE452A85672B32270a709aE2': { + LogPauseSwapAndArbitrage: [21337128], + }, + '0x2886a01a0645390872a9eb99dae1283664b0c524': { + LogPauseSwapAndArbitrage: [21374547], + }, + }; + + Object.entries(eventsToTest).forEach( + ([poolAddress, events]: [string, EventMappings]) => { + describe(`Events for ${poolAddress}`, () => { + beforeEach(() => { + dexPool = new FluidDexEventPool( + dexKey, + poolAddress, + network, + dexHelper, + logger, + ); + }); + Object.entries(events).forEach( + ([eventName, blockNumbers]: [string, number[]]) => { + describe(`${eventName}`, () => { + blockNumbers.forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async function () { + await testEventSubscriber( + dexPool, + dexPool.addressesSubscribed, + (_blockNumber: number) => + fetchState(dexPool, _blockNumber), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }, + ); + }); + }, + ); + }); +}); diff --git a/src/dex/fluid-dex/fluid-dex-factory.ts b/src/dex/fluid-dex/fluid-dex-factory.ts new file mode 100644 index 000000000..df77aba9f --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-factory.ts @@ -0,0 +1,151 @@ +import { Interface } from '@ethersproject/abi'; +import { BytesLike } from 'ethers/lib/utils'; +import { DeepReadonly } from 'ts-essentials'; +import { Log, Logger } from '../../types'; +import { catchParseLogError } from '../../utils'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import ResolverABI from '../../abi/fluid-dex/resolver.abi.json'; +import DexFactoryABI from '../../abi/fluid-dex/dexFactory.abi.json'; +import { CommonAddresses, Pool } from './types'; +import { MultiResult } from '../../lib/multi-wrapper'; +import { Address } from '../../types'; +import { generalDecoder } from '../../lib/decoders'; +import { Contract } from 'ethers'; + +type OnPoolCreatedCallback = (pools: readonly Pool[]) => void; + +export class FluidDexFactory extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => Promise | null>; + } = {}; + + logDecoder: (log: Log) => any; + + addressesSubscribed: Address[]; + protected dexFactoryIface = new Interface(DexFactoryABI); + protected resolverIface = new Interface(ResolverABI); + + constructor( + readonly parentName: string, + readonly commonAddresses: CommonAddresses, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedCallback, + ) { + super(parentName, 'factory', dexHelper, logger); + + this.logDecoder = (log: Log) => this.dexFactoryIface.parseLog(log); + this.addressesSubscribed = [commonAddresses.dexFactory]; + + // Add handlers + this.handlers['LogDexDeployed'] = this.handleDexDeployed.bind(this); + } + + /** + * Handle a trade rate change on the pool. + */ + async handleDexDeployed( + event: any, + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + const blockNumber_ = await this.dexHelper.provider.getBlockNumber(); + + const pools = await this.getStateOrGenerate(blockNumber_, false); + + this.onPoolCreated(pools); + + return pools; + } + + decodePool = (result: MultiResult | BytesLike): Pool => { + return generalDecoder( + result, + ['tuple(address pool, address token0, address token1, uint256 fee)'], + undefined, + decoded => { + return { + address: decoded[0].toLowerCase(), + token0: decoded[1].toLowerCase(), + token1: decoded[2].toLowerCase(), + }; + }, + ); + }; + + /** + * 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 + */ + async processLog( + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + try { + let event; + try { + event = this.logDecoder(log); + } catch (e) { + return null; + } + if (event.name in this.handlers) { + return await this.handlers[event.name](event, state, log); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + async getStateOrGenerate( + blockNumber: number, + readonly: boolean = false, + ): Promise> { + let state = this.getState(blockNumber); + if (!state) { + state = await this.generateState(blockNumber); + if (!readonly) this.setState(state, blockNumber); + } + return state; + } + + /** + * 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 resolverContract = new Contract( + this.commonAddresses.resolver, + ResolverABI, + this.dexHelper.provider, + ); + const rawResult = await resolverContract.callStatic.getAllPools({ + blockTag: blockNumber, + }); + + const pools: Pool[] = rawResult.map((result: any) => ({ + address: result[0], + token0: result[1], + token1: result[2], + })); + + return pools; + } +} diff --git a/src/dex/fluid-dex/fluid-dex-integration.test.ts b/src/dex/fluid-dex/fluid-dex-integration.test.ts new file mode 100644 index 000000000..730b32722 --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-integration.test.ts @@ -0,0 +1,355 @@ +/* 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 { FluidDex } from './fluid-dex'; +import { Tokens } from '../../../tests/constants-e2e'; +import ResolverABI from '../../abi/fluid-dex/resolver.abi.json'; +import { Contract } from 'ethers'; +import { Pool } from './types'; + +/* + README + ====== + + This test script adds tests for FluidDex general integration + with the DEX interface. The test cases below are example tests. + It is recommended to add tests which cover FluidDex specific + logic. + + You can run this individual test script by running: + `npx jest src/dex//-integration.test.ts` + + (This comment should be removed from the final implementation) +*/ + +function getReaderCalldata( + exchangeAddress: string, + readerIface: Interface, + poolAddress: string, + amounts: bigint[], + funcName: string, + pools: { address: string; token0: string; token1: string }[], + srcToken: string, +) { + const pool = pools.find( + item => item.address.toLowerCase() === poolAddress.toLowerCase(), + ); + + return amounts.map(amount => ({ + target: exchangeAddress, + callData: readerIface.encodeFunctionData(funcName, [ + poolAddress, + pool!.token0.toLowerCase() === srcToken.toLowerCase() ? true : false, + amount, + 0, + ]), + })); +} + +function decodeReaderResult( + results: Result, + readerIface: Interface, + funcName: string, +) { + return results.map(result => { + return BigInt(result); + }); +} + +async function checkOnChainPricing( + fluidDex: FluidDex, + funcName: string, + poolAddress: string, + blockNumber: number, + prices: bigint[], + amounts: bigint[], + dexHelper: DummyDexHelper, + srcToken: string, +) { + const resolverAddress = '0x45f4ad57e300da55c33dea579a40fcee000d7b94'; + + const readerIface = new Interface(ResolverABI); + + const resolverContract = new Contract( + resolverAddress, + ResolverABI, + dexHelper.provider, + ); + const rawResult = await resolverContract.callStatic.getAllPools({ + blockTag: blockNumber, + }); + + const pools: Pool[] = rawResult.map((result: any) => ({ + address: result[0], + token0: result[1], + token1: result[2], + })); + + const readerCallData = getReaderCalldata( + resolverAddress, + readerIface, + poolAddress, + amounts.slice(1), + funcName, + pools, + srcToken, + ); + + const readerResult = ( + await fluidDex.dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + + const expectedPrices = [0n].concat( + decodeReaderResult(readerResult, readerIface, funcName), + ); + + expect(prices).toEqual(expectedPrices); +} + +async function testPricingOnNetwork( + fluidDex: FluidDex, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, + dexHelper: DummyDexHelper, +) { + const networkTokens = Tokens[network]; + + const pools = await fluidDex.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await fluidDex.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + console.log( + 'logged params : ', + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + expect(poolPrices).not.toBeNull(); + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricing( + fluidDex, + funcNameToCheck, + poolPrices![0].poolAddresses![0], + blockNumber, + poolPrices![0].prices, + amounts, + dexHelper, + networkTokens[srcTokenSymbol].address, + ); +} + +describe('FluidDex', function () { + const dexKey = 'FluidDex'; + let blockNumber: number; + let fluidDex: FluidDex; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + beforeAll(async () => { + blockNumber = await dexHelper.provider.getBlockNumber(); + fluidDex = new FluidDex(network, dexKey, dexHelper); + if (fluidDex.initializePricing) { + await fluidDex.initializePricing(blockNumber); + } + }); + + describe('wstETH -> ETH', () => { + const tokenASymbol = 'wstETH'; + const tokenBSymbol = 'ETH'; + + const amountsForSwap = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + it('wstETH -> ETH, getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.SELL, + amountsForSwap, + 'estimateSwapIn', + dexHelper, + ); + }); + + it('wstETH -> ETH, getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.BUY, + amountsForSwap, + 'estimateSwapOut', + dexHelper, + ); + }); + + it('ETH -> wstETH, getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.SELL, + amountsForSwap, + 'estimateSwapIn', + dexHelper, + ); + }); + + it('ETH -> wstETH, getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.BUY, + amountsForSwap, + 'estimateSwapOut', + dexHelper, + ); + }); + }); + + describe('USDC -> USDT', () => { + const tokenASymbol = 'USDC'; + const tokenBSymbol = 'USDT'; + + const amountsForSwap = [ + 0n, + 10n * BI_POWS[6], + 20n * BI_POWS[6], + 30n * BI_POWS[6], + 40n * BI_POWS[6], + 50n * BI_POWS[6], + 60n * BI_POWS[6], + 70n * BI_POWS[6], + 80n * BI_POWS[6], + 90n * BI_POWS[6], + 100n * BI_POWS[6], + 1000n * BI_POWS[6], + ]; + + it('USDC -> USDT getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.SELL, + amountsForSwap, + 'estimateSwapIn', + dexHelper, + ); + }); + + it('USDC -> USDT getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenASymbol, + tokenBSymbol, + SwapSide.BUY, + amountsForSwap, + 'estimateSwapOut', + dexHelper, + ); + }); + + it('USDT -> USDC getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.SELL, + amountsForSwap, + 'estimateSwapIn', + dexHelper, + ); + }); + + it('USDT -> USDC getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + fluidDex, + network, + dexKey, + blockNumber, + tokenBSymbol, + tokenASymbol, + SwapSide.BUY, + amountsForSwap, + 'estimateSwapOut', + dexHelper, + ); + }); + }); + }); +}); diff --git a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts new file mode 100644 index 000000000..f6f7b890b --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts @@ -0,0 +1,207 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly } from 'ts-essentials'; +import { Log, Logger } from '../../types'; +import { bigIntify, catchParseLogError } from '../../utils'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import ResolverABI from '../../abi/fluid-dex/resolver.abi.json'; +import LiquidityABI from '../../abi/fluid-dex/liquidityUserModule.abi.json'; +import { + CommonAddresses, + FluidDexLiquidityProxyState, + PoolReserve, + PoolReserveResponse, +} from './types'; +import { Address } from '../../types'; +import { Contract } from 'ethers'; + +export class FluidDexLiquidityProxy extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => Promise | null>; + } = {}; + + logDecoder: (log: Log) => any; + + addressesSubscribed: Address[]; + + readonly liquidityIface = new Interface(LiquidityABI); + + readonly resolverIface = new Interface(ResolverABI); + + resolverContract: Contract; + + constructor( + readonly parentName: string, + readonly commonAddresses: CommonAddresses, + protected network: number, + readonly dexHelper: IDexHelper, + logger: Logger, + ) { + super(parentName, 'liquidity proxy', dexHelper, logger); + + this.logDecoder = (log: Log) => this.liquidityIface.parseLog(log); + + this.resolverContract = new Contract( + this.commonAddresses.resolver, + ResolverABI, + this.dexHelper.provider, + ); + + this.addressesSubscribed = [commonAddresses.liquidityProxy]; + + // Add handlers + this.handlers['LogOperate'] = this.handleOperate.bind(this); + } + + /** + * Handle a trade rate change on the pool. + */ + async handleOperate( + event: any, + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + return this.generateState(log.blockNumber); + } + + /** + * 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 + */ + async processLog( + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + try { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + return await this.handlers[event.name](event, state, log); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + async getStateOrGenerate( + blockNumber: number, + readonly: boolean = false, + ): Promise { + let state = this.getState(blockNumber); + if (!state) { + state = await this.generateState(blockNumber); + if (!readonly) this.setState(state, blockNumber); + } + return state; + } + + /** + * 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 rawResult = + await this.resolverContract.callStatic.getAllPoolsReservesAdjusted({ + blockTag: blockNumber, + }); + + const convertedResult = this.convertToFluidDexPoolState(rawResult); + this.logger.info(`${this.parentName}: ${this.name}: generating state...`); + + return convertedResult; + } + + private convertToFluidDexPoolState( + poolReserves: PoolReserveResponse[], + ): FluidDexLiquidityProxyState { + const result: PoolReserve[] = poolReserves.map(poolReserve => { + const [ + pool, + token0, + token1, + feeHex, + collateralReservesHex, + debtReservesHex, + dexLimitsHex, + ] = poolReserve; + + const fee = Number(feeHex.toString()); + + const collateralReserves = { + token0RealReserves: bigIntify(collateralReservesHex[0]), + token1RealReserves: bigIntify(collateralReservesHex[1]), + token0ImaginaryReserves: bigIntify(collateralReservesHex[2]), + token1ImaginaryReserves: bigIntify(collateralReservesHex[3]), + }; + + const debtReserves = { + token0Debt: bigIntify(debtReservesHex[0]), + token1Debt: bigIntify(debtReservesHex[1]), + token0RealReserves: bigIntify(debtReservesHex[2]), + token1RealReserves: bigIntify(debtReservesHex[3]), + token0ImaginaryReserves: bigIntify(debtReservesHex[4]), + token1ImaginaryReserves: bigIntify(debtReservesHex[5]), + }; + + const withdrawableToken0 = { + available: bigIntify(dexLimitsHex[0][0]), + expandsTo: bigIntify(dexLimitsHex[0][1]), + expandsDuration: bigIntify(dexLimitsHex[0][2]), + }; + + const withdrawableToken1 = { + available: bigIntify(dexLimitsHex[1][0]), + expandsTo: bigIntify(dexLimitsHex[1][1]), + expandsDuration: bigIntify(dexLimitsHex[1][2]), + }; + + const borrowableToken0 = { + available: bigIntify(dexLimitsHex[2][0]), + expandsTo: bigIntify(dexLimitsHex[2][1]), + expandsDuration: bigIntify(dexLimitsHex[2][2]), + }; + + const borrowableToken1 = { + available: bigIntify(dexLimitsHex[3][0]), + expandsTo: bigIntify(dexLimitsHex[3][1]), + expandsDuration: bigIntify(dexLimitsHex[3][2]), + }; + + const dexLimits = { + withdrawableToken0, + withdrawableToken1, + borrowableToken0, + borrowableToken1, + }; + + return { + pool, + token0, + token1, + fee, + collateralReserves, + debtReserves, + dexLimits, + }; + }); + + return { poolsReserves: result }; + } +} diff --git a/src/dex/fluid-dex/fluid-dex-pool.ts b/src/dex/fluid-dex/fluid-dex-pool.ts new file mode 100644 index 000000000..7550933de --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-pool.ts @@ -0,0 +1,138 @@ +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { DeepReadonly } from 'ts-essentials'; +import { Address, Log, Logger } from '../../types'; +import { IDexHelper } from '../../dex-helper'; +import { Interface } from '@ethersproject/abi'; +import FluidDexPoolABI from '../../abi/fluid-dex/fluid-dex.abi.json'; +import { catchParseLogError } from '../../utils'; +import { ethers } from 'ethers'; +import { uint256ToBigInt } from '../../lib/decoders'; +import { DecodedStateMultiCallResultWithRelativeBitmaps } from '../uniswap-v3/types'; + +const { + utils: { hexlify, hexZeroPad }, +} = ethers; + +type PoolState = { + isSwapAndArbitragePaused: boolean; +}; + +export class FluidDexEventPool extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => DeepReadonly | null; + } = {}; + + logDecoder: (log: Log) => any; + addressesSubscribed: Address[]; + protected poolIface = new Interface(FluidDexPoolABI); + + constructor( + readonly parentName: string, + readonly poolAddress: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + ) { + super(parentName, 'pool', dexHelper, logger); + + this.logDecoder = (log: Log) => this.poolIface.parseLog(log); + this.addressesSubscribed = [poolAddress]; + + // Add handlers + this.handlers['LogPauseSwapAndArbitrage'] = + this.handleLogPauseSwapAndArbitrage.bind(this); + this.handlers['LogUnpauseSwapAndArbitrage'] = + this.handleLogUnpauseSwapAndArbitrage.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 + */ + async processLog( + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + try { + let event; + try { + event = this.logDecoder(log); + } catch (e) { + return null; + } + + if (event.name in this.handlers) { + return this.handlers[event.name](event, state, log); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + handleLogPauseSwapAndArbitrage(): PoolState { + return { isSwapAndArbitragePaused: true }; + } + + handleLogUnpauseSwapAndArbitrage(): PoolState { + return { isSwapAndArbitragePaused: false }; + } + + async getStateOrGenerate( + blockNumber: number, + readonly: boolean = false, + ): Promise> { + let state = this.getState(blockNumber); + if (!state) { + state = await this.generateState(blockNumber); + if (!readonly) this.setState(state, blockNumber); + } + return state; + } + + /** + * 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 multicallData = [ + { + target: this.addressesSubscribed[0], + callData: this.poolIface.encodeFunctionData('readFromStorage', [ + hexZeroPad(hexlify(1), 32), + ]), + decodeFunction: uint256ToBigInt, + }, + ]; + + const storageResults = await this.dexHelper.multiWrapper.tryAggregate< + bigint | DecodedStateMultiCallResultWithRelativeBitmaps + >( + false, + multicallData, + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + false, + ); + + const isSwapAndArbitragePaused = + BigInt(storageResults[0].returnData.toString()) >> 255n === 1n; + + return { isSwapAndArbitragePaused }; + } +} diff --git a/src/dex/fluid-dex/fluid-dex.ts b/src/dex/fluid-dex/fluid-dex.ts new file mode 100644 index 000000000..fa3284ea4 --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex.ts @@ -0,0 +1,1156 @@ +import { BytesLike } from 'ethers/lib/utils'; +import { Interface } from '@ethersproject/abi'; +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + PoolLiquidity, + DexExchangeParam, + Logger, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { Context, IDex } from '../../dex/idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { + CollateralReserves, + DebtReserves, + FluidDexData, + FluidDexPool, + Pool, + DexLimits, + TokenLimit, +} from './types'; +import { SimpleExchange } from '../simple-exchange'; +import FluidDexPoolABI from '../../abi/fluid-dex/fluid-dex.abi.json'; +import { FluidDexConfig, FLUID_DEX_GAS_COST } from './config'; +import { FluidDexFactory } from './fluid-dex-factory'; +import { getDexKeysWithNetwork, getBigIntPow } from '../../utils'; +import { extractReturnAmountPosition } from '../../executor/utils'; +import { MultiResult } from '../../lib/multi-wrapper'; +import { generalDecoder } from '../../lib/decoders'; +import { BigNumber } from 'ethers'; +import { sqrt } from './utils'; +import { FluidDexLiquidityProxy } from './fluid-dex-liquidity-proxy'; +import { FluidDexEventPool } from './fluid-dex-pool'; +import { MIN_SWAP_LIQUIDITY } from './constants'; + +export class FluidDex extends SimpleExchange implements IDex { + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(FluidDexConfig); + + logger: Logger; + + pools: FluidDexPool[] = []; + + eventPools: FluidDexEventPool[] = []; + + readonly factory: FluidDexFactory; + + readonly liquidityProxy: FluidDexLiquidityProxy; + + readonly fluidDexPoolIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + this.factory = new FluidDexFactory( + dexKey, + FluidDexConfig[dexKey][network].commonAddresses, + network, + dexHelper, + this.logger, + this.onPoolCreatedUpdatePools.bind(this), + ); + + this.liquidityProxy = new FluidDexLiquidityProxy( + dexKey, + this.factory.commonAddresses, + this.network, + this.dexHelper, + this.logger, + ); + + this.fluidDexPoolIface = new Interface(FluidDexPoolABI); + } + + private async fetchFluidDexPools( + blockNumber: number, + ): Promise { + const poolsFromFactory = await this.factory.getStateOrGenerate( + blockNumber, + false, + ); + return this.generateFluidDexPoolsFromPoolsFactory(poolsFromFactory); + } + + private generateFluidDexPoolsFromPoolsFactory( + pools: readonly Pool[], + ): FluidDexPool[] { + return pools.map(pool => ({ + id: `FluidDex_${pool.address.toLowerCase()}`, + address: pool.address.toLowerCase(), + token0: pool.token0.toLowerCase(), + token1: pool.token1.toLowerCase(), + })); + } + + // Initialize pricing is called once in the start of + // pricing service. It is intended to setup the integration + // for pricing requests. It is optional for a DEX to + // implement this function + async initializePricing(blockNumber: number) { + await this.factory.initialize(blockNumber); + this.pools = await this.fetchFluidDexPools(blockNumber); + + this.eventPools = await Promise.all( + this.pools.map(async pool => { + const eventPool = new FluidDexEventPool( + this.dexKey, + pool.address, + this.network, + this.dexHelper, + this.logger, + ); + await eventPool.initialize(blockNumber); + return eventPool; + }), + ); + + await this.liquidityProxy.initialize(blockNumber); + } + + getAdapters(side: SwapSide) { + return null; + } + + protected onPoolCreatedUpdatePools(poolsFromFactory: readonly Pool[]) { + this.pools = this.generateFluidDexPoolsFromPoolsFactory(poolsFromFactory); + this.logger.info(`${this.dexKey}: pools list was updated ...`); + } + + decodePools = (result: MultiResult | BytesLike): Pool[] => { + return generalDecoder( + result, + ['tuple(address pool, address token0, address token1)[]'], + undefined, + decoded => { + return decoded.map((decodedPool: any) => ({ + address: decodedPool[0][0].toLowerCase(), + token0: decodedPool[0][1].toLowerCase(), + token1: decodedPool[0][2].toLowerCase(), + })); + }, + ); + }; + + // Returns list of pool identifiers that can be used + // for a given swap. poolIdentifiers must be unique + // across DEXes. It is recommended to use + // ${dexKey}_${poolAddress} as a poolIdentifier + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + const pools = this.getPoolsByTokenPair(srcToken.address, destToken.address); + return pools.map(pool => pool.id); + } + + getPoolsByTokenPair(srcToken: Address, destToken: Address): FluidDexPool[] { + const srcAddress = srcToken.toLowerCase(); + const destAddress = destToken.toLowerCase(); + + // A pair must have 2 different tokens. + if (srcAddress === destAddress) return []; + + const pools = this.pools.filter( + pool => + (srcAddress === pool.token0 && destAddress === pool.token1) || + (srcAddress === pool.token1 && destAddress === pool.token0), + ); + + return pools; + } + + // Returns pool prices for amounts. + // If limitPools is defined only pools in limitPools + // should be used. If limitPools is undefined then + // any pools can be used. + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + try { + if (srcToken.address.toLowerCase() === destToken.address.toLowerCase()) + return null; + + // Get the pools to use. + let pools = this.getPoolsByTokenPair(srcToken.address, destToken.address); + + if (limitPools) { + pools = pools.filter(pool => limitPools.includes(pool.id)); + } + + if (!pools.length) return null; + + const liquidityProxyState = await this.liquidityProxy.getStateOrGenerate( + blockNumber, + ); + + const poolsPrices = await Promise.all( + pools.map(async pool => { + const currentPoolReserves = liquidityProxyState.poolsReserves.find( + poolReserve => + poolReserve.pool.toLowerCase() === pool.address.toLowerCase(), + ); + + const eventPool = this.eventPools.find( + eventPool => + eventPool.poolAddress.toLowerCase() === + pool.address.toLowerCase(), + ); + + if (!eventPool) { + this.logger.warn( + `${this.dexKey}-${this.network}: Event pool ${pool.address} was not found...`, + ); + return null; + } + + const state = await eventPool.getStateOrGenerate(blockNumber); + + if (!currentPoolReserves || state.isSwapAndArbitragePaused === true) { + return null; + } + + const prices = amounts.map(amount => { + if (side === SwapSide.SELL) { + return this.swapIn( + srcToken.address.toLowerCase() === pool.token0.toLowerCase(), + amount, + currentPoolReserves.collateralReserves, + currentPoolReserves.debtReserves, + srcToken.decimals, + destToken.decimals, + BigInt(currentPoolReserves.fee), + currentPoolReserves.dexLimits, + Math.floor(Date.now() / 1000), + ); + } else { + return this.swapOut( + srcToken.address.toLowerCase() === pool.token0.toLowerCase(), + amount, + currentPoolReserves.collateralReserves, + currentPoolReserves.debtReserves, + srcToken.decimals, + destToken.decimals, + BigInt(currentPoolReserves.fee), + currentPoolReserves.dexLimits, + Math.floor(Date.now() / 1000), + ); + } + }); + + return { + prices: prices, + unit: getBigIntPow(destToken.decimals), + data: { + poolId: pool.id, + }, + exchange: this.dexKey, + poolIdentifier: pool.id, + gasCost: FLUID_DEX_GAS_COST, + poolAddresses: [pool.address], + }; + }), + ); + + const notNullResults = poolsPrices.filter( + res => res !== null, + ) as ExchangePrices; + + return notNullResults; + } catch (e) { + this.logger.error( + `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${ + destToken.address || destToken.symbol + }, ${side}:`, + e, + ); + return null; + } + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + // Encode params required by the exchange adapter + // V5: Used for multiSwap, buy & megaSwap + // V6: Not used, can be left blank + // Hint: abiCoder.encodeParameter() could be useful + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: FluidDexData, + side: SwapSide, + ): AdapterExchangeParam { + // Encode here the payload for adapter + const payload = ''; + + return { + targetExchange: '0x', + payload, + networkFee: '0', + }; + } + + // Returns list of top pools based on liquidity. Max + // limit number pools should be returned. + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + //@TODO + return []; + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: string, + destAmount: string, + recipient: Address, + data: FluidDexData, + side: SwapSide, + context: Context, + executorAddress: Address, + ): Promise { + let args: any; + let returnAmountPos: number | undefined; + + const method = side === SwapSide.SELL ? 'swapIn' : 'swapOut'; + + returnAmountPos = extractReturnAmountPosition( + this.fluidDexPoolIface, + method, + side === SwapSide.SELL ? 'amountOut_' : 'amountIn_', + ); + + const pool = this.pools.find(pool => pool.id === data.poolId); + if (!pool) + throw new Error( + `${this.dexKey}-${this.network}: Pool with id: ${data.poolId} was not found`, + ); + + if (side === SwapSide.SELL) { + if (pool!.token0.toLowerCase() !== srcToken.toLowerCase()) { + args = [false, BigInt(srcAmount), BigInt(destAmount), recipient]; + } else { + args = [true, BigInt(srcAmount), BigInt(destAmount), recipient]; + } + } else { + if (pool!.token0.toLowerCase() !== srcToken.toLowerCase()) { + args = [false, BigInt(destAmount), BigInt(srcAmount), recipient]; + } else { + args = [true, BigInt(destAmount), BigInt(srcAmount), recipient]; + } + } + const swapData = this.fluidDexPoolIface.encodeFunctionData(method, args); + return { + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: true, + exchangeData: swapData, + targetExchange: pool!.address, + returnAmountPos, + sendEthButSupportsInsertFromAmount: true, + }; + } + + /** + * Calculates the output amount for a given input amount in a swap operation. + * @param swap0To1 - Direction of the swap. True if swapping token0 for token1, false otherwise. + * @param amountIn - The amount of input token to be swapped (as a BigInt). + * @param colReserves - The reserves of the collateral pool. + * @param debtReserves - The reserves of the debt pool. + * @param inDecimals - The number of decimals for the input token. + * @param outDecimals - The number of decimals for the output token. + * @returns The calculated output amount (as a BigInt). + */ + public swapIn( + swap0To1: boolean, + amountIn: bigint, + colReserves: CollateralReserves, + debtReserves: DebtReserves, + inDecimals: number, + outDecimals: number, + fee: bigint, + currentLimits: DexLimits, + syncTime: number, + ): bigint { + if (amountIn === 0n) { + return 0n; // Return 0 if input amount is 0 + } + if ( + colReserves.token0RealReserves + debtReserves.token0RealReserves == 0n && + colReserves.token1RealReserves + debtReserves.token1RealReserves == 0n + ) { + return 0n; + } + + const amountInAdjusted = + (amountIn * BigInt(10 ** 12)) / BigInt(10 ** inDecimals); + + const amountOut = this.swapInAdjusted( + swap0To1, + amountInAdjusted, + colReserves, + debtReserves, + fee, + outDecimals, + currentLimits, + syncTime, + ); + return (amountOut * BigInt(10 ** outDecimals)) / BigInt(10 ** 12); + } + + /** + * Calculates the output amount for a given input amount in a swap operation. + * @param swap0To1 - Direction of the swap. True if swapping token0 for token1, false otherwise. + * @param amountToSwap - The amount of input token to be swapped. + * @param colReserves - The reserves of the collateral pool. + * @param debtReserves - The reserves of the debt pool. + * @returns The calculated output amount. + */ + public swapInAdjusted( + swap0To1: boolean, + amountToSwap: bigint, + colReserves: CollateralReserves, + debtReserves: DebtReserves, + fee: bigint, + outDecimals: number, + currentLimits: DexLimits, + syncTime: number, + ): bigint { + const { + token0RealReserves, + token1RealReserves, + token0ImaginaryReserves, + token1ImaginaryReserves, + } = colReserves; + const { + token0RealReserves: debtToken0RealReserves, + token1RealReserves: debtToken1RealReserves, + token0ImaginaryReserves: debtToken0ImaginaryReserves, + token1ImaginaryReserves: debtToken1ImaginaryReserves, + } = debtReserves; + + // Check if all reserves of collateral pool are greater than 0 + const colPoolEnabled = + token0RealReserves > 0 && + token1RealReserves > 0 && + token0ImaginaryReserves > 0 && + token1ImaginaryReserves > 0; + + // Check if all reserves of debt pool are greater than 0 + const debtPoolEnabled = + debtToken0RealReserves > 0 && + debtToken1RealReserves > 0 && + debtToken0ImaginaryReserves > 0 && + debtToken1ImaginaryReserves > 0; + + let colReserveIn, colReserveOut, debtReserveIn, debtReserveOut; + let colIReserveIn, colIReserveOut, debtIReserveIn, debtIReserveOut; + let borrowable, withdrawable; + + if (swap0To1) { + colReserveIn = token0RealReserves; + colReserveOut = token1RealReserves; + colIReserveIn = token0ImaginaryReserves; + colIReserveOut = token1ImaginaryReserves; + debtReserveIn = debtToken0RealReserves; + debtReserveOut = debtToken1RealReserves; + debtIReserveIn = debtToken0ImaginaryReserves; + debtIReserveOut = debtToken1ImaginaryReserves; + borrowable = this.getExpandedLimit( + syncTime, + currentLimits.borrowableToken1, + ); + withdrawable = this.getExpandedLimit( + syncTime, + currentLimits.withdrawableToken1, + ); + } else { + colReserveIn = token1RealReserves; + colReserveOut = token0RealReserves; + colIReserveIn = token1ImaginaryReserves; + colIReserveOut = token0ImaginaryReserves; + debtReserveIn = debtToken1RealReserves; + debtReserveOut = debtToken0RealReserves; + debtIReserveIn = debtToken1ImaginaryReserves; + debtIReserveOut = debtToken0ImaginaryReserves; + borrowable = this.getExpandedLimit( + syncTime, + currentLimits.borrowableToken0, + ); + withdrawable = this.getExpandedLimit( + syncTime, + currentLimits.withdrawableToken0, + ); + } + + // bring borrowable and withdrawable from token decimals to 1e12 decimals, same as amounts + borrowable = (borrowable * BigInt(10 ** 12)) / BigInt(10 ** outDecimals); + withdrawable = + (withdrawable * BigInt(10 ** 12)) / BigInt(10 ** outDecimals); + + let a: bigint; + if (colPoolEnabled && debtPoolEnabled) { + a = this.swapRoutingIn( + amountToSwap, + colIReserveOut, + colIReserveIn, + debtIReserveOut, + debtIReserveIn, + ); + } else if (debtPoolEnabled) { + a = -1n; // Route from debt pool + } else if (colPoolEnabled) { + a = amountToSwap + 1n; // Route from collateral pool + } else { + throw new Error('No pools are enabled'); + } + + let amountOutCollateral = 0n; + let amountOutDebt = 0n; + let amountInCollateral = 0n; + let amountInDebt = 0n; + + if (a <= 0n) { + // Entire trade routes through debt pool + amountInDebt = amountToSwap; + + amountOutDebt = this.getAmountOut( + this.applyFeeForSell(amountToSwap, fee), + debtIReserveIn, + debtIReserveOut, + ); + } else if (a >= amountToSwap) { + // Entire trade routes through collateral pool + amountInCollateral = amountToSwap; + amountOutCollateral = this.getAmountOut( + this.applyFeeForSell(amountToSwap, fee), + colIReserveIn, + colIReserveOut, + ); + } else { + // Trade routes through both pools + amountInCollateral = a; + amountOutCollateral = this.getAmountOut( + this.applyFeeForSell(a, fee), + colIReserveIn, + colIReserveOut, + ); + amountInDebt = amountToSwap - a; + amountOutDebt = this.getAmountOut( + this.applyFeeForSell(amountInDebt, fee), + debtIReserveIn, + debtIReserveOut, + ); + } + + if (amountOutDebt > debtReserveOut) { + return 0n; + } + if (amountOutDebt > borrowable) { + return 0n; + } + + if (amountOutCollateral > colReserveOut) { + return 0n; + } + + if (amountOutCollateral > withdrawable) { + return 0n; + } + + // For price calculations, we'll use a precision factor for bigint division + const PRECISION = 1000000000000000000000000000n; // 1e27 + + let oldPrice: bigint; + let newPrice: bigint; + + if (amountInCollateral > amountInDebt) { + // new pool price from col pool + oldPrice = swap0To1 + ? (colIReserveOut * PRECISION) / colIReserveIn + : (colIReserveIn * PRECISION) / colIReserveOut; + + newPrice = swap0To1 + ? ((colIReserveOut - amountOutCollateral) * PRECISION) / + (colIReserveIn + amountInCollateral) + : ((colIReserveIn + amountInCollateral) * PRECISION) / + (colIReserveOut - amountOutCollateral); + } else { + // new pool price from debt pool + oldPrice = swap0To1 + ? (debtIReserveOut * PRECISION) / debtIReserveIn + : (debtIReserveIn * PRECISION) / debtIReserveOut; + + newPrice = swap0To1 + ? ((debtIReserveOut - amountOutDebt) * PRECISION) / + (debtIReserveIn + amountInDebt) + : ((debtIReserveIn + amountInDebt) * PRECISION) / + (debtIReserveOut - amountOutDebt); + } + // Calculate price difference using bigint arithmetic + const MAX_PRICE_DIFF = 5n; // 5% + const priceDiff = + oldPrice > newPrice ? oldPrice - newPrice : newPrice - oldPrice; + const maxAllowedDiff = (oldPrice * MAX_PRICE_DIFF) / 100n; + + if (priceDiff > maxAllowedDiff) { + return 0n; + } + + if (amountInCollateral > 0) { + let reservesRatioValid = swap0To1 + ? this.verifyToken1Reserves( + colReserveIn + amountInCollateral, + colReserveOut - amountOutCollateral, + oldPrice, + ) + : this.verifyToken0Reserves( + colReserveOut - amountOutCollateral, + colReserveIn + amountInCollateral, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + + if (amountInDebt > 0) { + let reservesRatioValid = swap0To1 + ? this.verifyToken1Reserves( + debtReserveIn + amountInDebt, + debtReserveOut - amountOutDebt, + oldPrice, + ) + : this.verifyToken0Reserves( + debtReserveOut - amountOutDebt, + debtReserveIn + amountInDebt, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + + const totalAmountOut = amountOutCollateral + amountOutDebt; + + return totalAmountOut; + } + + /** + * Checks if token0 reserves are sufficient compared to token1 reserves. + * This helps prevent edge cases and ensures high precision in calculations. + * @param {number} token0Reserves - The reserves of token0. + * @param {number} token1Reserves - The reserves of token1. + * @param {number} price - The current price used for calculation. + * @returns {boolean} - Returns false if token0 reserves are too low, true otherwise. + */ + protected verifyToken0Reserves( + token0Reserves: bigint, + token1Reserves: bigint, + price: bigint, + ): boolean { + return ( + token0Reserves >= + (token1Reserves * 10n ** 27n) / (price * MIN_SWAP_LIQUIDITY) + ); + } + + /** + * Checks if token1 reserves are sufficient compared to token0 reserves. + * This helps prevent edge cases and ensures high precision in calculations. + * @param {number} token0Reserves - The reserves of token0. + * @param {number} token1Reserves - The reserves of token1. + * @param {number} price - The current price used for calculation. + * @returns {boolean} - Returns false if token1 reserves are too low, true otherwise. + */ + protected verifyToken1Reserves( + token0Reserves: bigint, + token1Reserves: bigint, + price: bigint, + ): boolean { + return ( + token1Reserves >= + (token0Reserves * price) / (10n ** 27n * MIN_SWAP_LIQUIDITY) + ); + } + + /** + * Calculates the currently available swappable amount for a token limit considering expansion since last syncTime. + * @param syncTime - timestamp in seconds when the limits were synced + * @param limit - token limit object containing available amount, expandsTo amount, and expandDuration + * @returns The calculated available swappable amount (borrowable or withdrawable) + */ + public getExpandedLimit(syncTime: number, limit: TokenLimit): bigint { + const currentTime = Math.floor(Date.now() / 1000); // convert milliseconds to seconds + const elapsedTime = currentTime - syncTime; + limit.expandsDuration = limit.expandsDuration || 0n; + if (elapsedTime < 10n) { + // if almost no time has elapsed, return available amount + return limit.available; + } + + if (elapsedTime >= limit.expandsDuration) { + // if duration has passed, return max amount + return limit.expandsTo; + } + + // Calculate expansion ratio using bigint arithmetic + // Multiply by a large factor for precision in integer arithmetic + const PRECISION = 1000000; + const ratio = BigInt(elapsedTime * PRECISION) / limit.expandsDuration; + + // Calculate expanded amount with precision factor + const expansion = + ((limit.expandsTo - limit.available) * ratio) / BigInt(PRECISION); + const expandedAmount = limit.available + expansion; + return expandedAmount; + } + + /** + * Given an input amount of asset and pair reserves, returns the maximum output amount of the other asset. + * @param amountIn - The amount of input asset. + * @param iReserveIn - Imaginary token reserve with input amount. + * @param iReserveOut - Imaginary token reserve of output amount. + * @returns The maximum output amount of the other asset. + */ + public getAmountOut( + amountIn: bigint, + iReserveIn: bigint, + iReserveOut: bigint, + ): bigint { + // Both numerator and denominator are scaled to 1e6 to factor in fee scaling. + const numerator = amountIn * iReserveOut; + const denominator = iReserveIn + amountIn; + + // Using the swap formula: (AmountIn * iReserveY) / (iReserveX + AmountIn) + // We use division with rounding down, which is the default for bigint division + + return numerator / denominator; + } + + /** + * Given an output amount of asset and pair reserves, returns the input amount of the other asset + * @param amountOut - Desired output amount of the asset. + * @param iReserveIn - Imaginary token reserve of input amount. + * @param iReserveOut - Imaginary token reserve of output amount. + * @returns The input amount of the other asset. + */ + public getAmountIn( + amountOut: bigint, + iReserveIn: bigint, + iReserveOut: bigint, + ): bigint { + // Both numerator and denominator are scaled to 1e6 to factor in fee scaling. + const numerator = amountOut * iReserveIn; + const denominator = iReserveOut - amountOut; + + // Using the swap formula: (AmountOut * iReserveX) / (iReserveY - AmountOut) + return numerator / denominator; + } + + /** + * Calculates how much of a swap should go through the collateral pool for output amount. + * @param t - Total amount out. + * @param x - Imaginary reserves of token in of collateral. + * @param y - Imaginary reserves of token out of collateral. + * @param x2 - Imaginary reserves of token in of debt. + * @param y2 - Imaginary reserves of token out of debt. + * @returns How much swap should go through collateral pool. Remaining will go from debt. + * @note If a < 0 then entire trade route through debt pool and debt pool arbitrage with col pool. + * @note If a > t then entire trade route through col pool and col pool arbitrage with debt pool. + * @note If a > 0 & a < t then swap will route through both pools. + */ + public swapRoutingOut( + t: bigint, + x: bigint, + y: bigint, + x2: bigint, + y2: bigint, + ): bigint { + // Adding 1e18 precision + const xyRoot = sqrt(BigNumber.from(x).mul(y).mul(BigInt(1e18))).toBigInt(); + const x2y2Root = sqrt( + BigNumber.from(x2).mul(y2).mul(BigInt(1e18)), + ).toBigInt(); + + // 1e18 precision gets cancelled out in division + const numerator = t * xyRoot + y * x2y2Root - y2 * xyRoot; + const denominator = xyRoot + x2y2Root; + + // Use integer division (rounds down) + const a = numerator / denominator; + + return a; + } + + /** + * Calculates how much of a swap should go through the collateral pool. + * @param t - Total amount in. + * @param x - Imaginary reserves of token out of collateral. + * @param y - Imaginary reserves of token in of collateral. + * @param x2 - Imaginary reserves of token out of debt. + * @param y2 - Imaginary reserves of token in of debt. + * @returns How much swap should go through collateral pool. Remaining will go from debt. + * @note If a < 0 then entire trade route through debt pool and debt pool arbitrage with col pool. + * @note If a > t then entire trade route through col pool and col pool arbitrage with debt pool. + * @note If a > 0 & a < t then swap will route through both pools. + */ + public swapRoutingIn( + t: bigint, + x: bigint, + y: bigint, + x2: bigint, + y2: bigint, + ): bigint { + // Adding 1e18 precision + + const xyRoot = sqrt(BigNumber.from(x).mul(y).mul(BigInt(1e18))).toBigInt(); + const x2y2Root = sqrt( + BigNumber.from(x2).mul(y2).mul(BigInt(1e18)), + ).toBigInt(); + + // Calculating 'a' using the given formula + const a = (y2 * xyRoot + t * xyRoot - y * x2y2Root) / (xyRoot + x2y2Root); + return a; + } + + /** + * Calculates the input amount for a given output amount in a swap operation. + * @param {boolean} swap0to1 - Direction of the swap. True if swapping token0 for token1, false otherwise. + * @param {bigint} amountOut - The amount of output token to be swapped. + * @param {Reserves} colReserves - The reserves of the collateral pool. + * @param {Reserves} debtReserves - The reserves of the debt pool. + * @param {number} inDecimals - The number of decimals for the input token. + * @param {number} outDecimals - The number of decimals for the output token. + * @param {number} fee - The fee for the swap. 1e4 = 1% + * @returns {bigint} amountIn - The calculated input amount required for the swap. + */ + public swapOut( + swap0to1: boolean, + amountOut: bigint, + colReserves: CollateralReserves, + debtReserves: DebtReserves, + inDecimals: number, + outDecimals: number, + fee: bigint, + currentLimits: DexLimits, + syncTime: number, + ): bigint { + const amountOutAdjusted = + (amountOut * BigInt(10 ** 12)) / BigInt(10 ** outDecimals); + + const amountIn = this.swapOutAdjusted( + swap0to1, + amountOutAdjusted, + colReserves, + debtReserves, + fee, + outDecimals, + currentLimits, + syncTime, + ); + + if (amountIn === 2n ** 256n - 1n) { + return amountIn; + } + const ans = (amountIn * BigInt(10 ** inDecimals)) / BigInt(10 ** 12); + + return ans; + } + + /** + * Calculates the input amount for a given output amount in a swap operation. + * @param {boolean} swap0to1 - Direction of the swap. True if swapping token0 for token1, false otherwise. + * @param {bigint} amountOut - The amount of output token to be swapped. + * @param {CollateralReserves} colReserves - The reserves of the collateral pool. + * @param {DebtReserves} debtReserves - The reserves of the debt pool. + * @returns {bigint} The calculated input amount required for the swap. + */ + public swapOutAdjusted( + swap0to1: boolean, + amountOut: bigint, + colReserves: CollateralReserves, + debtReserves: DebtReserves, + fee: bigint, + outDecimals: number, + currentLimits: DexLimits, + syncTime: number, + ): bigint { + if (amountOut === 0n) { + return 0n; // Return 0 if output amount is 0 + } + const { + token0RealReserves, + token1RealReserves, + token0ImaginaryReserves, + token1ImaginaryReserves, + } = colReserves; + + const { + token0RealReserves: debtToken0RealReserves, + token1RealReserves: debtToken1RealReserves, + token0ImaginaryReserves: debtToken0ImaginaryReserves, + token1ImaginaryReserves: debtToken1ImaginaryReserves, + } = debtReserves; + + // Check if all reserves of collateral pool are greater than 0 + const colPoolEnabled = + token0RealReserves > 0n && + token1RealReserves > 0n && + token0ImaginaryReserves > 0n && + token1ImaginaryReserves > 0n; + + // Check if all reserves of debt pool are greater than 0 + const debtPoolEnabled = + debtToken0RealReserves > 0n && + debtToken1RealReserves > 0n && + debtToken0ImaginaryReserves > 0n && + debtToken1ImaginaryReserves > 0n; + + let colReserveIn: bigint, + colReserveOut: bigint, + debtReserveIn: bigint, + debtReserveOut: bigint; + let colIReserveIn: bigint, + colIReserveOut: bigint, + debtIReserveIn: bigint, + debtIReserveOut: bigint; + let borrowable: bigint, withdrawable: bigint; + + if (swap0to1) { + colReserveIn = token0RealReserves; + colReserveOut = token1RealReserves; + colIReserveIn = token0ImaginaryReserves; + colIReserveOut = token1ImaginaryReserves; + debtReserveIn = debtToken0RealReserves; + debtReserveOut = debtToken1RealReserves; + debtIReserveIn = debtToken0ImaginaryReserves; + debtIReserveOut = debtToken1ImaginaryReserves; + borrowable = this.getExpandedLimit( + syncTime, + currentLimits.borrowableToken1, + ); + withdrawable = this.getExpandedLimit( + syncTime, + currentLimits.withdrawableToken1, + ); + } else { + colReserveIn = token1RealReserves; + colReserveOut = token0RealReserves; + colIReserveIn = token1ImaginaryReserves; + colIReserveOut = token0ImaginaryReserves; + debtReserveIn = debtToken1RealReserves; + debtReserveOut = debtToken0RealReserves; + debtIReserveIn = debtToken1ImaginaryReserves; + debtIReserveOut = debtToken0ImaginaryReserves; + borrowable = this.getExpandedLimit( + syncTime, + currentLimits.borrowableToken0, + ); + withdrawable = this.getExpandedLimit( + syncTime, + currentLimits.withdrawableToken0, + ); + } + + // bring borrowable and withdrawable from token decimals to 1e12 decimals, same as amounts + borrowable = (borrowable * BigInt(10 ** 12)) / BigInt(10 ** outDecimals); + withdrawable = + (withdrawable * BigInt(10 ** 12)) / BigInt(10 ** outDecimals); + + let a: bigint; + if (colPoolEnabled && debtPoolEnabled) { + a = this.swapRoutingOut( + amountOut, + colIReserveIn, + colIReserveOut, + debtIReserveIn, + debtIReserveOut, + ); + } else if (debtPoolEnabled) { + a = -1n; // Route from debt pool + } else if (colPoolEnabled) { + a = amountOut + 1n; // Route from collateral pool + } else { + throw new Error('No pools are enabled'); + } + + let amountInCollateral: bigint = 0n; + let amountInDebt: bigint = 0n; + let amountOutCollateral: bigint = 0n; + let amountOutDebt: bigint = 0n; + + if (a <= 0n) { + // Entire trade routes through debt pool + + amountOutDebt = amountOut; + amountInDebt = this.getAmountIn( + amountOut, + debtIReserveIn, + debtIReserveOut, + ); + amountInDebt = this.applyFeeForBuy(amountInDebt, fee); + if (amountOut > debtReserveOut) { + return 2n ** 256n - 1n; + } + if (amountOut > borrowable) { + return 2n ** 256n - 1n; + } + } else if (a >= amountOut) { + // Entire trade routes through collateral pool + amountOutCollateral = amountOut; + amountInCollateral = this.getAmountIn( + amountOut, + colIReserveIn, + colIReserveOut, + ); + amountInCollateral = this.applyFeeForBuy(amountInCollateral, fee); + if (amountOut > colReserveOut) { + return 2n ** 256n - 1n; + } + if (amountOut > withdrawable) { + return 2n ** 256n - 1n; + } + } else { + // Trade routes through both pools + amountOutCollateral = a; + amountInCollateral = this.getAmountIn(a, colIReserveIn, colIReserveOut); + const amountOutDebtAdjusted = amountOut - a; + + amountInCollateral = this.applyFeeForBuy(amountInCollateral, fee); + + amountInDebt = this.getAmountIn( + amountOutDebtAdjusted, + debtIReserveIn, + debtIReserveOut, + ); + + amountInDebt = this.applyFeeForBuy(amountInDebt, fee); + if (amountOutDebt > debtReserveOut || a > colReserveOut) { + return 2n ** 256n - 1n; + } + if (amountOutDebt > borrowable || a > withdrawable) { + return 2n ** 256n - 1n; + } + } + + let oldPrice: bigint; + let newPrice: bigint; + const SCALE = BigInt(1e27); + + // from whatever pool higher amount of swap is routing we are taking that as final price, does not matter much because both pools final price should be same + if (amountOutCollateral > amountOutDebt) { + // new pool price from col pool + oldPrice = swap0to1 + ? (colIReserveOut * SCALE) / colIReserveIn + : (colIReserveIn * SCALE) / colIReserveOut; + newPrice = swap0to1 + ? ((colIReserveOut - amountOutCollateral) * SCALE) / + (colIReserveIn + amountInCollateral) + : ((colIReserveIn + amountInCollateral) * SCALE) / + (colIReserveOut - amountOutCollateral); + } else { + // new pool price from debt pool + oldPrice = swap0to1 + ? (debtIReserveOut * SCALE) / debtIReserveIn + : (debtIReserveIn * SCALE) / debtIReserveOut; + newPrice = swap0to1 + ? ((debtIReserveOut - amountOutDebt) * SCALE) / + (debtIReserveIn + amountInDebt) + : ((debtIReserveIn + amountInDebt) * SCALE) / + (debtIReserveOut - amountOutDebt); + } + + const MAX_PRICE_DIFF = BigInt(5); // 5% + if ( + this.abs(oldPrice - newPrice) > + (oldPrice / BigInt(100)) * MAX_PRICE_DIFF + ) { + // if price diff is > 5% then swap would revert. + return 2n ** 256n - 1n; + } + + if (amountInCollateral > 0) { + let reservesRatioValid = swap0to1 + ? this.verifyToken1Reserves( + colReserveIn + amountInCollateral, + colReserveOut - amountOutCollateral, + oldPrice, + ) + : this.verifyToken0Reserves( + colReserveOut - amountOutCollateral, + colReserveIn + amountInCollateral, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + if (amountInDebt > 0) { + let reservesRatioValid = swap0to1 + ? this.verifyToken1Reserves( + debtReserveIn + amountInDebt, + debtReserveOut - amountOutDebt, + oldPrice, + ) + : this.verifyToken0Reserves( + debtReserveOut - amountOutDebt, + debtReserveIn + amountInDebt, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + + const totalAmountIn = amountInCollateral + amountInDebt; + + return totalAmountIn; + } + + private applyFeeForBuy(amount: bigint, fee: bigint): bigint { + return (amount * 10n ** 6n) / (10n ** 6n - fee); + } + + private applyFeeForSell(amount: bigint, fee: bigint): bigint { + return (amount * (10n ** 6n - fee)) / 10n ** 6n; + } + + private abs(value: bigint): bigint { + return value < 0 ? -value : value; + } +} diff --git a/src/dex/fluid-dex/types.ts b/src/dex/fluid-dex/types.ts new file mode 100644 index 000000000..0fb982e07 --- /dev/null +++ b/src/dex/fluid-dex/types.ts @@ -0,0 +1,101 @@ +import { Address } from '../../types'; +import { BigNumber } from 'ethers'; + +export type PoolReserve = { + pool: string; + token0: string; + token1: string; + fee: number; + collateralReserves: CollateralReserves; + debtReserves: DebtReserves; + dexLimits: DexLimits; +}; + +export type PoolReserveResponse = [ + string, + string, + string, + BigNumber, + BigNumber[], + BigNumber[], + DexLimitResponse, +]; + +export type DexLimits = { + withdrawableToken0: TokenLimit; + withdrawableToken1: TokenLimit; + borrowableToken0: TokenLimit; + borrowableToken1: TokenLimit; +}; + +export type DexLimitResponse = [ + TokenLimitResponse, + TokenLimitResponse, + TokenLimitResponse, + TokenLimitResponse, +]; + +export type TokenLimit = { + available: bigint; + expandsTo: bigint; + expandsDuration: bigint; +}; + +export type TokenLimitResponse = [bigint, bigint, bigint]; + +export type FluidDexLiquidityProxyState = { + poolsReserves: readonly PoolReserve[]; +}; + +export type CollateralReserves = { + token0RealReserves: bigint; + token1RealReserves: bigint; + token0ImaginaryReserves: bigint; + token1ImaginaryReserves: bigint; +}; + +export type DebtReserves = { + token0Debt: bigint; + token1Debt: bigint; + token0RealReserves: bigint; + token1RealReserves: bigint; + token0ImaginaryReserves: bigint; + token1ImaginaryReserves: bigint; +}; + +export interface PoolWithReserves { + pool: string; + token0: string; + token1: string; + fee: number; + collateralReserves: CollateralReserves; + debtReserves: DebtReserves; +} + +export type FluidDexData = { + poolId: string; +}; + +// Each pool has a contract address and token pairs. +export type FluidDexPool = { + id: string; + address: Address; + token0: Address; + token1: Address; +}; + +export type DexParams = { + commonAddresses: CommonAddresses; +}; + +export type CommonAddresses = { + liquidityProxy: Address; + resolver: Address; + dexFactory: Address; +}; + +export type Pool = { + address: Address; + token0: Address; + token1: Address; +}; diff --git a/src/dex/fluid-dex/utils.ts b/src/dex/fluid-dex/utils.ts new file mode 100644 index 000000000..ff9dd3620 --- /dev/null +++ b/src/dex/fluid-dex/utils.ts @@ -0,0 +1,15 @@ +import { BigNumber } from 'ethers'; + +const ONE = BigNumber.from(1); +const TWO = BigNumber.from(2); + +export function sqrt(value: BigNumber) { + let x = value; + let z = x.add(ONE).div(TWO); + let y = x; + while (z.sub(y).isNegative()) { + y = z; + z = x.div(z).add(z).div(TWO); + } + return y; +} diff --git a/src/dex/generic-rfq/e2e-test-config.ts b/src/dex/generic-rfq/e2e-test-config.ts index e52c8c083..42b289646 100644 --- a/src/dex/generic-rfq/e2e-test-config.ts +++ b/src/dex/generic-rfq/e2e-test-config.ts @@ -8,74 +8,18 @@ export const testConfig: { amount: string; }>; } = { - [Network.MAINNET]: [ - // { - // srcToken: 'USDT', - // destToken: 'WETH', - // swapSide: SwapSide.BUY, - // amount: '10000000000000000', - // }, - // { - // srcToken: 'WETH', - // destToken: 'USDT', - // swapSide: SwapSide.BUY, - // amount: '1000000', - // }, - // { - // srcToken: 'USDT', - // destToken: 'WETH', - // swapSide: SwapSide.SELL, - // amount: '1000000', - // }, - { - // srcToken: 'WETH', - // destToken: 'DAI', - srcToken: 'DAI', - destToken: 'WETH', - swapSide: SwapSide.BUY, - amount: '10000000000000000', - }, - { - srcToken: 'DAI', - destToken: 'ETH', - swapSide: SwapSide.BUY, - amount: '10000000000000000', - }, - { - srcToken: 'ETH', - destToken: 'DAI', - swapSide: SwapSide.BUY, - amount: '10000000000000000', - }, + [Network.ARBITRUM]: [ { srcToken: 'WETH', - destToken: 'DAI', + destToken: 'USDC', + amount: '10000', swapSide: SwapSide.BUY, - amount: '10000000000000000', }, { - srcToken: 'DAI', + srcToken: 'USDC', destToken: 'WETH', + amount: '10000', swapSide: SwapSide.SELL, - amount: '10000000000000000', - }, - { - srcToken: 'DAI', - destToken: 'ETH', - swapSide: SwapSide.SELL, - amount: '10000000000000000', - }, - { - srcToken: 'ETH', - destToken: 'DAI', - swapSide: SwapSide.SELL, - amount: '10000000000000000', - }, - { - srcToken: 'WETH', - destToken: 'DAI', - swapSide: SwapSide.SELL, - amount: '10000000000000000', }, ], }; diff --git a/src/dex/generic-rfq/generic-rfq-e2e.test.ts b/src/dex/generic-rfq/generic-rfq-e2e.test.ts index 1e58919d5..54de508eb 100644 --- a/src/dex/generic-rfq/generic-rfq-e2e.test.ts +++ b/src/dex/generic-rfq/generic-rfq-e2e.test.ts @@ -85,7 +85,8 @@ const buildConfigForGenericRFQ = (): RFQConfig => { }; }; -const SKIP_TENDERLY = !!getEnv('GENERIC_RFQ_SKIP_TENDERLY', true); +const SKIP_TENDERLY = + (process.env.GENERIC_RFQ_SKIP_TENDERLY ?? 'true') === 'true'; const dexKey = 'YOUR_NAME'; describe(`GenericRFQ ${dexKey} E2E`, () => { @@ -133,8 +134,8 @@ describe(`GenericRFQ ${dexKey} E2E`, () => { } const contractMethod = testCase.swapSide === SwapSide.BUY - ? ContractMethod.swapExactAmountInOutOnAugustusRFQTryBatchFill - : ContractMethod.swapExactAmountInOutOnAugustusRFQTryBatchFill; + ? ContractMethod.swapOnAugustusRFQTryBatchFill + : ContractMethod.swapOnAugustusRFQTryBatchFill; describe(`${contractMethod}`, () => { it(`${testCase.swapSide} ${testCase.srcToken} -> ${testCase.destToken}`, async () => { await newTestE2E({ diff --git a/src/dex/index.ts b/src/dex/index.ts index caca39828..89e9008f8 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -86,13 +86,20 @@ import { Spark } from './spark/spark'; import { VelodromeSlipstream } from './uniswap-v3/forks/velodrome-slipstream/velodrome-slipstream'; import { AaveV3Stata } from './aave-v3-stata/aave-v3-stata'; import { OSwap } from './oswap/oswap'; +import { FluidDex } from './fluid-dex/fluid-dex'; import { ConcentratorArusd } from './concentrator-arusd/concentrator-arusd'; import { FxProtocolRusd } from './fx-protocol-rusd/fx-protocol-rusd'; import { AaveGsm } from './aave-gsm/aave-gsm'; import { LitePsm } from './lite-psm/lite-psm'; -import { UsualBond } from './usual-bond/usual-bond'; import { StkGHO } from './stkgho/stkgho'; +import { BalancerV3 } from './balancer-v3/balancer-v3'; +import { balancerV3Merge } from './balancer-v3/optimizer'; import { SkyConverter } from './sky-converter/sky-converter'; +import { Cables } from './cables/cables'; +import { Stader } from './stader/stader'; +import { UsualBond } from './usual/usual-bond'; +import { UsualMWrappedM } from './usual/usual-m-wrapped-m'; +import { UsualMUsd0 } from './usual/usual-m-usd0'; const LegacyDexes = [ CurveV2, @@ -112,6 +119,7 @@ const LegacyDexes = [ ]; const Dexes = [ + Stader, Bebop, Dexalot, CurveV1, @@ -119,6 +127,7 @@ const Dexes = [ Swerve, BalancerV1, BalancerV2, + BalancerV3, UniswapV2, UniswapV3, Algebra, @@ -181,6 +190,10 @@ const Dexes = [ UsualBond, StkGHO, SkyConverter, + Cables, + FluidDex, + UsualMWrappedM, + UsualMUsd0, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< @@ -211,6 +224,7 @@ export class DexAdapterService { public routeOptimizers: IRouteOptimizer[] = [ balancerV1Merge, balancerV2Merge, + balancerV3Merge, uniswapMerge, curveV1Merge, ]; diff --git a/src/dex/infusion/config.ts b/src/dex/infusion/config.ts index 8c9621178..8dc857616 100644 --- a/src/dex/infusion/config.ts +++ b/src/dex/infusion/config.ts @@ -10,12 +10,12 @@ export const InfusionConfig: DexConfigMap = { factoryAddress: '0x2D9A3a2bd6400eE28d770c7254cA840c82faf23f', router: '0x1E891C9F96DcA29Da8B97bE3403D16135EBe8028', initCode: - '0xc1ac28b1c4ebe53c0cff67bab5878c4eb68759bb1e9f73977cd266b247d149f0', + '0x219feae2806b91b43e3369a02074b26dde4d33b44972f02d717f37ac78ab0245', // updatable fees on the factory without event - stableFee: 2, - volatileFee: 2, + stableFee: 5, + volatileFee: 5, poolGasCost: 180 * 1000, - feeCode: 2, + feeCode: 5, }, }, }; diff --git a/src/dex/maverick-v1/maverick-v1-pool.ts b/src/dex/maverick-v1/maverick-v1-pool.ts index 21537b441..61f66b903 100644 --- a/src/dex/maverick-v1/maverick-v1-pool.ts +++ b/src/dex/maverick-v1/maverick-v1-pool.ts @@ -80,7 +80,7 @@ export class MaverickV1EventPool extends StatefulEventSubscriber { this.poolMath = new MaverickPoolMath( parentName, - BigInt(fee * 1e18), + BigInt((fee * 1e18).toFixed(0)), BigInt(tickSpacing), BigInt(protocolFeeRatio), ); diff --git a/src/dex/pancakeswap-v3/pancakeswap-v3-pool.ts b/src/dex/pancakeswap-v3/pancakeswap-v3-pool.ts index fc2fdb9f1..a97d99297 100644 --- a/src/dex/pancakeswap-v3/pancakeswap-v3-pool.ts +++ b/src/dex/pancakeswap-v3/pancakeswap-v3-pool.ts @@ -250,6 +250,33 @@ export class PancakeSwapV3EventPool extends StatefulEventSubscriber { return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER; } + async checkState( + blockNumber: number, + ): Promise | null> { + const state = this.getState(blockNumber); + if (state) { + return state; + } + + this.logger.error( + `PancakeV3: No state found for ${this.name} ${this.addressesSubscribed[0]} for bn: ${blockNumber}`, + ); + return null; + } + + _setState(state: any, blockNumber: number, reason?: string): void { + if (this.parentName === 'PancakeswapV3') { + this.logger.info( + `PancakeV3: Setting state: ${!!state ? 'non-empty' : 'empty'} for '${ + this.name + }' for bn: '${blockNumber}' due to reason: '${ + reason ?? 'outside_of_event_subscriber' + }'`, + ); + } + super._setState(state, blockNumber); + } + async generateState(blockNumber: number): Promise> { const callData = this._getStateRequestCallData(); diff --git a/src/dex/pancakeswap-v3/pancakeswap-v3.ts b/src/dex/pancakeswap-v3/pancakeswap-v3.ts index fc267ad0f..ccc3ddc26 100644 --- a/src/dex/pancakeswap-v3/pancakeswap-v3.ts +++ b/src/dex/pancakeswap-v3/pancakeswap-v3.ts @@ -613,6 +613,10 @@ export class PancakeswapV3 if (selectedPools.length === 0) return null; + await Promise.all( + selectedPools.map(pool => pool.checkState(blockNumber)), + ); + const poolsToUse = selectedPools.reduce( (acc, pool) => { let state = pool.getState(blockNumber); @@ -632,6 +636,12 @@ export class PancakeswapV3 }, ); + poolsToUse.poolWithoutState.forEach(pool => { + this.logger.warn( + `PancakeV3: Pool ${pool.name} on ${this.dexKey} has no state. Fallback to rpc`, + ); + }); + const rpcResultsPromise = this.getPricingFromRpc( _srcToken, _destToken, diff --git a/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts b/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts index 631eb196f..1d06f0ee8 100644 --- a/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts +++ b/src/dex/paraswap-limit-orders/paraswap-limit-orders.ts @@ -67,7 +67,7 @@ export class ParaSwapLimitOrders super(dexHelper, dexKey); this.augustusRFQAddress = dexHelper.config.data.augustusRFQAddress.toLowerCase(); - this.logger = dexHelper.getLogger(dexKey); + this.logger = dexHelper.getLogger(`${dexKey}-${network}`); } get limitOrderProviderName() { diff --git a/src/dex/solidly/config.ts b/src/dex/solidly/config.ts index d606a52d9..574499f00 100644 --- a/src/dex/solidly/config.ts +++ b/src/dex/solidly/config.ts @@ -62,10 +62,10 @@ export const SolidlyConfig: DexConfigMap = { initCode: '0xc1ac28b1c4ebe53c0cff67bab5878c4eb68759bb1e9f73977cd266b247d149f0', // updatable fees on the factory without event - stableFee: 2, - volatileFee: 2, + stableFee: 5, + volatileFee: 5, poolGasCost: 180 * 1000, - feeCode: 2, + feeCode: 5, }, }, VelodromeV2: { diff --git a/src/dex/stader/config.ts b/src/dex/stader/config.ts new file mode 100644 index 000000000..265c88cf6 --- /dev/null +++ b/src/dex/stader/config.ts @@ -0,0 +1,13 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network } from '../../constants'; + +export const StaderConfig: DexConfigMap = { + Stader: { + [Network.MAINNET]: { + ETHx: '0xA35b1B31Ce002FBF2058D22F30f95D405200A15b', + SSPM: '0xcf5EA1b38380f6aF39068375516Daf40Ed70D299', + StaderOracle: '0xF64bAe65f6f2a5277571143A24FaaFDFC0C2a737', + }, + }, +}; diff --git a/src/dex/stader/stader-e2e.test.ts b/src/dex/stader/stader-e2e.test.ts new file mode 100644 index 000000000..37473da15 --- /dev/null +++ b/src/dex/stader/stader-e2e.test.ts @@ -0,0 +1,74 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { ContractMethod, Network, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +describe('ETHx', () => { + const network = Network.MAINNET; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const dexKey = 'Stader'; + + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + ]); + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'ETH', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + { + name: 'ETHx', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + ], + [ + { + name: 'WETH', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + { + name: 'ETHx', + sellAmount: '1000000000000000000', + buyAmount: '1000000000000000000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL ? pair[0].sellAmount : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }); + }), + ); +}); diff --git a/src/dex/stader/stader-events.test.ts b/src/dex/stader/stader-events.test.ts new file mode 100644 index 000000000..0c8325a16 --- /dev/null +++ b/src/dex/stader/stader-events.test.ts @@ -0,0 +1,64 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { ETHxEventPool } from './stader-pool'; +import { Network } from '../../constants'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { ETHxPoolState } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { StaderConfig } from './config'; +import StadeOracleAbi from '../../abi/StaderOracle.json'; + +jest.setTimeout(50 * 1000); + +async function fetchETHxPoolState( + staderPools: ETHxEventPool, + blockNumber: number, +): Promise { + return staderPools.generateState(blockNumber); +} + +describe('Stader EventPool Mainnet', function () { + const dexKey = 'Stader'; + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + const logger = dexHelper.getLogger(dexKey); + let staderPool: ETHxEventPool; + + const blockNumbers: { [eventName: string]: number[] } = { + ExchangeRateUpdated: [ + 21341230, 21334070, 21326910, 21319802, 21312600, 21305433, 21291465, + 21276861, + ], + }; + + beforeEach(async () => { + staderPool = new ETHxEventPool( + dexKey, + dexHelper, + StaderConfig[dexKey][network].StaderOracle.toLowerCase(), + new Interface(StadeOracleAbi as JsonFragment[]), + logger, + ); + }); + + Object.keys(blockNumbers).forEach((event: string) => { + blockNumbers[event].forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async function () { + await testEventSubscriber( + staderPool, + staderPool.addressesSubscribed, + (_blockNumber: number) => + fetchETHxPoolState(staderPool, _blockNumber), + blockNumber, + `${dexKey}_${StaderConfig[dexKey][ + network + ].StaderOracle.toLowerCase()}`, + dexHelper.provider, + ); + }); + }); + }); +}); diff --git a/src/dex/stader/stader-integration.test.ts b/src/dex/stader/stader-integration.test.ts new file mode 100644 index 000000000..3c27bc505 --- /dev/null +++ b/src/dex/stader/stader-integration.test.ts @@ -0,0 +1,240 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { Stader } from './stader'; +import { checkPoolPrices, checkPoolsLiquidity } from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { Interface, Result } from '@ethersproject/abi'; +import SSPMABI from '../../abi/SSPM.json'; + +function getReaderCalldata( + exchangeAddress: string, + readerIface: Interface, + poolAddress: string, + 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 => { + return BigInt(result); + }); +} + +async function checkOnChainPricing( + stader: Stader, + funcName: string, + poolAddress: string, + blockNumber: number, + prices: bigint[], + amounts: bigint[], + dexHelper: DummyDexHelper, + srcToken: string, +) { + const readerIface = new Interface(SSPMABI); + const SSPMAddress = stader.SSPM_Address; + + const readerCallData = getReaderCalldata( + SSPMAddress, + readerIface, + poolAddress, + amounts.slice(1), + funcName, + ); + + const readerResult = ( + await dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + + const expectedPrices = [0n].concat( + decodeReaderResult(readerResult, readerIface, funcName), + ); + + expect(prices).toEqual(expectedPrices); +} + +describe('Stader', function () { + const dexKey = 'Stader'; + let blockNumber: number; + let stader: Stader; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + stader = new Stader(network, dexKey, dexHelper); + if (stader.initializePricing) { + await stader.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume ETH -> ETHx SELL', async () => { + const srcTokenSymbol = 'ETH'; + const destTokenSymbol = 'ETHx'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const pools = await stader.getPoolIdentifiers( + tokens[srcTokenSymbol], + tokens[destTokenSymbol], + SwapSide.SELL, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await stader.getPricesVolume( + tokens[srcTokenSymbol], + tokens[destTokenSymbol], + amountsForSell, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amountsForSell, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricing( + stader, + 'previewDeposit', + poolPrices![0].poolAddresses![0], + blockNumber, + poolPrices![0].prices, + amountsForSell, + dexHelper, + tokens[srcTokenSymbol].address, + ); + }); + + it('getPoolIdentifiers and getPricesVolume WETH -> ETHx SELL', async () => { + const srcTokenSymbol = 'WETH'; + const destTokenSymbol = 'ETHx'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const pools = await stader.getPoolIdentifiers( + tokens[srcTokenSymbol], + tokens[destTokenSymbol], + SwapSide.SELL, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await stader.getPricesVolume( + tokens[srcTokenSymbol], + tokens[destTokenSymbol], + amountsForSell, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amountsForSell, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricing( + stader, + 'previewDeposit', + poolPrices![0].poolAddresses![0], + blockNumber, + poolPrices![0].prices, + amountsForSell, + dexHelper, + tokens[srcTokenSymbol].address, + ); + }); + + it('ETH getTopPoolsForToken', async function () { + const poolLiquidity = await stader.getTopPoolsForToken( + tokens['ETH'].address, + 10, + ); + console.log(`ETH Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokens['ETH'].address, dexKey); + }); + + it('WETH getTopPoolsForToken', async function () { + const poolLiquidity = await stader.getTopPoolsForToken( + tokens['WETH'].address, + 10, + ); + console.log(`WETH Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokens['WETH'].address, dexKey); + }); + + it('ETHx getTopPoolsForToken', async function () { + const poolLiquidity = await stader.getTopPoolsForToken( + tokens['ETHx'].address, + 10, + ); + console.log(`ETHx Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, tokens['ETHx'].address, dexKey); + }); + }); +}); diff --git a/src/dex/stader/stader-pool.ts b/src/dex/stader/stader-pool.ts new file mode 100644 index 000000000..6c8db35c2 --- /dev/null +++ b/src/dex/stader/stader-pool.ts @@ -0,0 +1,77 @@ +import { Interface } from '@ethersproject/abi'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { DeepReadonly } from 'ts-essentials'; +import { catchParseLogError } from '../../utils'; +import { ETHxPoolState } from './types'; +import { getOnChainStateETHx } from './utils'; +import { BI_POWS } from '../../bigint-constants'; + +export class ETHxEventPool extends StatefulEventSubscriber { + decoder = (log: Log) => this.poolInterface.parseLog(log); + DECIMALS = 1000000000000000000n; + addressesSubscribed: string[]; + + constructor( + parentName: string, + protected dexHelper: IDexHelper, + private poolAddress: Address, + private poolInterface: Interface, + logger: Logger, + ) { + super(parentName, 'ETHx', dexHelper, logger); + this.addressesSubscribed = [poolAddress]; + } + + protected processLog( + state: DeepReadonly, + log: Readonly, + ): DeepReadonly | null { + try { + const event = this.decoder(log); + if (event.name === 'ExchangeRateUpdated') { + const totalEth = BigInt(event.args.totalEth); + const ethxSupply = BigInt(event.args.ethxSupply); + return { + totalETHBalance: totalEth, + totalETHXSupply: ethxSupply, + }; + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + async getOrGenerateState( + blockNumber: number, + ): Promise> { + let state = this.getState(blockNumber); + if (!state) { + state = await this.generateState(blockNumber); + this.setState(state, blockNumber); + } + return state; + } + + async generateState( + blockNumber: number, + ): Promise> { + const state = await getOnChainStateETHx( + this.dexHelper.multiContract, + this.poolAddress, + this.poolInterface, + blockNumber, + ); + + return state; + } + + getPrice(state: ETHxPoolState, ethAmount: bigint): bigint { + const { totalETHBalance, totalETHXSupply } = state; + + return (ethAmount * totalETHXSupply) / totalETHBalance; + } +} diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts new file mode 100644 index 000000000..409ecc30f --- /dev/null +++ b/src/dex/stader/stader.ts @@ -0,0 +1,240 @@ +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { NumberAsString, SwapSide } from '@paraswap/core'; +import { + AdapterExchangeParam, + Address, + DexExchangeParam, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + Token, + TransferFeeParams, +} from '../../types'; +import { IDexTxBuilder } from '../idex'; +import SSPMAbi from '../../abi/SSPM.json'; +import StadeOracleAbi from '../../abi/StaderOracle.json'; +import { ETHER_ADDRESS, Network, NULL_ADDRESS } from '../../constants'; +import { IDexHelper } from '../../dex-helper'; +import { SimpleExchange } from '../simple-exchange'; +import { BI_POWS } from '../../bigint-constants'; +import { AsyncOrSync } from 'ts-essentials'; +import { ETHxEventPool } from './stader-pool'; +import { StaderData, SSPMFunctions } from './types'; +import { StaderConfig } from './config'; +import { WethFunctions } from '../weth/types'; +import ERC20ABI from '../../abi/erc20.json'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import _ from 'lodash'; +import { extractReturnAmountPosition } from '../../executor/utils'; +import { getDexKeysWithNetwork, isETHAddress } from '../../utils'; + +export class Stader + extends SimpleExchange + implements IDexTxBuilder +{ + static dexKeys = ['Stader']; + ETHxAddress: string; + SSPM_Address: string; + SSPMInterface: Interface; + StaderOracleAddress: string; + StaderOracleInterface: Interface; + erc20Interface: Interface; + needWrapNative = false; + ethxPool: ETHxEventPool; + logger: Logger; + hasConstantPriceLargeAmounts: boolean = true; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(StaderConfig, ['Stader'])); + + constructor( + protected network: Network, + dexKey: string, + dexHelper: IDexHelper, + protected config = StaderConfig[dexKey][network], + ) { + super(dexHelper, 'Stader'); + this.network = dexHelper.config.data.network; + this.ETHxAddress = this.config.ETHx.toLowerCase(); + this.SSPM_Address = this.config.SSPM.toLowerCase(); + this.SSPMInterface = new Interface(SSPMAbi as JsonFragment[]); + this.StaderOracleAddress = this.config.StaderOracle.toLowerCase(); + this.StaderOracleInterface = new Interface( + StadeOracleAbi as JsonFragment[], + ); + this.erc20Interface = new Interface(ERC20ABI); + this.logger = dexHelper.getLogger(this.dexKey); + this.ethxPool = new ETHxEventPool( + this.dexKey, + dexHelper, + this.StaderOracleAddress, + this.StaderOracleInterface, + this.logger, + ); + } + + getAdapterParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: StaderData, + side: SwapSide, + ): AdapterExchangeParam { + return { + targetExchange: NULL_ADDRESS, + payload: '0x', + networkFee: '0', + }; + } + + async initializePricing(blockNumber: number) { + await this.ethxPool.initialize(blockNumber); + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amountsIn: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[] | undefined, + transferFees?: TransferFeeParams | undefined, + isFirstSwap?: boolean | undefined, + ): Promise | null> { + if (side === SwapSide.BUY) return null; + if (!this.isEligibleSwap(srcToken, destToken)) return null; + + const pool = this.ethxPool; + const state = await pool.getOrGenerateState(blockNumber); + + const unitIn = BI_POWS[18]; + const unitOut = pool.getPrice(state, unitIn); + const amountsOut = amountsIn.map(amountIn => + pool.getPrice(state, amountIn), + ); + + return [ + { + prices: amountsOut, + unit: unitOut, + data: {}, + exchange: this.dexKey, + poolIdentifier: `${ETHER_ADDRESS}_${destToken.address}`.toLowerCase(), + gasCost: 120_000, + poolAddresses: [destToken.address], + }, + ]; + } + + isEligibleSwap(srcToken: Token | string, destToken: Token | string): boolean { + const srcTokenAddress = ( + typeof srcToken === 'string' ? srcToken : srcToken.address + ).toLowerCase(); + const destTokenAddress = ( + typeof destToken === 'string' ? destToken : destToken.address + ).toLowerCase(); + + return ( + (isETHAddress(srcTokenAddress) || this.isWETH(srcTokenAddress)) && + destTokenAddress === this.ETHxAddress + ); + } + + getDexParam( + srcToken: Address, + _destToken: Address, + srcAmount: NumberAsString, + _destAmount: NumberAsString, + _recipient: Address, + _data: StaderData, + _side: SwapSide, + ): DexExchangeParam { + if (_side === SwapSide.BUY) throw new Error(`Buy not supported`); + + const swapData = this.SSPMInterface.encodeFunctionData( + SSPMFunctions.deposit, + [_recipient], + ); + + return { + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: true, + exchangeData: swapData, + targetExchange: this.config.SSPM.toLowerCase(), + preSwapUnwrapCalldata: this.isWETH(srcToken) + ? this.erc20Interface.encodeFunctionData(WethFunctions.withdraw, [ + srcAmount, + ]) + : undefined, + returnAmountPos: + _side === SwapSide.SELL + ? extractReturnAmountPosition( + this.SSPMInterface, + SSPMFunctions.deposit, + ) + : undefined, + }; + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (side === SwapSide.BUY) return []; + + if (!this.isEligibleSwap(srcToken, destToken)) return []; + + return [`${ETHER_ADDRESS}_${destToken.address}`.toLowerCase()]; + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return CALLDATA_GAS_COST.DEX_OVERHEAD + CALLDATA_GAS_COST.LENGTH_SMALL; + } + + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return null; + } + + getTopPoolsForToken( + tokenAddress: string, + limit: number, + ): AsyncOrSync { + if (isETHAddress(tokenAddress) || this.isWETH(tokenAddress.toLowerCase())) { + return [ + { + exchange: this.dexKey, + address: this.config.ETHx, + connectorTokens: [ + { + decimals: 18, + address: this.config.ETHx, + }, + ], + liquidityUSD: 1000000000, + }, + ]; + } + + if (tokenAddress.toLowerCase() === this.ETHxAddress.toLowerCase()) { + const eth = ETHER_ADDRESS; + const weth = this.dexHelper.config.data.wrappedNativeTokenAddress; + return [eth, weth].map(t => ({ + exchange: this.dexKey, + address: this.config.ETHx, + connectorTokens: [ + { + decimals: 18, + address: t, + }, + ], + liquidityUSD: 1000000000, + })); + } + + return []; + } +} diff --git a/src/dex/stader/types.ts b/src/dex/stader/types.ts new file mode 100644 index 000000000..3dcf7b530 --- /dev/null +++ b/src/dex/stader/types.ts @@ -0,0 +1,16 @@ +export type ETHxPoolState = { + totalETHBalance: bigint; + totalETHXSupply: bigint; +}; + +export type StaderData = {}; + +export type DexParams = { + ETHx: string; + SSPM: string; + StaderOracle: string; +}; + +export enum SSPMFunctions { + deposit = 'deposit', +} diff --git a/src/dex/stader/utils.ts b/src/dex/stader/utils.ts new file mode 100644 index 000000000..4bf0f9a2b --- /dev/null +++ b/src/dex/stader/utils.ts @@ -0,0 +1,34 @@ +import { Contract } from 'web3-eth-contract'; +import { ETHxPoolState } from './types'; +import { Interface, AbiCoder } from '@ethersproject/abi'; +import { BI_POWS } from '../../bigint-constants'; + +export async function getOnChainStateETHx( + multiContract: Contract, + poolAddress: string, + poolInterface: Interface, + blockNumber: number | 'latest', +): Promise { + const coder = new AbiCoder(); + const data: { returnData: any[] } = await multiContract.methods + .aggregate([ + { + target: poolAddress, + callData: poolInterface.encodeFunctionData('getExchangeRate', []), + }, + ]) + .call({}, blockNumber); + + const decodedData = coder.decode( + ['uint256', 'uint256', 'uint256'], + data.returnData[0], + ); + + const totalETHBalance = BigInt(decodedData[1].toString()); + const totalETHXSupply = BigInt(decodedData[2].toString()); + + return { + totalETHBalance, + totalETHXSupply, + }; +} diff --git a/src/dex/swaap-v2/config.ts b/src/dex/swaap-v2/config.ts index d1c1a53a3..0a57694a9 100644 --- a/src/dex/swaap-v2/config.ts +++ b/src/dex/swaap-v2/config.ts @@ -10,6 +10,7 @@ export const SwaapV2Config: DexConfigMap = { [Network.BASE]: {}, [Network.BSC]: {}, [Network.OPTIMISM]: {}, + [Network.AVALANCHE]: {}, }, }; diff --git a/src/dex/swaap-v2/swaap-v2-e2e.test.ts b/src/dex/swaap-v2/swaap-v2-e2e.test.ts index bd3799e04..52e3e4a7a 100644 --- a/src/dex/swaap-v2/swaap-v2-e2e.test.ts +++ b/src/dex/swaap-v2/swaap-v2-e2e.test.ts @@ -148,18 +148,6 @@ describe('SwaapV2 E2E', () => { buyAmount: '150000000000000000000', }, ], - [ - { - name: 'WMATIC', - sellAmount: '150000000000000000000', - buyAmount: '100000000', - }, - { - name: 'USDCe', - sellAmount: '100000000', - buyAmount: '150000000000000000000', - }, - ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -228,12 +216,12 @@ describe('SwaapV2 E2E', () => { { name: 'WETH', sellAmount: '10000000000000000', - buyAmount: '1000000000000000000', + buyAmount: '100000', }, { name: 'USDC', - sellAmount: '1000000', - buyAmount: '1000000', + sellAmount: '100000', + buyAmount: '10000000000000000', }, ], ]; @@ -304,12 +292,12 @@ describe('SwaapV2 E2E', () => { { name: 'WETH', sellAmount: '100000000000000000', - buyAmount: '1000000000000000', + buyAmount: '100000', }, { name: 'USDC', - sellAmount: '10000000', - buyAmount: '1000000', + sellAmount: '100000', + buyAmount: '100000000000000000', }, ], ]; @@ -379,13 +367,13 @@ describe('SwaapV2 E2E', () => { [ { name: 'WBNB', - sellAmount: '1000000000000000000', - buyAmount: '100000000000000000000', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', }, { name: 'USDT', - sellAmount: '100000000000000000000', - buyAmount: '1000000000000000000', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', }, ], ]; @@ -455,12 +443,88 @@ describe('SwaapV2 E2E', () => { [ { name: 'WETH', + sellAmount: '10000000000000000', + buyAmount: '100000', + }, + { + name: 'USDC', + sellAmount: '100000', + buyAmount: '10000000000000000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + undefined, + sleepMs, + ); + }); + }); + }); + }); + }), + ); + }); + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'WAVAX', sellAmount: '1000000000000000000', - buyAmount: '100000000', + buyAmount: '1000000', }, { name: 'USDC', - sellAmount: '45410357', + sellAmount: '1000000', buyAmount: '1000000000000000000', }, ], diff --git a/src/dex/swaap-v2/swaap-v2-integration.test.ts b/src/dex/swaap-v2/swaap-v2-integration.test.ts index ed6bf4f50..4fa24c1a9 100644 --- a/src/dex/swaap-v2/swaap-v2-integration.test.ts +++ b/src/dex/swaap-v2/swaap-v2-integration.test.ts @@ -178,4 +178,42 @@ describe('Swaap V2', function () { } }); }); + + describe('Avalanche', () => { + const network = Network.AVALANCHE; + const dexHelper = new DummyDexHelper(network); + + beforeAll(async () => { + swaapV2 = new SwaapV2(network, dexKey, dexHelper); + if (swaapV2.initializePricing) { + await swaapV2.initializePricing(0); + await sleep(5000); + } + }); + + const tokens = Tokens[network]; + const srcTokenSymbol = 'USDC'; + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const swaapV2 = new SwaapV2(network, dexKey, dexHelper); + const poolLiquidity = await swaapV2.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log( + `${srcTokenSymbol} Top Pools:`, + util.inspect(poolLiquidity, false, null, true), + ); + + if (!swaapV2.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + tokens[srcTokenSymbol].address, + dexKey, + ); + } + }); + }); }); diff --git a/src/dex/synthetix/synthetix.ts b/src/dex/synthetix/synthetix.ts index 52edd88ed..a6650154f 100644 --- a/src/dex/synthetix/synthetix.ts +++ b/src/dex/synthetix/synthetix.ts @@ -45,7 +45,7 @@ export class Synthetix extends SimpleExchange implements IDex { synthetixState: SynthetixState; // It is intermediate measure before we have event base Oracles - statePollingTimer?: NodeJS.Timer; + statePollingTimer?: NodeJS.Timeout; public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork(SynthetixConfig); diff --git a/src/dex/uniswap-v2/config.ts b/src/dex/uniswap-v2/config.ts index 02a45af11..9adbda125 100644 --- a/src/dex/uniswap-v2/config.ts +++ b/src/dex/uniswap-v2/config.ts @@ -427,6 +427,22 @@ export const UniswapV2Config: DexConfigMap = { '0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303', feeCode: 30, }, + [Network.GNOSIS]: { + subgraphURL: '7czeiia7ZXvsW45szX2w8EK1ZNgZWZET83zYCwE6JT9x', + factoryAddress: '0xc35dadb65012ec5796536bd9864ed8773abc74c4', + initCode: + '0xe18a34eb0e04b04f7a0ac29a6e80748dca96319b42c54d679cb821dca90c6303', + feeCode: 30, + }, + }, + HoneySwap: { + [Network.GNOSIS]: { + subgraphURL: '7vjh6q6gj6M1bhRNgkKZmWqd5jot79kV84BiQtUDQSdV', + factoryAddress: '0xa818b4f111ccac7aa31d0bcc0806d64f2e0737d7', + initCode: + '0xdbd265d4828759db2a0351a7b97180bcc41449a038bcd08a45afe937147f6267', + feeCode: 30, + }, }, Zyberswap: { [Network.ARBITRUM]: { diff --git a/src/dex/uniswap-v2/nomiswap-v2.ts b/src/dex/uniswap-v2/nomiswap-v2.ts index 0dac17734..51cd2320c 100644 --- a/src/dex/uniswap-v2/nomiswap-v2.ts +++ b/src/dex/uniswap-v2/nomiswap-v2.ts @@ -18,6 +18,17 @@ export const NomiswapV2Config: DexConfigMap = { subgraphURL: '5CBKsDihF7KeBrNX4bgtb4tVFqy41PguVm88zBGpd4Hi', }, }, + // not exactly the same, but swapFee logic works in the same way + SwaprV2: { + [Network.GNOSIS]: { + factoryAddress: '0x5D48C95AdfFD4B40c1AAADc4e08fc44117E02179', + initCode: + '0xfcee8e246628cf9708fcb9d6f5d4aa0d96b62bb50f1af012a75a3292849e2dca', + poolGasCost: 120 * 1000, + feeCode: 25, // this is ignored as SwaprV2 uses dynamic fees, + subgraphURL: 'EWoa3JwNntAWtaLsLixTU25smp4R5tzGvs9rFXx9NHKZ', + }, + }, }; export class NomiswapV2 extends UniswapV2 { diff --git a/src/dex/uniswap-v2/uniswap-v2-e2e-gnosis.test.ts b/src/dex/uniswap-v2/uniswap-v2-e2e-gnosis.test.ts new file mode 100644 index 000000000..66ced3f61 --- /dev/null +++ b/src/dex/uniswap-v2/uniswap-v2-e2e-gnosis.test.ts @@ -0,0 +1,283 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +describe('UniswapV2 E2E Gnosis', () => { + const network = Network.GNOSIS; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + describe('SushiSwap', () => { + const dexKey = 'SushiSwap'; + + describe('swapExactAmountIn', () => { + it('SushiSwap ETH -> TOKEN', async () => { + await testE2E( + tokens.XDAI, + tokens.USDC, + holders.XDAI, + '700000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> ETH', async () => { + await testE2E( + tokens.WETH, + tokens.XDAI, + holders.WETH, + '700000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> TOKEN', async () => { + await testE2E( + tokens.WETH, + tokens.USDC, + holders.WETH, + '7000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + }); + + describe('swapExactAmountOut', () => { + it('SushiSwap ETH -> TOKEN', async () => { + await testE2E( + tokens.XDAI, + tokens.USDC, + holders.XDAI, + '700000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> ETH', async () => { + await testE2E( + tokens.USDC, + tokens.XDAI, + holders.USDC, + '7000000000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> TOKEN', async () => { + await testE2E( + tokens.USDC, + tokens.WETH, + holders.USDC, + '7000000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + }); + }); + + describe('HoneySwap', () => { + const dexKey = 'HoneySwap'; + + describe('swapExactAmountIn', () => { + it('SushiSwap ETH -> TOKEN', async () => { + await testE2E( + tokens.XDAI, + tokens.USDC, + holders.XDAI, + '700000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> ETH', async () => { + await testE2E( + tokens.WETH, + tokens.XDAI, + holders.WETH, + '700000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> TOKEN', async () => { + await testE2E( + tokens.WETH, + tokens.USDC, + holders.WETH, + '7000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + }); + + describe('swapExactAmountOut', () => { + it('SushiSwap ETH -> TOKEN', async () => { + await testE2E( + tokens.XDAI, + tokens.USDC, + holders.XDAI, + '700000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> ETH', async () => { + await testE2E( + tokens.USDC, + tokens.XDAI, + holders.USDC, + '7000000000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + it('SushiSwap TOKEN -> TOKEN', async () => { + await testE2E( + tokens.USDC, + tokens.WETH, + holders.USDC, + '700000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + }); + }); + + describe('SwaprV2', () => { + const dexKey = 'SwaprV2'; + + describe('swapExactAmountIn', () => { + it('SushiSwap ETH -> TOKEN', async () => { + await testE2E( + tokens.XDAI, + tokens.USDC, + holders.XDAI, + '700000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + it('SwaprV2 TOKEN -> ETH', async () => { + await testE2E( + tokens.WETH, + tokens.XDAI, + holders.WETH, + '700000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + it('SwaprV2 TOKEN -> TOKEN', async () => { + await testE2E( + tokens.WETH, + tokens.USDC, + holders.WETH, + '7000000000000000', + SwapSide.SELL, + dexKey, + ContractMethod.swapExactAmountIn, + network, + provider, + ); + }); + }); + + describe('swapExactAmountOut', () => { + it('SwaprV2 ETH -> TOKEN', async () => { + await testE2E( + tokens.XDAI, + // SWPR/WXDAI pool has non-standard feeCode - 100 + tokens.SWPR, + holders.XDAI, + '7000000000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + it('SwaprV2 TOKEN -> ETH', async () => { + await testE2E( + tokens.USDC, + tokens.XDAI, + holders.USDC, + '7000000000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + it('SwaprV2 TOKEN -> TOKEN', async () => { + await testE2E( + tokens.USDC, + tokens.WETH, + holders.USDC, + '700000000000000', + SwapSide.BUY, + dexKey, + ContractMethod.swapExactAmountOut, + network, + provider, + ); + }); + }); + }); +}); diff --git a/src/dex/uniswap-v2/uniswap-v2.ts b/src/dex/uniswap-v2/uniswap-v2.ts index 111b1edc4..ff022a36c 100644 --- a/src/dex/uniswap-v2/uniswap-v2.ts +++ b/src/dex/uniswap-v2/uniswap-v2.ts @@ -681,7 +681,7 @@ export class UniswapV2 } getAdapters(side: SwapSide): { name: string; index: number }[] | null { - return this.adapters[side]; + return this.adapters?.[side] ?? null; } async getTopPoolsForToken( diff --git a/src/dex/uniswap-v3/config.ts b/src/dex/uniswap-v3/config.ts index 88aa59a65..363671e15 100644 --- a/src/dex/uniswap-v3/config.ts +++ b/src/dex/uniswap-v3/config.ts @@ -285,8 +285,7 @@ export const UniswapV3Config: DexConfigMap = { chunksCount: 5, initRetryFrequency: 30, initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, - subgraphURL: - 'https://api.studio.thegraph.com/query/44554/uniswap-v3/version/latest', + subgraphURL: 'E9PLkFzXVX1a9YFtLLyXmLV93ihAUFCvgrnrdnTrnFeN', }, }, SpookySwapV3: { @@ -346,6 +345,20 @@ export const UniswapV3Config: DexConfigMap = { subgraphURL: 'https://api.studio.thegraph.com/query/59130/v3alb/0.3', }, }, + OkuTradeV3: { + [Network.GNOSIS]: { + factory: '0xe32f7dd7e3f098d518ff19a22d5f028e076489b1', + quoter: '0x7E9cB3499A6cee3baBe5c8a3D328EA7FD36578f4', + router: '0xB5253c895361678FF5D0fFDdA81Dd02f1F7a81D6', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x35Db9Ac2ff3C5A86fde165Bd26D43d303417942E', + uniswapMulticall: '0x4dfa9a980efE4802E969AC33968E3d6E59B8a19e', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'Dimv1udMsJu1DqirVM4G2vNRvH8CWzWTn7GffQQCGAaq', + }, + }, VelodromeSlipstream: { [Network.OPTIMISM]: { factory: '0x548118C7E0B865C2CfA94D15EC86B666468ac758', diff --git a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts index a0096439d..3c8a7a425 100644 --- a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts @@ -1364,4 +1364,28 @@ describe('UniswapV3 E2E', () => { }); }); }); + + describe('OkuTradeV3 E2E', () => { + const dexKey = 'OkuTradeV3'; + describe('Gnosis', () => { + const network = Network.GNOSIS; + + const tokenASymbol: string = 'WETH'; + const tokenBSymbol: string = 'sDAI'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + }); }); diff --git a/src/dex/uniswap-v3/uniswap-v3-pool.ts b/src/dex/uniswap-v3/uniswap-v3-pool.ts index 2110470b2..c050eb4d7 100644 --- a/src/dex/uniswap-v3/uniswap-v3-pool.ts +++ b/src/dex/uniswap-v3/uniswap-v3-pool.ts @@ -254,6 +254,33 @@ export class UniswapV3EventPool extends StatefulEventSubscriber { return TICK_BITMAP_TO_USE + TICK_BITMAP_BUFFER; } + async checkState( + blockNumber: number, + ): Promise | null> { + const state = this.getState(blockNumber); + if (state) { + return state; + } + + this.logger.error( + `UniV3: No state found for ${this.name} ${this.addressesSubscribed[0]} for bn: ${blockNumber}`, + ); + return null; + } + + _setState(state: any, blockNumber: number, reason?: string): void { + if (this.parentName === 'UniswapV3') { + this.logger.info( + `UniV3: Setting state: '${!!state ? 'non-empty' : 'empty'}' for '${ + this.name + }' for bn: '${blockNumber}' due to reason: '${ + reason ?? 'outside_of_event_subscriber' + }'`, + ); + } + super._setState(state, blockNumber); + } + async generateState(blockNumber: number): Promise> { const callData = this._getStateRequestCallData(); diff --git a/src/dex/uniswap-v3/uniswap-v3.ts b/src/dex/uniswap-v3/uniswap-v3.ts index 3c2e3385a..91eeb74c0 100644 --- a/src/dex/uniswap-v3/uniswap-v3.ts +++ b/src/dex/uniswap-v3/uniswap-v3.ts @@ -115,6 +115,7 @@ export class UniswapV3 'BaseswapV3', 'PharaohV2', 'AlienBaseV3', + 'OkuTradeV3', ]), ); @@ -714,6 +715,10 @@ export class UniswapV3 if (selectedPools.length === 0) return null; + await Promise.all( + selectedPools.map(pool => pool.checkState(blockNumber)), + ); + const poolsToUse = selectedPools.reduce( (acc, pool) => { let state = pool.getState(blockNumber); @@ -721,6 +726,7 @@ export class UniswapV3 this.logger.trace( `${this.dexKey}: State === null. Fallback to rpc ${pool.name}`, ); + // as we generate state (if nullified) in previous Promise.all, here should only be pools with failed initialization acc.poolWithoutState.push(pool); } else { acc.poolWithState.push(pool); @@ -733,6 +739,12 @@ export class UniswapV3 }, ); + poolsToUse.poolWithoutState.forEach(pool => { + this.logger.warn( + `UniV3: Pool ${pool.name} on ${this.dexKey} has no state. Fallback to rpc`, + ); + }); + const states = poolsToUse.poolWithState.map( p => p.getState(blockNumber)!, ); diff --git a/src/dex/usual-bond/config.ts b/src/dex/usual-bond/config.ts deleted file mode 100644 index 09fe45eca..000000000 --- a/src/dex/usual-bond/config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DexParams } from './types'; -import { DexConfigMap } from '../../types'; -import { Network } from '../../constants'; - -export const UsualBondConfig: DexConfigMap = { - UsualBond: { - [Network.MAINNET]: { - usd0Address: '0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5', - usd0ppAddress: '0x35D8949372D46B7a3D5A56006AE77B215fc69bC0', - }, - }, -}; diff --git a/src/dex/usual-bond/usual-bond-integration.test.ts b/src/dex/usual-bond/usual-bond-integration.test.ts deleted file mode 100644 index 3844bc200..000000000 --- a/src/dex/usual-bond/usual-bond-integration.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-disable no-console */ -import dotenv from 'dotenv'; -dotenv.config(); - -import { Interface } from '@ethersproject/abi'; -import { DummyDexHelper } from '../../dex-helper/index'; -import { Network, SwapSide } from '../../constants'; -import { BI_POWS } from '../../bigint-constants'; -import { UsualBond } from './usual-bond'; -import { - checkPoolPrices, - checkConstantPoolPrices, - checkPoolsLiquidity, -} from '../../../tests/utils'; -import { Tokens } from '../../../tests/constants-e2e'; - -async function testPricingOnNetwork( - usualBond: UsualBond, - network: Network, - dexKey: string, - blockNumber: number, - srcTokenAddress: string, - destTokenAddress: string, - side: SwapSide, - amounts: bigint[], - funcNameToCheck: string, -) { - const networkTokens = Tokens[network]; - - console.log(amounts); - - const pools = await usualBond.getPoolIdentifiers( - networkTokens['USD0'], - networkTokens['USD0++'], - side, - blockNumber, - ); - console.log(`${'USD0'} <> ${'USD0++'} Pool Identifiers: `, pools); - - expect(pools.length).toBeGreaterThan(0); - - const poolPrices = await usualBond.getPricesVolume( - networkTokens['USD0'], - networkTokens['USD0++'], - amounts, - side, - blockNumber, - pools, - ); - console.log(`${'USD0'} <> ${'USD0++'} Pool Prices: `, poolPrices); - - expect(poolPrices).not.toBeNull(); - if (usualBond.hasConstantPriceLargeAmounts) { - checkConstantPoolPrices(poolPrices!, amounts, dexKey); - } else { - checkPoolPrices(poolPrices!, amounts, side, dexKey); - } - - // Check if onchain pricing equals to calculated ones - checkPoolPrices(poolPrices!, amounts, side, dexKey); -} - -describe('UsualBond', function () { - const dexKey = 'UsualBond'; - let blockNumber: number; - let usualBond: UsualBond; - - describe('Mainnet', () => { - const network = Network.MAINNET; - const dexHelper = new DummyDexHelper(network); - - // Don't forget to update relevant tokens in constant-e2e.ts - - const amountsForSell = [ - 0n, - 1n * BI_POWS[18], - 2n * BI_POWS[18], - 3n * BI_POWS[18], - 4n * BI_POWS[18], - 5n * BI_POWS[18], - 6n * BI_POWS[18], - 7n * BI_POWS[18], - 8n * BI_POWS[18], - 9n * BI_POWS[18], - 10n * BI_POWS[18], - ]; - - beforeAll(async () => { - blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); - usualBond = new UsualBond(network, dexKey, dexHelper); - if (usualBond.initializePricing) { - await usualBond.initializePricing(blockNumber); - } - }); - - it('getPoolIdentifiers and getPricesVolume SELL', async function () { - await testPricingOnNetwork( - usualBond, - network, - dexKey, - blockNumber, - 'USD0', - 'USD0++', - SwapSide.SELL, - amountsForSell, - '', - ); - }); - - it('getTopPoolsForToken: USD0', async function () { - const tokenA = Tokens[network]['USD0']; - const dexHelper = new DummyDexHelper(network); - const usualBond = new UsualBond(network, dexKey, dexHelper); - - const poolLiquidity = await usualBond.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - - it('getTopPoolsForToken: USD0++', async function () { - const tokenA = Tokens[network]['USD0++']; - const dexHelper = new DummyDexHelper(network); - const usualBond = new UsualBond(network, dexKey, dexHelper); - - const poolLiquidity = await usualBond.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - }); -}); diff --git a/src/dex/usual-bond/types.ts b/src/dex/usual/types.ts similarity index 55% rename from src/dex/usual-bond/types.ts rename to src/dex/usual/types.ts index ce90c4eb4..ef8151986 100644 --- a/src/dex/usual-bond/types.ts +++ b/src/dex/usual/types.ts @@ -5,6 +5,6 @@ export type PoolState = {}; export type UsualBondData = {}; export type DexParams = { - usd0Address: Address; - usd0ppAddress: Address; + fromToken: { address: Address; decimals: number }; + toToken: { address: Address; decimals: number }; }; diff --git a/src/dex/usual/usual-bond.ts b/src/dex/usual/usual-bond.ts new file mode 100644 index 000000000..ac39a92bc --- /dev/null +++ b/src/dex/usual/usual-bond.ts @@ -0,0 +1,69 @@ +import { + Address, + NumberAsString, + DexExchangeParam, + DexConfigMap, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { UsualBondData, DexParams } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import USD0PP_ABI from '../../abi/usual-bond/usd0pp.abi.json'; +import { Usual } from './usual'; +import { getDexKeysWithNetwork } from '../../utils'; + +const Config: DexConfigMap = { + UsualBond: { + [Network.MAINNET]: { + fromToken: { + address: '0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5', + decimals: 18, + }, + toToken: { + address: '0x35d8949372d46b7a3d5a56006ae77b215fc69bc0', + decimals: 18, + }, + }, + }, +}; + +export class UsualBond extends Usual { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + usd0ppIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper, Config[dexKey][network]); + this.usd0ppIface = new Interface(USD0PP_ABI as JsonFragment[]); + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: UsualBondData, + side: SwapSide, + ): Promise { + if (this.isFromToken(srcToken) && this.isToToken(destToken)) { + const exchangeData = this.usd0ppIface.encodeFunctionData('mint', [ + srcAmount, + ]); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: this.config.toToken.address, + returnAmountPos: undefined, + }; + } + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual-bond/usual-bond-e2e.test.ts b/src/dex/usual/usual-e2e.test.ts similarity index 66% rename from src/dex/usual-bond/usual-bond-e2e.test.ts rename to src/dex/usual/usual-e2e.test.ts index f6d987514..1c3008a15 100644 --- a/src/dex/usual-bond/usual-bond-e2e.test.ts +++ b/src/dex/usual/usual-e2e.test.ts @@ -3,11 +3,7 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { - Tokens, - Holders, - NativeTokenSymbols, -} from '../../../tests/constants-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; import { Network, ContractMethod, SwapSide } from '../../constants'; import { StaticJsonRpcProvider } from '@ethersproject/providers'; import { generateConfig } from '../../config'; @@ -78,3 +74,49 @@ describe('UsualBond E2E', () => { ); }); }); + +describe('UsualMWrappedM E2E', () => { + const dexKey = 'UsualMWrappedM'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'WrappedM'; + const tokenBSymbol: string = 'UsualM'; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); + +describe('UsualM<>Usd0 E2E', () => { + const dexKey = 'UsualMUsd0'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'UsualM'; + const tokenBSymbol: string = 'USD0'; + + const tokenAAmount: string = '1000000'; + const tokenBAmount: string = '1000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/usual/usual-integration.test.ts b/src/dex/usual/usual-integration.test.ts new file mode 100644 index 000000000..3041c157c --- /dev/null +++ b/src/dex/usual/usual-integration.test.ts @@ -0,0 +1,319 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { + checkPoolPrices, + checkConstantPoolPrices, + checkPoolsLiquidity, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { UsualMWrappedM } from './usual-m-wrapped-m'; +import { UsualMUsd0 } from './usual-m-usd0'; +import { Usual } from './usual'; +import { UsualBond } from './usual-bond'; + +async function testPricingOnNetwork( + usual: Usual, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + console.log(amounts); + + const pools = await usual.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usual.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (usual.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + // Check if onchain pricing equals to calculated ones + checkPoolPrices(poolPrices!, amounts, side, dexKey); +} + +describe('UsualBond', function () { + const dexKey = 'UsualBond'; + let blockNumber: number; + let usualBond: UsualBond; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualBond = new UsualBond(network, dexKey, dexHelper); + if (usualBond.initializePricing) { + await usualBond.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualBond, + network, + dexKey, + blockNumber, + 'USD0', + 'USD0++', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: USD0', async function () { + const tokenA = Tokens[network]['USD0']; + const dexHelper = new DummyDexHelper(network); + const usualBond = new UsualBond(network, dexKey, dexHelper); + + const poolLiquidity = await usualBond.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: USD0++', async function () { + const tokenA = Tokens[network]['USD0++']; + const dexHelper = new DummyDexHelper(network); + const usualBond = new UsualBond(network, dexKey, dexHelper); + + const poolLiquidity = await usualBond.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); + +describe('WrappedM<>UsualM', function () { + const dexKey = 'UsualMWrappedM'; + let blockNumber: number; + let usualMWrappedM: UsualMWrappedM; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualMWrappedM = new UsualMWrappedM(network, dexKey, dexHelper); + if (usualMWrappedM.initializePricing) { + await usualMWrappedM.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualMWrappedM, + network, + dexKey, + blockNumber, + 'WrappedM', + 'UsualM', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: WrappedM', async function () { + const tokenA = Tokens[network]['WrappedM']; + const dexHelper = new DummyDexHelper(network); + const usualMWrappedM = new UsualMWrappedM(network, dexKey, dexHelper); + + const poolLiquidity = await usualMWrappedM.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: UsualM', async function () { + const tokenA = Tokens[network]['UsualM']; + const dexHelper = new DummyDexHelper(network); + const usualMWrappedM = new UsualMWrappedM(network, dexKey, dexHelper); + + const poolLiquidity = await usualMWrappedM.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); + +describe('UsualM<>USD0', function () { + const dexKey = 'UsualMUsd0'; + let blockNumber: number; + let usualMUsd0: UsualMUsd0; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + if (usualMUsd0.initializePricing) { + await usualMUsd0.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualMUsd0, + network, + dexKey, + blockNumber, + 'UsualM', + 'USD0', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: UsualM', async function () { + const tokenA = Tokens[network]['UsualM']; + const dexHelper = new DummyDexHelper(network); + const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + + const poolLiquidity = await usualMUsd0.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: USD0', async function () { + const tokenA = Tokens[network]['USD0']; + const dexHelper = new DummyDexHelper(network); + const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + + const poolLiquidity = await usualMUsd0.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); diff --git a/src/dex/usual/usual-m-usd0.ts b/src/dex/usual/usual-m-usd0.ts new file mode 100644 index 000000000..653450cee --- /dev/null +++ b/src/dex/usual/usual-m-usd0.ts @@ -0,0 +1,76 @@ +import { + Address, + NumberAsString, + DexExchangeParam, + DexConfigMap, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { DexParams } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { Usual } from './usual'; +import { getDexKeysWithNetwork } from '../../utils'; +import USUAL_DAO_COLLATERAL_ABI from '../../abi/usual-m-usd0/usualCollateralDao.abi.json'; + +const Config: DexConfigMap = + { + UsualMUsd0: { + [Network.MAINNET]: { + usualDaoCollateralAddress: '0xde6e1F680C4816446C8D515989E2358636A38b04', + fromToken: { + address: '0x4cbc25559dbbd1272ec5b64c7b5f48a2405e6470', + decimals: 6, + }, + toToken: { + address: '0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5', + decimals: 18, + }, + }, + }, + }; + +export class UsualMUsd0 extends Usual { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + usualDaoCollateralIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper, Config[dexKey][network]); + this.usualDaoCollateralIface = new Interface( + USUAL_DAO_COLLATERAL_ABI as JsonFragment[], + ); + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: {}, + side: SwapSide, + ): Promise { + if (this.isFromToken(srcToken) && this.isToToken(destToken)) { + const exchangeData = this.usualDaoCollateralIface.encodeFunctionData( + 'swap', + [srcToken, srcAmount, destAmount], + ); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: + Config[this.dexKey][this.network].usualDaoCollateralAddress, + returnAmountPos: undefined, + }; + } + + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual/usual-m-wrapped-m.ts b/src/dex/usual/usual-m-wrapped-m.ts new file mode 100644 index 000000000..8eb5d822f --- /dev/null +++ b/src/dex/usual/usual-m-wrapped-m.ts @@ -0,0 +1,71 @@ +import { + Address, + NumberAsString, + DexExchangeParam, + DexConfigMap, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { DexParams } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { Usual } from './usual'; +import { getDexKeysWithNetwork } from '../../utils'; +import USUALM_ABI from '../../abi/usual-m-wrapped-m/usualM.abi.json'; + +const Config: DexConfigMap = { + UsualMWrappedM: { + [Network.MAINNET]: { + fromToken: { + address: '0x437cc33344a0b27a429f795ff6b469c72698b291', + decimals: 6, + }, + toToken: { + address: '0x4cbc25559dbbd1272ec5b64c7b5f48a2405e6470', + decimals: 6, + }, + }, + }, +}; + +export class UsualMWrappedM extends Usual { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + usualMIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper, Config[dexKey][network]); + this.usualMIface = new Interface(USUALM_ABI as JsonFragment[]); + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: {}, + side: SwapSide, + ): Promise { + if (this.isFromToken(srcToken) && this.isToToken(destToken)) { + const exchangeData = this.usualMIface.encodeFunctionData( + 'wrap(address, uint256)', + [recipient, srcAmount], + ); + + return { + needWrapNative: false, + dexFuncHasRecipient: true, + exchangeData, + targetExchange: this.config.toToken.address, + returnAmountPos: undefined, + }; + } + + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual-bond/usual-bond.ts b/src/dex/usual/usual.ts similarity index 51% rename from src/dex/usual-bond/usual-bond.ts rename to src/dex/usual/usual.ts index 295dc0db4..b17019e5d 100644 --- a/src/dex/usual-bond/usual-bond.ts +++ b/src/dex/usual/usual.ts @@ -5,47 +5,30 @@ import { PoolPrices, AdapterExchangeParam, Logger, - NumberAsString, - DexExchangeParam, PoolLiquidity, } from '../../types'; import { SwapSide, Network } from '../../constants'; import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; -import { getDexKeysWithNetwork } from '../../utils'; import { IDex } from '../idex'; import { IDexHelper } from '../../dex-helper/idex-helper'; import { UsualBondData, DexParams } from './types'; import { SimpleExchange } from '../simple-exchange'; -import { UsualBondConfig } from './config'; -import { Interface, JsonFragment } from '@ethersproject/abi'; -import USD0PP_ABI from '../../abi/usual-bond/usd0pp.abi.json'; import { BI_POWS } from '../../bigint-constants'; -export class UsualBond extends SimpleExchange implements IDex { - protected config: DexParams; - +export class Usual extends SimpleExchange implements IDex { readonly hasConstantPriceLargeAmounts = true; readonly needWrapNative = false; readonly isFeeOnTransferSupported = false; - public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(UsualBondConfig); - - usd0ppIface: Interface; logger: Logger; constructor( readonly network: Network, readonly dexKey: string, readonly dexHelper: IDexHelper, + readonly config: DexParams, ) { super(dexHelper, dexKey); - const config = UsualBondConfig[dexKey][network]; - this.usd0ppIface = new Interface(USD0PP_ABI as JsonFragment[]); - this.config = { - usd0Address: config.usd0Address.toLowerCase(), - usd0ppAddress: config.usd0ppAddress.toLowerCase(), - }; this.logger = dexHelper.getLogger(dexKey); } @@ -53,20 +36,16 @@ export class UsualBond extends SimpleExchange implements IDex { // No initialization needed for constant price } - getConfig() { - return this.config; + isFromToken(token: string) { + return token.toLowerCase() === this.config.fromToken.address.toLowerCase(); } - is_usd0(token: string) { - return token.toLowerCase() === this.config.usd0Address.toLowerCase(); + isToToken(token: string) { + return token.toLowerCase() === this.config.toToken.address.toLowerCase(); } - is_usd0pp(token: string) { - return token.toLowerCase() === this.config.usd0ppAddress.toLowerCase(); - } - - is_usd0_swap_token(srcToken: string, destToken: string) { - return this.is_usd0(srcToken) && this.is_usd0pp(destToken); + isValidTokens(srcToken: string, destToken: string) { + return this.isFromToken(srcToken) && this.isToToken(destToken); } getAdapters() { @@ -92,8 +71,8 @@ export class UsualBond extends SimpleExchange implements IDex { return []; } - if (this.is_usd0_swap_token(srcTokenAddress, destTokenAddress)) { - return [`${this.dexKey}_${this.config.usd0ppAddress}`]; + if (this.isValidTokens(srcTokenAddress, destTokenAddress)) { + return [`${this.dexKey}_${this.config.toToken}`]; } return []; @@ -111,24 +90,25 @@ export class UsualBond extends SimpleExchange implements IDex { return null; } - const isUSD0SwapToken = this.is_usd0_swap_token( - srcToken.address, - destToken.address, - ); + const isValidSwap = this.isValidTokens(srcToken.address, destToken.address); - if (!isUSD0SwapToken) { + if (!isValidSwap) { return null; } - const unitOut = BI_POWS[18]; // 1:1 swap - const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input + const unitOut = BI_POWS[this.config.toToken.decimals]; // 1:1 swap + const amountsOut = amounts.map( + amount => + (amount * BI_POWS[this.config.toToken.decimals]) / + BI_POWS[this.config.fromToken.decimals], + ); // 1:1 swap, so output amounts are the same as input return [ { unit: unitOut, prices: amountsOut, data: {}, - poolAddresses: [this.config.usd0ppAddress], + poolAddresses: [this.config.toToken.address], exchange: this.dexKey, gasCost: 70000, poolIdentifier: this.dexKey, @@ -151,55 +131,27 @@ export class UsualBond extends SimpleExchange implements IDex { const payload = '0x'; return { - targetExchange: this.config.usd0ppAddress, + targetExchange: this.config.toToken.address, payload, networkFee: '0', }; } - async getDexParam( - srcToken: Address, - destToken: Address, - srcAmount: NumberAsString, - destAmount: NumberAsString, - recipient: Address, - data: UsualBondData, - side: SwapSide, - ): Promise { - if (this.is_usd0(srcToken) && this.is_usd0pp(destToken)) { - const exchangeData = this.usd0ppIface.encodeFunctionData('mint', [ - srcAmount, - ]); - - return { - needWrapNative: false, - dexFuncHasRecipient: false, - exchangeData, - targetExchange: this.config.usd0ppAddress, - returnAmountPos: undefined, - }; - } - throw new Error('LOGIC ERROR'); - } - async getTopPoolsForToken( tokenAddress: Address, limit: number, ): Promise { - const isUsd0 = this.is_usd0(tokenAddress); - if (!isUsd0 && !this.is_usd0pp(tokenAddress)) return []; + const isFromToken = this.isFromToken(tokenAddress); + const isToToken = this.isToToken(tokenAddress); + + if (!(isFromToken || isToToken)) return []; return [ { exchange: this.dexKey, - address: this.config.usd0ppAddress, + address: this.config.toToken.address, connectorTokens: [ - { - decimals: 18, - address: isUsd0 - ? this.config.usd0ppAddress - : this.config.usd0Address, - }, + isFromToken ? this.config.toToken : this.config.fromToken, ], liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred }, diff --git a/src/dex/weth/config.ts b/src/dex/weth/config.ts index 653b9133f..f1208d4cf 100644 --- a/src/dex/weth/config.ts +++ b/src/dex/weth/config.ts @@ -25,6 +25,9 @@ export const WethConfig: DexConfigMap = { [Network.BASE]: { poolGasCost: WethGasCost, }, + [Network.SEPOLIA]: { + poolGasCost: WethGasCost, + }, }, Wbnb: { [Network.BSC]: { @@ -46,6 +49,11 @@ export const WethConfig: DexConfigMap = { poolGasCost: WethGasCost, }, }, + Wxdai: { + [Network.GNOSIS]: { + poolGasCost: WethGasCost, + }, + }, }; export const Adapters: { diff --git a/src/dex/wusdm/config.ts b/src/dex/wusdm/config.ts index 7e7d1733d..d2f1e1de6 100644 --- a/src/dex/wusdm/config.ts +++ b/src/dex/wusdm/config.ts @@ -25,4 +25,12 @@ export const WUSDMConfig: DexConfigMap = { USDMAddress: '0x59D9356E565Ab3A36dD77763Fc0d87fEaf85508C', }, }, + // not really wUSDM, but works in the same way + // might give 1wei difference on BUY + sDAI: { + [Network.GNOSIS]: { + wUSDMAddress: '0xaf204776c7245bF4147c2612BF6e5972Ee483701', // sDAI + USDMAddress: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', // WXDAI + }, + }, }; diff --git a/src/dex/wusdm/wusdm-e2e.test.ts b/src/dex/wusdm/wusdm-e2e.test.ts index 89aad8afb..9c922575b 100644 --- a/src/dex/wusdm/wusdm-e2e.test.ts +++ b/src/dex/wusdm/wusdm-e2e.test.ts @@ -163,4 +163,42 @@ describe('MountainProtocol E2E', () => { tokenBAmount, ); }); + + describe('Gnosis', () => { + const network = Network.GNOSIS; + + const tokenASymbol: string = 'sDAI'; + const tokenBSymbol: string = 'WXDAI'; + + const tokenAAmount: string = '500000000000000000'; + const tokenBAmount: string = '500000000000000000'; + + testForNetwork( + network, + 'sDAI', + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + + describe('Gnosis with Native', () => { + const network = Network.GNOSIS; + + const tokenASymbol: string = 'sDAI'; + const tokenBSymbol: string = 'XDAI'; + + const tokenAAmount: string = '500000000000000000'; + const tokenBAmount: string = '500000000000000000'; + + testForNetwork( + network, + 'sDAI', + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); }); diff --git a/src/dex/wusdm/wusdm-pool.ts b/src/dex/wusdm/wusdm-pool.ts index ad7a87acb..d45dc4c6a 100644 --- a/src/dex/wusdm/wusdm-pool.ts +++ b/src/dex/wusdm/wusdm-pool.ts @@ -87,7 +87,7 @@ export class WusdmEventPool extends StatefulEventSubscriber { ): Promise> { return { totalAssets: state.totalAssets + BigInt(event.args.assets), - totalShares: state.totalAssets + BigInt(event.args.assets), + totalShares: state.totalShares + BigInt(event.args.shares), }; } diff --git a/src/dex/wusdm/wusdm.ts b/src/dex/wusdm/wusdm.ts index 5505e0696..98c0e1682 100644 --- a/src/dex/wusdm/wusdm.ts +++ b/src/dex/wusdm/wusdm.ts @@ -38,6 +38,7 @@ export class WUSDM { readonly hasConstantPriceLargeAmounts = true; readonly isFeeOnTransferSupported = false; + readonly needWrapNative: boolean = true; public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork(WUSDMConfig); @@ -89,7 +90,10 @@ export class WUSDM side: SwapSide, blockNumber: number, ): Promise { - return this.isAppropriatePair(srcToken, destToken) + const _srcToken = this.dexHelper.config.wrapETH(srcToken); + const _destToken = this.dexHelper.config.wrapETH(destToken); + + return this.isAppropriatePair(_srcToken, _destToken) ? [`${this.dexKey}_${this.wUSDMAddress}`] : []; } @@ -121,13 +125,16 @@ export class WUSDM blockNumber: number, limitPools?: string[], ): Promise> { - if (!this.isAppropriatePair(srcToken, destToken)) return null; + const _srcToken = this.dexHelper.config.wrapETH(srcToken); + const _destToken = this.dexHelper.config.wrapETH(destToken); + + if (!this.isAppropriatePair(_srcToken, _destToken)) return null; const state = this.eventPool.getState(blockNumber); if (!state) return null; - const isSrcAsset = this.isUSDM(srcToken.address); + const isSrcAsset = this.isUSDM(_srcToken.address); - const isWrap = this.isWrap(srcToken, destToken, side); + const isWrap = this.isWrap(_srcToken, _destToken, side); let calcFunction: Function; diff --git a/src/executor/Executor01BytecodeBuilder.ts b/src/executor/Executor01BytecodeBuilder.ts index c46a14481..9d6e743b0 100644 --- a/src/executor/Executor01BytecodeBuilder.ts +++ b/src/executor/Executor01BytecodeBuilder.ts @@ -275,6 +275,7 @@ export class Executor01BytecodeBuilder extends ExecutorBytecodeBuilder< curExchangeParam.approveData.target, curExchangeParam.approveData.token, flags.approves[index], + curExchangeParam.permit2Approval, ); swapCallData = hexConcat([approveCallData, swapCallData]); @@ -298,6 +299,7 @@ export class Executor01BytecodeBuilder extends ExecutorBytecodeBuilder< curExchangeParam.approveData.target, curExchangeParam.approveData.token, flags.approves[index], + curExchangeParam.permit2Approval, ); } @@ -377,17 +379,21 @@ export class Executor01BytecodeBuilder extends ExecutorBytecodeBuilder< let fromAmountPos = 0; if (insertFromAmount) { - const fromAmount = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [swap.swapExchanges[swapExchangeIndex].srcAmount], - ); - - const fromAmountIndex = exchangeData - .replace('0x', '') - .indexOf(fromAmount.replace('0x', '')); - - fromAmountPos = - (fromAmountIndex !== -1 ? fromAmountIndex : exchangeData.length) / 2; + if (exchangeParam.insertFromAmountPos) { + fromAmountPos = exchangeParam.insertFromAmountPos; + } else { + const fromAmount = ethers.utils.defaultAbiCoder.encode( + ['uint256'], + [swap.swapExchanges[swapExchangeIndex].srcAmount], + ); + + const fromAmountIndex = exchangeData + .replace('0x', '') + .indexOf(fromAmount.replace('0x', '')); + + fromAmountPos = + (fromAmountIndex !== -1 ? fromAmountIndex : exchangeData.length) / 2; + } } return this.buildCallData( diff --git a/src/executor/Executor02BytecodeBuilder.ts b/src/executor/Executor02BytecodeBuilder.ts index 63e149a65..90cf76be8 100644 --- a/src/executor/Executor02BytecodeBuilder.ts +++ b/src/executor/Executor02BytecodeBuilder.ts @@ -335,16 +335,20 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder< let fromAmountPos = 0; if (insertFromAmount) { - const fromAmount = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [swapExchange.srcAmount], - ); - const fromAmountIndex = exchangeData - .replace('0x', '') - .indexOf(fromAmount.replace('0x', '')); + if (exchangeParam.insertFromAmountPos) { + fromAmountPos = exchangeParam.insertFromAmountPos; + } else { + const fromAmount = ethers.utils.defaultAbiCoder.encode( + ['uint256'], + [swapExchange.srcAmount], + ); + const fromAmountIndex = exchangeData + .replace('0x', '') + .indexOf(fromAmount.replace('0x', '')); - fromAmountPos = - (fromAmountIndex !== -1 ? fromAmountIndex : exchangeData.length) / 2; + fromAmountPos = + (fromAmountIndex !== -1 ? fromAmountIndex : exchangeData.length) / 2; + } } return this.buildCallData( @@ -613,6 +617,7 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder< curExchangeParam.approveData.target, curExchangeParam.approveData.token, flags.approves[exchangeParamIndex], + curExchangeParam.permit2Approval, ); swapExchangeCallData = hexConcat([approveCallData, swapExchangeCallData]); @@ -630,6 +635,7 @@ export class Executor02BytecodeBuilder extends ExecutorBytecodeBuilder< curExchangeParam.approveData.target, curExchangeParam.approveData.token, flags.approves[exchangeParamIndex], + curExchangeParam.permit2Approval, ); } diff --git a/src/executor/Executor03BytecodeBuilder.ts b/src/executor/Executor03BytecodeBuilder.ts index 5ed683903..c9331b80c 100644 --- a/src/executor/Executor03BytecodeBuilder.ts +++ b/src/executor/Executor03BytecodeBuilder.ts @@ -73,6 +73,7 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder< swappedAmountNotPresentInExchangeData, specialDexFlag, specialDexSupportsInsertFromAmount, + sendEthButSupportsInsertFromAmount, } = exchangeParam; const isSpecialDex = specialDexFlag !== undefined && specialDexFlag !== SpecialDex.DEFAULT; @@ -93,9 +94,16 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder< Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP; // 0 if (isEthSrc && !needWrap) { + const preventInsertForSendEth = + forcePreventInsertFromAmount || !sendEthButSupportsInsertFromAmount; + dexFlag = dexFuncHasRecipient - ? Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 9 - : Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 5 + ? preventInsertForSendEth + ? Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 9 + : Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_PLUS_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP // 18 + : preventInsertForSendEth + ? Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP // 5 + : Flag.SEND_ETH_EQUAL_TO_FROM_AMOUNT_PLUS_INSERT_FROM_AMOUNT_CHECK_SRC_TOKEN_BALANCE_AFTER_SWAP; // 14 } else if (isEthDest && !needUnwrap) { dexFlag = forcePreventInsertFromAmount ? Flag.DONT_INSERT_FROM_AMOUNT_CHECK_ETH_BALANCE_AFTER_SWAP // 4 @@ -178,6 +186,7 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder< curExchangeParam.approveData.target, curExchangeParam.approveData.token, flags.approves[index], + curExchangeParam.permit2Approval, ); swapCallData = hexConcat([approveCallData, swapCallData]); @@ -197,6 +206,7 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder< curExchangeParam.approveData.target, curExchangeParam.approveData.token, flags.approves[index], + curExchangeParam.permit2Approval, ); } @@ -321,24 +331,31 @@ export class Executor03BytecodeBuilder extends ExecutorBytecodeBuilder< let fromAmountPos = 0; let toAmountPos = 0; if (insertAmount) { - const fromAmount = ethers.utils.defaultAbiCoder.encode( - ['uint256'], - [swap.swapExchanges[swapExchangeIndex].srcAmount], - ); + if (exchangeParam.insertFromAmountPos) { + fromAmountPos = exchangeParam.insertFromAmountPos; + } else { + const fromAmount = ethers.utils.defaultAbiCoder.encode( + ['uint256'], + [swap.swapExchanges[swapExchangeIndex].srcAmount], + ); + + const fromAmountIndex = exchangeData + .replace('0x', '') + .indexOf(fromAmount.replace('0x', '')); + + fromAmountPos = + (fromAmountIndex !== -1 ? fromAmountIndex : exchangeData.length) / 2; + } + const toAmount = ethers.utils.defaultAbiCoder.encode( ['uint256'], [swap.swapExchanges[swapExchangeIndex].destAmount], ); - const fromAmountIndex = exchangeData - .replace('0x', '') - .indexOf(fromAmount.replace('0x', '')); const toAmountIndex = exchangeData .replace('0x', '') .indexOf(toAmount.replace('0x', '')); - fromAmountPos = - (fromAmountIndex !== -1 ? fromAmountIndex : exchangeData.length) / 2; toAmountPos = (toAmountIndex !== -1 ? toAmountIndex : exchangeData.length) / 2; } diff --git a/src/executor/ExecutorBytecodeBuilder.ts b/src/executor/ExecutorBytecodeBuilder.ts index ce333eabe..e81fc5be1 100644 --- a/src/executor/ExecutorBytecodeBuilder.ts +++ b/src/executor/ExecutorBytecodeBuilder.ts @@ -1,6 +1,7 @@ import { Interface } from '@ethersproject/abi'; import { IDexHelper } from '../dex-helper'; import ERC20ABI from '../abi/erc20.json'; +import Permit2Abi from '../abi/permit2.json'; import { ethers } from 'ethers'; import { Address, @@ -23,14 +24,17 @@ import { DISABLED_MAX_UNIT_APPROVAL_TOKENS, } from './constants'; import { Executors, Flag, SpecialDex } from './types'; -import { MAX_UINT, Network } from '../constants'; +import { MAX_UINT, Network, PERMIT2_ADDRESS } from '../constants'; import { DexExchangeBuildParam, DexExchangeParam } from '../types'; -import { ExecutorDetector } from './ExecutorDetector'; +import { BI_MAX_UINT160, BI_MAX_UINT48 } from '../bigint-constants'; const { utils: { hexlify, hexDataLength, hexConcat, hexZeroPad, solidityPack }, } = ethers; +const MAX_UINT48 = BI_MAX_UINT48.toString(); +const MAX_UINT160 = BI_MAX_UINT160.toString(); + export type SingleSwapCallDataParams = { priceRoute: OptimalRate; exchangeParams: DexExchangeBuildParam[]; @@ -54,9 +58,11 @@ export type DexCallDataParams = { export abstract class ExecutorBytecodeBuilder { type!: Executors; erc20Interface: Interface; + permit2Interface: Interface; constructor(protected dexHelper: IDexHelper) { this.erc20Interface = new Interface(ERC20ABI); + this.permit2Interface = new Interface(Permit2Abi); } protected buildSimpleSwapFlags( @@ -120,8 +126,13 @@ export abstract class ExecutorBytecodeBuilder { spender: string, tokenAddr: Address, flag: Flag, + permit2 = false, amount = MAX_UINT, ): string { + if (permit2) { + return this.buildPermit2CallData(spender, tokenAddr, flag); + } + let approveCalldata = this.erc20Interface.encodeFunctionData('approve', [ spender, amount, @@ -155,6 +166,7 @@ export abstract class ExecutorBytecodeBuilder { spender, tokenAddr, Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP, + false, '0', ), approvalCalldata, @@ -164,6 +176,55 @@ export abstract class ExecutorBytecodeBuilder { return approvalCalldata; } + protected buildPermit2CallData( + spender: string, + tokenAddr: Address, + flag: Flag, + ): string { + // first, give approval for Permit2 on the token contract + // (with this approval, Permit2 contract can invoke safeTransferFrom on the token) + let approveData = this.erc20Interface.encodeFunctionData('approve', [ + PERMIT2_ADDRESS, + MAX_UINT, + ]); + + let approvalCalldata = this.buildCallData( + tokenAddr, + approveData, + 0, + APPROVE_CALLDATA_DEST_TOKEN_POS, + SpecialDex.DEFAULT, + Flag.DONT_INSERT_FROM_AMOUNT_DONT_CHECK_BALANCE_AFTER_SWAP, + ); + + // second, give approval for spender on Permit2 contract + // (with this approval, spender can interact with Permit2 on behalf of the executor) + let permit2Data = this.permit2Interface.encodeFunctionData('approve', [ + tokenAddr, + spender, + MAX_UINT160, + MAX_UINT48, + ]); + + let permit2Calldata = this.buildCallData( + PERMIT2_ADDRESS, + permit2Data, + 0, + APPROVE_CALLDATA_DEST_TOKEN_POS, + SpecialDex.DEFAULT, + flag, + ); + + // as approval given only for MAX_UNIT or 0, no need to use insertFromAmount flag + const checkSrcTokenBalance = flag % 3 === 2; + + if (checkSrcTokenBalance) { + permit2Calldata = hexConcat([permit2Calldata, ZEROS_12_BYTES, tokenAddr]); + } + + return hexConcat([approvalCalldata, permit2Calldata]); + } + protected buildWrapEthCallData( wethAddress: string, depositCallData: string, diff --git a/src/executor/WETHBytecodeBuilder.ts b/src/executor/WETHBytecodeBuilder.ts index 7e2d68106..e53d291b5 100644 --- a/src/executor/WETHBytecodeBuilder.ts +++ b/src/executor/WETHBytecodeBuilder.ts @@ -1,9 +1,9 @@ import { DexExchangeBuildParam } from '../types'; -import { OptimalRate, SwapSide } from '@paraswap/core'; +import { OptimalRate } from '@paraswap/core'; import { isETHAddress } from '../utils'; import { DepositWithdrawReturn } from '../dex/weth/types'; import { WethConfig } from '../dex/weth/config'; -import { Executors, Flag, SpecialDex } from './types'; +import { Executors } from './types'; import { ExecutorBytecodeBuilder } from './ExecutorBytecodeBuilder'; import { Network } from '../constants'; @@ -15,6 +15,7 @@ const SUPPORTED_NETWORKS = [ Network.POLYGON, Network.OPTIMISM, Network.ZKEVM, + Network.GNOSIS, ]; const SUPPORTED_EXCHANGES = Object.keys(WethConfig); diff --git a/src/generic-swap-transaction-builder.ts b/src/generic-swap-transaction-builder.ts index fedb6c7a1..8442d38d5 100644 --- a/src/generic-swap-transaction-builder.ts +++ b/src/generic-swap-transaction-builder.ts @@ -687,7 +687,7 @@ export class GenericSwapTransactionBuilder { ): Promise { const spender = bytecodeBuilder.getAddress(); const tokenTargetMapping: { - params: [token: Address, target: Address]; + params: [token: Address, target: Address, permit2: boolean]; exchangeParamIndex: number; }[] = []; @@ -704,7 +704,11 @@ export class GenericSwapTransactionBuilder { if (approveParams) { tokenTargetMapping.push({ - params: [approveParams.token, approveParams?.target], + params: [ + approveParams.token, + approveParams.target, + !!curExchangeParam.permit2Approval, + ], exchangeParamIndex: currentExchangeParamIndex, }); } diff --git a/src/lib/fetcher/wsFetcher.ts b/src/lib/fetcher/wsFetcher.ts index f42936f29..5fd2acd31 100644 --- a/src/lib/fetcher/wsFetcher.ts +++ b/src/lib/fetcher/wsFetcher.ts @@ -1,10 +1,6 @@ import { Logger } from 'log4js'; import { RequestConfig, Response } from '../../dex-helper/irequest-wrapper'; -import { - connection as WebSocketConnection, - client as WebSocketClient, -} from 'websocket'; - +import WebSocket from 'ws'; export class SkippingRequest { constructor(public message = '') {} } @@ -26,74 +22,77 @@ export type RequestInfoWithHandler = { export class WebSocketFetcher { private requests: RequestInfoWithHandler; + // Time to wait before declaring connection as broken and restarting it + private timeoutInterval: number; + // Time to wait after disconnection before reconnecting + private reconnectDelay: number; + private pingTimeout: NodeJS.Timeout | undefined = undefined; public lastFetchSucceeded: boolean = false; private stop: boolean = true; - private ws: WebSocketClient = new WebSocketClient(); - private connection: WebSocketConnection | null = null; - - constructor(requestsInfo: RequestInfoWithHandler, private logger: Logger) { + private connection: WebSocket | null = null; + + constructor( + requestsInfo: RequestInfoWithHandler, + private logger: Logger, + timeoutInterval: number = 10000, + reconnectDelay: number = 5000, + ) { this.requests = requestsInfo; - this.ws.on('connect', this.connected.bind(this)); - this.ws.on('connectFailed', this.connectFailed.bind(this)); + this.timeoutInterval = timeoutInterval; + this.reconnectDelay = reconnectDelay; } - private connected(connection: WebSocketConnection) { - this.connection = connection; + private connected() { this.logger.info(`Connected to ${this.requests.info.requestOptions.url}`); - this.connection.on('error', this.onError.bind(this)); - this.connection.on('close', this.onClose.bind(this)); - this.connection.on('message', this.onMessage.bind(this)); + this.heartbeat(); } - private connectFailed(error: any) { - this.logger.error(`Connect Error: ${error.toString()}. Reconnecting...`); - // reconnect on errors / failures - setTimeout(() => { - this.startPolling(); - }, 3000); + private heartbeat() { + clearTimeout(this.pingTimeout); + this.pingTimeout = setTimeout(() => { + this.logger.warn('No heartbeat. Terminating Connection...'); + this?.connection?.terminate(); + }, this.timeoutInterval); } private onClose() { - this.logger.info(`Connection closed. Reconnecting...`); - // reconnect on errors / failures - setTimeout(() => { - this.startPolling(); - }, 3000); + this.logger.info(`Connection closed.`); + // Do not reconnect if polling is stopped + if (this.stop) { + clearTimeout(this.pingTimeout); + return; + } + + this.logger.info(`Unexpected closure, Reconnecting...`); + this.reconnectWithDelay(); } private onError(error: any) { this.logger.error( - `Connection Error: ${error.toString()}. Stopping & Reconnecting...`, + `Websocket Error: ${error.toString()}. Stopping & Reconnecting...`, ); - this.stopPolling(); - - // reconnect on errors / failures - setTimeout(() => { - this.startPolling(); - }, 3000); + this?.connection?.terminate(); } - private onMessage(message: any) { - if (message.type === 'utf8') { - const response = JSON.parse(message.utf8Data) as Response; - const reqInfo = this.requests; - const info = reqInfo.info; - const options = reqInfo.info.requestOptions; - this.logger.debug(`(${options.url}) received new data`); - - try { - const parsedData = info.caster(response); - reqInfo.handler(parsedData); - } catch (e) { - this.logger.info(e); - this.logger.info( - `(${options.url}) received incorrect data ${JSON.stringify( - response, - ).replace(/(?:\r\n|\r|\n)/g, ' ')}`, - e, - ); - return; - } + private onMessage(data: WebSocket.RawData) { + this.heartbeat(); + const reqInfo = this.requests; + const info = reqInfo.info; + const options = reqInfo.info.requestOptions; + this.logger.debug(`(${options.url}) received new data`); + + try { + const parsedData = info.caster(data); + reqInfo.handler(parsedData); + } catch (e) { + this.logger.info(e); + this.logger.info( + `(${options.url}) received incorrect data ${JSON.stringify( + data, + ).replace(/(?:\r\n|\r|\n)/g, ' ')}`, + e, + ); + return; } } @@ -110,30 +109,44 @@ export class WebSocketFetcher { this.logger.info( `Connecting to ${this.requests.info.requestOptions.url}...`, ); - this.ws.connect( - this.requests.info.requestOptions.url!, - undefined, - undefined, - { + const ws = new WebSocket(this.requests.info.requestOptions.url!, { + headers: { Authorization: authorization, name: name, }, - ); + }); + + ws.on('open', this.connected.bind(this)); + ws.on('message', this.onMessage.bind(this)); + ws.on('error', this.onError.bind(this)); + ws.on('close', this.onClose.bind(this)); + this.connection = ws; } - startPolling(): void { - this.stop = false; + reconnectWithDelay() { + this.logger.info(`Waiting ${this.reconnectDelay}ms before reconnecting...`); + clearTimeout(this.pingTimeout); + setTimeout(() => { + this.reconnect(); + }, this.reconnectDelay); + } + + reconnect() { + clearTimeout(this.pingTimeout); this.connect(); this.logger.info( `Connection started for ${this.requests.info.requestOptions.url}`, ); } + startPolling(): void { + this.stop = false; + this.reconnect(); + } + stopPolling() { - if (this.connection) { - this.connection.close(); - } this.stop = true; + this.connection?.terminate(); this.logger.info( `Connection stopped for ${this.requests.info.requestOptions.url}`, ); diff --git a/src/stateful-event-subscriber.ts b/src/stateful-event-subscriber.ts index f89d6a11a..3705cc3f2 100644 --- a/src/stateful-event-subscriber.ts +++ b/src/stateful-event-subscriber.ts @@ -89,14 +89,14 @@ export abstract class StatefulEventSubscriber ) { let masterBn: undefined | number = undefined; if (options && options.state) { - this.setState(options.state, blockNumber); + this.setState(options.state, blockNumber, 'initialize_1'); } else if (options && options.forceRegenerate) { // ZkEVM forces to always regenerate state when it is old this.logger.debug( `${this.parentName}: ${this.name}: forced to regenerate state`, ); const state = await this.generateState(blockNumber); - this.setState(state, blockNumber); + this.setState(state, blockNumber, 'initialize_2'); } else { if (this.dexHelper.config.isSlave && this.masterPoolNeeded) { let stateAsString = await this.dexHelper.cache.hget( @@ -135,14 +135,14 @@ export abstract class StatefulEventSubscriber } // set state and the according blockNumber. state.bn can be smaller, greater or equal // to blockNumber - this.setState(state.state, blockNumber); + this.setState(state.state, blockNumber, 'initialize_3'); } else { // if no state found in cache generate new state using rpc this.logger.info( `${this.parentName}: ${this.name}: did not found state on cache generating new one`, ); const state = await this.generateState(blockNumber); - this.setState(state, blockNumber); + this.setState(state, blockNumber, 'initialize_4'); // we should publish only if generateState succeeded const data = this.getPoolIdentifierData(); @@ -161,7 +161,7 @@ export abstract class StatefulEventSubscriber `${this.parentName}: ${this.name}: cache generating state`, ); const state = await this.generateState(blockNumber); - this.setState(state, blockNumber); + this.setState(state, blockNumber, 'initialize_5'); } } @@ -235,7 +235,7 @@ export abstract class StatefulEventSubscriber this.logger.info( `StatefulEventSubscriber_1 restart, bn: ${blockNumber}, state_bn: ${this.stateBlockNumber}: ${this.parentName}: ${this.name}`, ); - this._setState(null, blockNumber); + this._setState(null, blockNumber, 'restart'); } } @@ -275,7 +275,7 @@ export abstract class StatefulEventSubscriber } if (!this.state) { const freshState = await this.generateState(blockNumber); - this.setState(freshState, blockNumber); + this.setState(freshState, blockNumber, 'update_1'); } //Find the last state before the blockNumber of the logs let stateBeforeLog: DeepReadonly | undefined; @@ -291,7 +291,7 @@ export abstract class StatefulEventSubscriber logs.slice(index, indexBlockEnd), blockHeader, ); - if (nextState) this.setState(nextState, blockNumber); + if (nextState) this.setState(nextState, blockNumber, 'update_2'); } lastBlockNumber = blockNumber; index = indexBlockEnd; @@ -315,7 +315,7 @@ export abstract class StatefulEventSubscriber ); try { const state = await this.generateState(latestBlockNumber); - this.setState(state, latestBlockNumber); + this.setState(state, latestBlockNumber, 'update_3'); return true; } catch (e) { this.logger.error( @@ -346,12 +346,12 @@ export abstract class StatefulEventSubscriber } if (lastBn) { - this._setState(this.stateHistory[lastBn], lastBn); + this._setState(this.stateHistory[lastBn], lastBn, 'rollback_1'); } else { this.logger.info( `StatefulEventSubscriber_1 rollback, bn: ${blockNumber}: ${this.parentName}: ${this.name}`, ); - this._setState(null, blockNumber); + this._setState(null, blockNumber, 'rollback_2'); } } else { //Keep the current state in this.state and in the history @@ -390,7 +390,11 @@ export abstract class StatefulEventSubscriber return this.state; } - _setState(state: DeepReadonly | null, blockNumber: number) { + _setState( + state: DeepReadonly | null, + blockNumber: number, + reason?: string, + ) { if ( this.dexHelper.config.isSlave && this.masterPoolNeeded && @@ -418,7 +422,7 @@ export abstract class StatefulEventSubscriber `${this.parentName}: received state from a scheduled job`, 'info', ); - this.setState(state.state, state.bn); + this.setState(state.state, state.bn, 'addBatchHGet'); return true; }, ); @@ -481,14 +485,18 @@ export abstract class StatefulEventSubscriber //no longer needed. If the blockNumber is greater than or equal to the //current state, then the current state will be updated and the invalid flag //can be reset. - setState(state: DeepReadonly, blockNumber: number): void { + setState( + state: DeepReadonly, + blockNumber: number, + reason?: string, + ): void { if (!blockNumber) { this.logger.error('setState() with blockNumber', blockNumber); return; } this.stateHistory[blockNumber] = state; if (!this.state || blockNumber >= this.stateBlockNumber) { - this._setState(state, blockNumber); + this._setState(state, blockNumber, reason); this.invalid = false; } const minBlockNumberToKeep = this.stateBlockNumber - MAX_BLOCKS_HISTORY; diff --git a/src/types.ts b/src/types.ts index 24e94dcaf..843cf67e8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -173,6 +173,8 @@ export type DexExchangeParam = { swappedAmountNotPresentInExchangeData?: boolean; preSwapUnwrapCalldata?: string; returnAmountPos: number | undefined; + insertFromAmountPos?: number; + permit2Approval?: boolean; }; export type DexExchangeParamWithBooleanNeedWrapNative = DexExchangeParam & { @@ -294,7 +296,7 @@ export type Config = { wrappedNativeTokenAddress: Address; hasEIP1559: boolean; augustusAddress: Address; - augustusV6Address?: Address; + augustusV6Address: Address; augustusRFQAddress: Address; tokenTransferProxyAddress: Address; multicallV2Address: Address; diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index d0f06b1b2..744baed67 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -7,6 +7,7 @@ import { allowedFn, _balancesFn, _allowancesFn, + balanceAndBlacklistStatesFn, } from '../tests/smart-tokens'; import { Address } from '../src/types'; import { ETHER_ADDRESS, Network } from '../src/constants'; @@ -91,6 +92,10 @@ export const Tokens: { address: ETHER_ADDRESS, decimals: 18, }, + ETHx: { + address: '0xA35b1B31Ce002FBF2058D22F30f95D405200A15b', + decimals: 18, + }, SWETH: { address: '0xf951e335afb289353dc249e82926178eac7ded78', decimals: 18, @@ -126,6 +131,10 @@ export const Tokens: { addBalance: balancesFn, addAllowance: allowedFn, }, + INST: { + address: '0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb', + decimals: 18, + }, aEthUSDC: { address: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', decimals: 6, @@ -545,6 +554,21 @@ export const Tokens: { decimals: 18, symbol: 'USD0++', }, + wUSDL: { + address: '0x7751e2f4b8ae93ef6b79d86419d42fe3295a4559', + decimals: 18, + symbol: 'wUSDL', + }, + WrappedM: { + address: '0x437cc33344a0B27A429f795ff6B469C72698B291', + decimals: 6, + symbol: 'wM', + }, + UsualM: { + address: '0x4cbc25559dbbd1272ec5b64c7b5f48a2405e6470', + decimals: 6, + symbol: 'USUALM', + }, }, [Network.POLYGON]: { jGBP: { @@ -1177,6 +1201,12 @@ export const Tokens: { address: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', decimals: 6, symbol: 'USDC', + addBalance: balanceAndBlacklistStatesFn, + addAllowance: allowedFn, + }, + SUSHI: { + address: '0xd4d42F0b6DEF4CE0383636770eF773390d85c61A', + decimals: 18, }, crvUSD: { address: '0x498bf2b1e120fed3ad3d42ea2165e9b73f99c1e5', @@ -1319,6 +1349,8 @@ export const Tokens: { WETH: { address: '0x4200000000000000000000000000000000000006', decimals: 18, + addBalance: balanceOfFn, + addAllowance: allowanceFn, }, ETH: { address: ETHER_ADDRESS, decimals: 18 }, USDCe: { @@ -1450,6 +1482,84 @@ export const Tokens: { decimals: 6, }, }, + [Network.GNOSIS]: { + XDAI: { + address: ETHER_ADDRESS, + decimals: 18, + }, + WETH: { + address: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1', + decimals: 18, + }, + WBTC: { + address: '0x8e5bBbb09Ed1ebdE8674Cda39A0c169401db4252', + decimals: 8, + }, + USDC: { + address: '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83', + decimals: 6, + }, + USDCe: { + address: '0x2a22f9c3b484c3629090feed35f17ff8f88f76f0', + decimals: 6, + }, + USDT: { + address: '0x4ECaBa5870353805a9F068101A40E0f32ed605C6', + decimals: 6, + }, + COW: { + address: '0x177127622c4A00F3d409B75571e12cB3c8973d3c', + decimals: 18, + }, + WXDAI: { + address: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d', + decimals: 18, + }, + aGnoWETH: { + address: '0xa818f1b57c201e092c4a2017a91815034326efd1', + decimals: 18, + }, + waGnoWETH: { + address: '0x57f664882F762FA37903FC864e2B633D384B411A', + decimals: 18, + }, + aGnowstETH: { + address: '0x23e4e76d01b2002be436ce8d6044b0aa2f68b68a', + decimals: 18, + }, + waGnowstETH: { + address: '0x773CDA0CADe2A3d86E6D4e30699d40bB95174ff2', + decimals: 18, + }, + aGnoUSDC: { + address: '0xc6b7aca6de8a6044e0e32d0c841a89244a10d284', + decimals: 6, + }, + GNO: { + address: '0x9c58bacc331c9aa871afd802db6379a98e80cedb', + decimals: 18, + }, + wstETH: { + address: '0x6C76971f98945AE98dD7d4DFcA8711ebea946eA6', + decimals: 18, + }, + aGnoWXDAI: { + address: '0xd0dd6cef72143e22cced4867eb0d5f2328715533', + decimals: 18, + }, + sDAI: { + address: '0xaf204776c7245bF4147c2612BF6e5972Ee483701', + decimals: 18, + }, + crvUSD: { + address: '0xaBEf652195F98A91E490f047A5006B71c85f058d', + decimals: 18, + }, + SWPR: { + address: '0x532801ED6f82FFfD2DAB70A19fC2d7B2772C4f4b', + decimals: 18, + }, + }, [Network.BASE]: { wstETH: { address: `0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452`, @@ -1548,6 +1658,41 @@ export const Tokens: { decimals: 6, }, }, + [Network.SEPOLIA]: { + ETH: { address: ETHER_ADDRESS, decimals: 18 }, + WETH: { + address: '0x7b79995e5f793a07bc00c21412e50ecae098e7f9', + decimals: 18, + }, + bal: { + address: `0xb19382073c7a0addbb56ac6af1808fa49e377b75`, + decimals: 18, + }, + daiAave: { + address: `0xff34b3d4aee8ddcd6f9afffb6fe49bd371b8a357`, + decimals: 18, + }, + usdcAave: { + address: `0x94a9d9ac8a22534e3faca9f4e7f2e2cf85d5e4c8`, + decimals: 6, + }, + usdtAave: { + address: `0xaa8e23fb1079ea71e0a56f48a2aa51851d8433d0`, + decimals: 6, + }, + aDaiAave: { + address: `0xde46e43f46ff74a23a65ebb0580cbe3dfe684a17`, + decimals: 18, + }, + stataUSDC: { + address: `0x8a88124522dbbf1e56352ba3de1d9f78c143751e`, + decimals: 6, + }, + stataUSDT: { + address: `0x978206fae13faf5a8d293fb614326b237684b750`, + decimals: 6, + }, + }, }; export const Holders: { @@ -1584,7 +1729,7 @@ export const Holders: { BADGER: '0x34e2741a3f8483dbe5231f61c005110ff4b9f50a', STETH: '0x6663613FbD927cE78abBF7F5Ca7e2c3FE0d96d18', SUSHI: '0x8a108e4761386c94b8d2f98A5fFe13E472cFE76a', - wstETH: '0x5fEC2f34D80ED82370F733043B6A536d7e9D7f8d', + wstETH: '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', WETH: '0x6B44ba0a126a2A1a8aa6cD1AdeeD002e141Bcd44', USDT: '0xAf64555DDD61FcF7D094824dd9B4eBea165aFc5b', XAUT: '0xc4e161e8d8a4bc4ac762ab33a28bbac5474203d7', @@ -1661,10 +1806,12 @@ export const Holders: { weETH: '0x267ed5f71EE47D3E45Bb1569Aa37889a2d10f91e', rUSD: '0xEC2eda1C4F981E468ABF62424a10B69B738b498E', arUSD: '0xeFc24206053a452e2299BF3b8f964512b041Db4C', - USD0: '0x6A5d5Af0E266a24648a9d7E8D388EAEc7AbD8433', + USD0: '0x224762e69169E425239EeEE0012d1B0e041C123D', + WrappedM: '0xE0663f2372cAa1459b7ade90812Dc737CE587FA6', 'USD0++': '0x2227b6806339906707b43F36a1f07B52FF7Fa776', USDM: '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812', wUSDM: '0x3B95bC951EE0f553ba487327278cAc44f29715E5', + UsualM: '0xE3f7A0c4a44b740328157A5152A85c3bCB54DA09', }, [Network.POLYGON]: { jGBP: '0x02aa0B826c7BA6386DdBE04C0a8715A1c0A16B24', @@ -1777,7 +1924,7 @@ export const Holders: { BETS: '0x8cc2284c90d05578633418f9cde104f402375a65', HATCHY: '0x14ec295ec8def851ec6e2959df872dd24e422631', USDCe: '0x3a2434c698f8d79af1f5a9e43013157ca8b11a66', - USDC: '0xcc2da711D621A4491b338CAC88B9C0954db3e75B', + USDC: '0x64b4dE1b00EF830f3CC2FD68ee056aAD76C45BF6', USDTe: '0x84d34f4f83a87596cd3fb6887cff8f17bf5a7b83', WETHe: '0x9bdB521a97E95177BF252C253E256A60C3e14447', POPS: '0x5268c2331658cb0b2858cfa9db27d8f22f5434bc', @@ -1793,7 +1940,7 @@ export const Holders: { TSD: '0x691A89db352B72dDb249bFe16503494eC0D920A4', THO: '0xc40d16c47394a506d451475c8a7c46c1175c1da1', aAvaUSDT: '0x50B1Ba98Cf117c9682048D56628B294ebbAA4ec2', - USDT: '0x0d0707963952f2fba59dd06f2b425ace40b492fe', + USDT: '0xCddc5d0Ebeb71a08ffF26909AA6c0d4e256b4fE1', aAvaWAVAX: '0x1B18Df70863636AEe4BfBAb6F7C70ceBCA9bA404', oldFRAX: '0x4e3376018add04ebe4c46bf6f924ddec8c67aa7b', newFRAX: '0x4e3376018add04ebe4c46bf6f924ddec8c67aa7b', @@ -1836,7 +1983,7 @@ export const Holders: { LINK: '0x7f1fa204bb700853d36994da19f830b6ad18455c', DMT: '0x40414f138eb2ef938e6c3629897ef99d4464d4e8', PENDLE: '0x5bdf85216ec1e38d6458c870992a69e38e03f7ef', - wstETH: '0x27edc7700f1820cb38ec3bbb84c542945f21b5a1', + wstETH: '0x3c22ec75ea5D745c78fc84762F7F1E6D82a2c5BF', EURA: '0x6dd7b830896b56812aa667bdd14b71c8b3252f8e', stEUR: '0xE588611e7A2392507879E3be80531654b85C16aA', USDA: '0xa86ff337db9107b54862d30d1a598f8be847b05e', @@ -1887,6 +2034,26 @@ export const Holders: { WBTC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', USDC: '0x99b31498b0a1dae01fc3433e3cb60f095340935c', }, + [Network.GNOSIS]: { + GNO: '0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f', + COW: '0x4fFAD6ac852c0Af0AA301376F4C5Dea3a928b120', + XDAI: '0x9fc062032d4F2Fe7dAA601bd8B06C45F9c8f17Be', + WXDAI: '0xBA12222222228d8Ba445958a75a0704d566BF2C8', + WETH: '0x800e12aF6c96790EDDdc5B3f3302899e27B2A918', + USDT: '0x1098503a90c3224F0e6BE7c124a337888C0BA564', + WBTC: '0x8e5bBbb09Ed1ebdE8674Cda39A0c169401db4252', + USDC: '0xd4A39d219ADB43aB00739DC5D876D98Fdf0121Bf', + aGnoWXDAI: '0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f', + aGnoUSDC: '0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f', + wstETH: '0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f', + aGnowstETH: '0x458cD345B4C05e8DF39d0A07220feb4Ec19F5e6f', + sDAI: '0x79f08F2e75A8C99428DE4A2e6456c07C99E55da5', + USDCe: '0x555CE236C0220695b68341bc48C68d52210cC35b', + crvUSD: '0xE4A982fa1f1E8AD1AF238A7b1226b13b56bf5CcD', + SWPR: '0x9467dcFD4519287e3878C018c02f5670465a9003', + waGnoWETH: '0x854B004700885A61107B458f11eCC169A019b764', + waGnowstETH: '0x854B004700885A61107B458f11eCC169A019b764', + }, [Network.BASE]: { WETH: '0x4bb6b2efe7036020ba6f02a05602546c9f25bf28', PRIME: '0xe3879b7359695f802d6FD56Bb76fD82C362Dafd6', @@ -1913,6 +2080,15 @@ export const Holders: { cbETH: '0x50e011dD1e2b4906F1534623cD134B30422bb11E', wUSDM: '0xe30965Acd0Ee1CE2e0Cd0AcBFB3596bD6fC78A51', }, + [Network.SEPOLIA]: { + bal: '0xDb4ff41B4C1222c2b1869A67Be115070688989a2', + daiAave: '0xbB0bc84687fFb642fd90a3D12215e7eC16352A49', + WETH: '0x546e37DAA15cdb82fd1a717E5dEEa4AF08D4349A', + ETH: '0x2CdA41645F2dBffB852a605E92B185501801FC28', + stataUSDC: '0x75D06bae37a9c349142fE7cee77804900b1C0EC3', + stataUSDT: '0x75D06bae37a9c349142fE7cee77804900b1C0EC3', + usdcAave: '0xdD5De55eA6804EFb283f43b0C091C25000a6486c', + }, }; export const SmartTokens = Object.keys(Tokens).reduce((acc, _network) => { @@ -1938,6 +2114,8 @@ export const NativeTokenSymbols: { [network: number]: string } = { [Network.ARBITRUM]: 'ETH', [Network.OPTIMISM]: 'ETH', [Network.BASE]: 'ETH', + [Network.SEPOLIA]: 'ETH', + [Network.GNOSIS]: 'XDAI', }; export const WrappedNativeTokenSymbols: { [network: number]: string } = { @@ -1949,4 +2127,6 @@ export const WrappedNativeTokenSymbols: { [network: number]: string } = { [Network.ARBITRUM]: 'WETH', [Network.OPTIMISM]: 'WETH', [Network.BASE]: 'WETH', + [Network.SEPOLIA]: 'WETH', + [Network.GNOSIS]: 'WXDAI', }; diff --git a/tests/smart-tokens.ts b/tests/smart-tokens.ts index aec340c33..5398e0e46 100644 --- a/tests/smart-tokens.ts +++ b/tests/smart-tokens.ts @@ -43,6 +43,9 @@ const constructAddBAllowanceFn = (varName: string): AddAllowanceFn => { export const balanceOfFn = constructAddBalanceFn('balanceOf'); export const balancesFn = constructAddBalanceFn('balances'); +export const balanceAndBlacklistStatesFn = constructAddBalanceFn( + 'balanceAndBlacklistStates', +); export const _balancesFn = constructAddBalanceFn('_balances'); export const allowanceFn = constructAddBAllowanceFn('allowance'); export const _allowancesFn = constructAddBAllowanceFn('_allowances'); diff --git a/tests/utils-e2e.ts b/tests/utils-e2e.ts index 7c3770560..a0343d057 100644 --- a/tests/utils-e2e.ts +++ b/tests/utils-e2e.ts @@ -397,15 +397,18 @@ export async function testE2E( await ts.setup(); if (srcToken.address.toLowerCase() !== ETHER_ADDRESS.toLowerCase()) { - const allowanceTx = await ts.simulate( - allowTokenTransferProxyParams(srcToken.address, senderAddress, network), - ); + // check if v5 is available in the config + if (generateConfig(network).tokenTransferProxyAddress !== NULL_ADDRESS) { + const allowanceTx = await ts.simulate( + allowTokenTransferProxyParams(srcToken.address, senderAddress, network), + ); + if (!allowanceTx.success) console.log(allowanceTx.url); + expect(allowanceTx!.success).toEqual(true); + } const augustusV6Allowance = await ts.simulate( allowAugustusV6(srcToken.address, senderAddress, network), ); - if (!allowanceTx.success) console.log(allowanceTx.url); if (!augustusV6Allowance.success) console.log(augustusV6Allowance.url); - expect(allowanceTx!.success).toEqual(true); expect(augustusV6Allowance!.success).toEqual(true); } @@ -777,7 +780,9 @@ export async function newTestE2E({ .addBalance(senderAddress, twiceAmount.toString()) .addAllowance( senderAddress, - config.tokenTransferProxyAddress, + priceRoute.version === ParaSwapVersion.V5 + ? config.tokenTransferProxyAddress + : config.augustusV6Address, amount.toString(), ); } else { @@ -785,7 +790,9 @@ export async function newTestE2E({ .addBalance(senderAddress, MAX_UINT) .addAllowance( senderAddress, - config.tokenTransferProxyAddress, + priceRoute.version === ParaSwapVersion.V5 + ? config.tokenTransferProxyAddress + : config.augustusV6Address, (BigInt(MAX_UINT) / 8n).toString(), ); } diff --git a/tsconfig.json b/tsconfig.json index 6318f0667..2055cd3c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "allowSyntheticDefaultImports": true, - "lib": ["es2019"], + "lib": ["ESNext"], "module": "commonjs", "noImplicitAny": true, "outDir": "build", @@ -12,7 +12,7 @@ "sourceMap": true, "declaration": true, "skipLibCheck": true, - "target": "es2020", + "target": "ESNext", "resolveJsonModule": true, "allowJs": true, "typeRoots": ["node_modules/@types"], diff --git a/yarn.lock b/yarn.lock index ac8694f8c..ba5b0cc5c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -348,6 +348,11 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@balancer-labs/balancer-maths@^0.0.19": + version "0.0.19" + resolved "https://registry.yarnpkg.com/@balancer-labs/balancer-maths/-/balancer-maths-0.0.19.tgz#d3b92c4b17caca67894806bd7768af1ea31cb956" + integrity sha512-x8t17q8fbXYZJSaidr4BWBg3O9jVnF6xoAsoMWWyUd6AnaxPyFnxAGs76+iQln4WEmC7S5G5uufWJ0OefvcqXQ== + "@balancer-labs/sor@4.1.1-beta.4": version "4.1.1-beta.4" resolved "https://registry.yarnpkg.com/@balancer-labs/sor/-/sor-4.1.1-beta.4.tgz#9369435f8bbc781e9048f33aa496a484631b56e9" @@ -379,7 +384,19 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/eslint-utils@^4.4.0": + version "4.4.1" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz#d1145bf2c20132d6400495d6df4bf59362fd9d56" + integrity sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA== + dependencies: + eslint-visitor-keys "^3.4.3" + +"@eslint-community/regexpp@^4.10.0": + version "4.12.1" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.12.1.tgz#cfc6cffe39df390a3841cde2abccf92eaa7ae0e0" + integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ== + +"@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== @@ -1233,6 +1250,59 @@ bignumber.js "^9.0.2" ts-essentials "^9.1.2" +"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" + integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== + +"@protobufjs/base64@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" + integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== + +"@protobufjs/codegen@^2.0.4": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" + integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== + +"@protobufjs/eventemitter@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" + integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== + +"@protobufjs/fetch@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" + integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== + dependencies: + "@protobufjs/aspromise" "^1.1.1" + "@protobufjs/inquire" "^1.1.0" + +"@protobufjs/float@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" + integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== + +"@protobufjs/inquire@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" + integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== + +"@protobufjs/path@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" + integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== + +"@protobufjs/pool@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" + integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== + +"@protobufjs/utf8@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" + integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== + "@sideway/address@^4.1.4": version "4.1.4" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.4.tgz#03dccebc6ea47fdc226f7d3d1ad512955d4783f0" @@ -1496,11 +1566,6 @@ jest-diff "^26.0.0" pretty-format "^26.0.0" -"@types/json-schema@^7.0.9": - version "7.0.15" - resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" - integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== - "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -1540,15 +1605,24 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== +"@types/node@>=13.7.0": + version "22.8.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.8.5.tgz#a978fb6755dbb32889b0bc3e37554dbc04f261a6" + integrity sha512-5iYk6AMPtsMbkZqCO1UGF9W5L38twq11S2pYWkybGHH2ogPUvXWNlQqJBzuEZWKj/WRH+QTeiv6ySWqJtvIEgA== + dependencies: + undici-types "~6.19.8" + "@types/node@^12.12.54", "@types/node@^12.12.6": version "12.20.55" resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.55.tgz#c329cbd434c42164f846b909bd6f85b5537f6240" integrity sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ== -"@types/node@^17.0.21": - version "17.0.45" - resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190" - integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw== +"@types/node@^20": + version "20.17.9" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.17.9.tgz#5f141d4b7ee125cdee5faefe28de095398865bab" + integrity sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw== + dependencies: + undici-types "~6.19.2" "@types/pbkdf2@^3.0.0": version "3.1.2" @@ -1600,11 +1674,6 @@ dependencies: "@types/node" "*" -"@types/semver@^7.3.12": - version "7.5.6" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" - integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== - "@types/send@*": version "0.17.4" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" @@ -1651,7 +1720,7 @@ dependencies: "@types/node" "*" -"@types/ws@^8.2.2": +"@types/ws@^8.2.2", "@types/ws@^8.5.12": version "8.5.12" resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.12.tgz#619475fe98f35ccca2a2f6c137702d85ec247b7e" integrity sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ== @@ -1677,89 +1746,86 @@ dependencies: "@types/yargs-parser" "*" -"@typescript-eslint/eslint-plugin@^5.47.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" - integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== +"@typescript-eslint/eslint-plugin@^8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz#2ee073c421f4e81e02d10e731241664b6253b23c" + integrity sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w== dependencies: - "@eslint-community/regexpp" "^4.4.0" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/type-utils" "5.62.0" - "@typescript-eslint/utils" "5.62.0" - debug "^4.3.4" + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/type-utils" "8.17.0" + "@typescript-eslint/utils" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" graphemer "^1.4.0" - ignore "^5.2.0" - natural-compare-lite "^1.4.0" - semver "^7.3.7" - tsutils "^3.21.0" - -"@typescript-eslint/parser@^5.47.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" - integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== - dependencies: - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" + ignore "^5.3.1" + natural-compare "^1.4.0" + ts-api-utils "^1.3.0" + +"@typescript-eslint/parser@^8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.17.0.tgz#2ee972bb12fa69ac625b85813dc8d9a5a053ff52" + integrity sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg== + dependencies: + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" - integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== +"@typescript-eslint/scope-manager@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz#a3f49bf3d4d27ff8d6b2ea099ba465ef4dbcaa3a" + integrity sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" -"@typescript-eslint/type-utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" - integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== +"@typescript-eslint/type-utils@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz#d326569f498cdd0edf58d5bb6030b4ad914e63d3" + integrity sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw== dependencies: - "@typescript-eslint/typescript-estree" "5.62.0" - "@typescript-eslint/utils" "5.62.0" + "@typescript-eslint/typescript-estree" "8.17.0" + "@typescript-eslint/utils" "8.17.0" debug "^4.3.4" - tsutils "^3.21.0" + ts-api-utils "^1.3.0" -"@typescript-eslint/types@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" - integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== +"@typescript-eslint/types@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.17.0.tgz#ef84c709ef8324e766878834970bea9a7e3b72cf" + integrity sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA== -"@typescript-eslint/typescript-estree@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" - integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== +"@typescript-eslint/typescript-estree@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz#40b5903bc929b1e8dd9c77db3cb52cfb199a2a34" + integrity sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw== dependencies: - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/visitor-keys" "8.17.0" debug "^4.3.4" - globby "^11.1.0" + fast-glob "^3.3.2" is-glob "^4.0.3" - semver "^7.3.7" - tsutils "^3.21.0" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" -"@typescript-eslint/utils@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" - integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== +"@typescript-eslint/utils@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.17.0.tgz#41c05105a2b6ab7592f513d2eeb2c2c0236d8908" + integrity sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w== dependencies: - "@eslint-community/eslint-utils" "^4.2.0" - "@types/json-schema" "^7.0.9" - "@types/semver" "^7.3.12" - "@typescript-eslint/scope-manager" "5.62.0" - "@typescript-eslint/types" "5.62.0" - "@typescript-eslint/typescript-estree" "5.62.0" - eslint-scope "^5.1.1" - semver "^7.3.7" - -"@typescript-eslint/visitor-keys@5.62.0": - version "5.62.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" - integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== - dependencies: - "@typescript-eslint/types" "5.62.0" - eslint-visitor-keys "^3.3.0" + "@eslint-community/eslint-utils" "^4.4.0" + "@typescript-eslint/scope-manager" "8.17.0" + "@typescript-eslint/types" "8.17.0" + "@typescript-eslint/typescript-estree" "8.17.0" + +"@typescript-eslint/visitor-keys@8.17.0": + version "8.17.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz#4dbcd0e28b9bf951f4293805bf34f98df45e1aa8" + integrity sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg== + dependencies: + "@typescript-eslint/types" "8.17.0" + eslint-visitor-keys "^4.2.0" "@ungap/structured-clone@^1.2.0": version "1.2.0" @@ -1909,11 +1975,6 @@ array-includes@^3.1.7: get-intrinsic "^1.2.1" is-string "^1.0.7" -array-union@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" - integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== - array.prototype.findlastindex@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz#b37598438f97b579166940814e2c0493a4f50207" @@ -1985,6 +2046,11 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== +async@^3.2.3: + version "3.2.6" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.6.tgz#1b0728e14929d51b85b449b7f06e27c1145e38ce" + integrity sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA== + async@^3.2.4: version "3.2.5" resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" @@ -2223,6 +2289,13 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" +brace-expansion@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" + integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== + dependencies: + balanced-match "^1.0.0" + braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -2299,7 +2372,7 @@ browserslist@^4.22.2: node-releases "^2.0.14" update-browserslist-db "^1.0.13" -bs-logger@0.x: +bs-logger@^0.2.6: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog== @@ -2463,7 +2536,7 @@ chalk@^2.3.0, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^4.0.0: +chalk@^4.0.0, chalk@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== @@ -2930,13 +3003,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" - integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== - dependencies: - path-type "^4.0.0" - doctrine@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" @@ -2987,6 +3053,13 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== +ejs@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" + integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== + dependencies: + jake "^10.8.5" + electron-to-chromium@^1.4.601: version "1.4.643" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.643.tgz#081a20c5534db91e66ef094f68624960f674768f" @@ -3120,16 +3193,6 @@ es5-ext@^0.10.35, es5-ext@^0.10.50: es6-symbol "^3.1.3" next-tick "^1.1.0" -es5-ext@^0.10.62, es5-ext@^0.10.63, es5-ext@~0.10.14: - version "0.10.64" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" - integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== - dependencies: - es6-iterator "^2.0.3" - es6-symbol "^3.1.3" - esniff "^2.0.1" - next-tick "^1.1.0" - es6-iterator@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" @@ -3238,14 +3301,6 @@ eslint-plugin-import@^2.26.0: semver "^6.3.1" tsconfig-paths "^3.15.0" -eslint-scope@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" - integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== - dependencies: - esrecurse "^4.3.0" - estraverse "^4.1.1" - eslint-scope@^7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" @@ -3259,6 +3314,11 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== +eslint-visitor-keys@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz#687bacb2af884fcdda8a6e7d65c606f46a14cd45" + integrity sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw== + eslint@^8.30.0: version "8.56.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.56.0.tgz#4957ce8da409dc0809f99ab07a1b94832ab74b15" @@ -3303,16 +3363,6 @@ eslint@^8.30.0: strip-ansi "^6.0.1" text-table "^0.2.0" -esniff@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" - integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== - dependencies: - d "^1.0.1" - es5-ext "^0.10.62" - event-emitter "^0.3.5" - type "^2.7.2" - espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" @@ -3341,11 +3391,6 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1: - version "4.3.0" - resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" - integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== - estraverse@^5.1.0, estraverse@^5.2.0: version "5.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" @@ -3530,14 +3575,6 @@ ethjs-util@^0.1.3: is-hex-prefixed "1.0.0" strip-hex-prefix "1.0.0" -event-emitter@^0.3.5: - version "0.3.5" - resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" - integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== - dependencies: - d "1" - es5-ext "~0.10.14" - eventemitter3@4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" @@ -3671,7 +3708,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== -fast-glob@^3.2.9: +fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -3723,6 +3760,13 @@ file-uri-to-path@1.0.0: resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +filelist@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" + integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== + dependencies: + minimatch "^5.0.1" + fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -3985,18 +4029,6 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" @@ -4245,6 +4277,11 @@ ignore@^5.2.0, ignore@^5.3.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== +ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== + import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -4549,6 +4586,16 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jake@^10.8.5: + version "10.9.2" + resolved "https://registry.yarnpkg.com/jake/-/jake-10.9.2.tgz#6ae487e6a69afec3a5e167628996b59f35ae2b7f" + integrity sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA== + dependencies: + async "^3.2.3" + chalk "^4.0.2" + filelist "^1.0.4" + minimatch "^3.1.2" + jayson@^4.1.1: version "4.1.2" resolved "https://registry.yarnpkg.com/jayson/-/jayson-4.1.2.tgz#443c26a8658703e0b2e881117b09395d88b6982e" @@ -5130,7 +5177,7 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" -lodash.memoize@4.x: +lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag== @@ -5156,6 +5203,11 @@ log4js@6.6.1: rfdc "^1.3.0" streamroller "^3.1.2" +long@^5.0.0: + version "5.2.3" + resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" + integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== + lower-case@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28" @@ -5194,7 +5246,7 @@ make-dir@^4.0.0: dependencies: semver "^7.5.3" -make-error@1.x, make-error@^1.1.1: +make-error@^1.1.1, make-error@^1.3.6: version "1.3.6" resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== @@ -5230,7 +5282,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -merge2@^1.3.0, merge2@^1.4.1: +merge2@^1.3.0: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== @@ -5312,6 +5364,20 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" +minimatch@^5.0.1: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== + dependencies: + brace-expansion "^2.0.1" + +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" @@ -5421,11 +5487,6 @@ nano-json-stream-parser@^0.1.2: resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" integrity sha512-9MqxMH/BSJC7dnLsEMPyfN5Dvoo49IsPFYMcHw3Bcfc2kN0lpHRBSzlMSVx4HGyJ7s9B31CyBTVehWJoQ8Ctew== -natural-compare-lite@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" - integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5758,11 +5819,6 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== -path-type@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" - integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== - pbkdf2@^3.0.17, pbkdf2@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" @@ -5871,6 +5927,24 @@ prompts@^2.0.1: kleur "^3.0.3" sisteransi "^1.0.5" +protobufjs@^7.4.0: + version "7.4.0" + resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.4.0.tgz#7efe324ce9b3b61c82aae5de810d287bc08a248a" + integrity sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw== + dependencies: + "@protobufjs/aspromise" "^1.1.2" + "@protobufjs/base64" "^1.1.2" + "@protobufjs/codegen" "^2.0.4" + "@protobufjs/eventemitter" "^1.1.0" + "@protobufjs/fetch" "^1.1.0" + "@protobufjs/float" "^1.0.2" + "@protobufjs/inquire" "^1.1.0" + "@protobufjs/path" "^1.1.2" + "@protobufjs/pool" "^1.1.0" + "@protobufjs/utf8" "^1.1.0" + "@types/node" ">=13.7.0" + long "^5.0.0" + proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" @@ -6222,13 +6296,18 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.5.3, semver@^7.5.4: +semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== dependencies: lru-cache "^6.0.0" +semver@^7.6.0, semver@^7.6.3: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -6653,6 +6732,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +ts-api-utils@^1.3.0: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.4.3.tgz#bfc2215fe6528fecab2b0fba570a2e8a4263b064" + integrity sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw== + ts-essentials@9.1.2: version "9.1.2" resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.1.2.tgz#46db6944b73b4cd603f3d959ef1123c16ba56f59" @@ -6663,21 +6747,22 @@ ts-essentials@^9.1.2: resolved "https://registry.yarnpkg.com/ts-essentials/-/ts-essentials-9.4.1.tgz#6a6b6f81c2138008a5eef216e9fa468d8d9e2ab4" integrity sha512-oke0rI2EN9pzHsesdmrOrnqv1eQODmJpd/noJjwj2ZPC3Z4N2wbjrOEqnsEgmvlO2+4fBb0a794DCna2elEVIQ== -ts-jest@^29.0.3: - version "29.1.2" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.1.2.tgz#7613d8c81c43c8cb312c6904027257e814c40e09" - integrity sha512-br6GJoH/WUX4pu7FbZXuWGKGNDuU7b8Uj77g/Sp7puZV6EXzuByl6JrECvm0MzVzSTkSHWTihsXt+5XYER5b+g== +ts-jest@^29.2.5: + version "29.2.5" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-29.2.5.tgz#591a3c108e1f5ebd013d3152142cb5472b399d63" + integrity sha512-KD8zB2aAZrcKIdGk4OwpJggeLcH1FgrICqDSROWqlnJXGCXK4Mn6FcdK2B6670Xr73lHMG1kHw8R87A0ecZ+vA== dependencies: - bs-logger "0.x" - fast-json-stable-stringify "2.x" + bs-logger "^0.2.6" + ejs "^3.1.10" + fast-json-stable-stringify "^2.1.0" jest-util "^29.0.0" json5 "^2.2.3" - lodash.memoize "4.x" - make-error "1.x" - semver "^7.5.3" - yargs-parser "^21.0.1" + lodash.memoize "^4.1.2" + make-error "^1.3.6" + semver "^7.6.3" + yargs-parser "^21.1.1" -ts-node@^10.6.0: +ts-node@^10.9.2: version "10.9.2" resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== @@ -6711,11 +6796,6 @@ tslib@2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tslib@^1.8.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" - integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== - tslib@^2.0.3, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" @@ -6726,13 +6806,6 @@ tslib@^2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -6831,10 +6904,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@4.6.4: - version "4.6.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" - integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== +typescript@^5.7.2: + version "5.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== ultron@~1.1.0: version "1.1.1" @@ -6861,6 +6934,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2, undici-types@~6.19.8: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" @@ -7267,18 +7345,6 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== -websocket@1.0.35: - version "1.0.35" - resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.35.tgz#374197207d7d4cc4c36cbf8a1bb886ee52a07885" - integrity sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q== - dependencies: - bufferutil "^4.0.1" - debug "^2.2.0" - es5-ext "^0.10.63" - typedarray-to-buffer "^3.1.5" - utf-8-validate "^5.0.2" - yaeti "^0.0.6" - websocket@^1.0.32: version "1.0.34" resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.34.tgz#2bdc2602c08bf2c82253b730655c0ef7dcab3111" @@ -7379,7 +7445,7 @@ ws@^7.5.10: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== -ws@^8.5.0: +ws@^8.18.0, ws@^8.5.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -7451,7 +7517,7 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yargs-parser@^21.0.1, yargs-parser@^21.1.1: +yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==