From 8fb0c12ba0460ac5c34425bf28d48b3f36265e20 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:07:34 +0530 Subject: [PATCH 01/18] feat: add stader-ethx integration --- src/abi/SSPM.json | 829 +++++++++++ src/abi/StaderOracle.json | 2170 +++++++++++++++++++++++++++++ src/dex/index.ts | 2 + src/dex/stader/config.ts | 26 + src/dex/stader/stader-e2e.test.ts | 37 + src/dex/stader/stader-pool.ts | 68 + src/dex/stader/stader.ts | 253 ++++ src/dex/stader/types.ts | 15 + src/dex/stader/utils.ts | 34 + tests/constants-e2e.ts | 5 + 10 files changed, 3439 insertions(+) create mode 100644 src/abi/SSPM.json create mode 100644 src/abi/StaderOracle.json create mode 100644 src/dex/stader/config.ts create mode 100644 src/dex/stader/stader-e2e.test.ts create mode 100644 src/dex/stader/stader-pool.ts create mode 100644 src/dex/stader/stader.ts create mode 100644 src/dex/stader/types.ts create mode 100644 src/dex/stader/utils.ts 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/dex/index.ts b/src/dex/index.ts index 3b759aaf7..131f52b51 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -94,6 +94,7 @@ import { LitePsm } from './lite-psm/lite-psm'; import { UsualBond } from './usual-bond/usual-bond'; import { StkGHO } from './stkgho/stkgho'; import { SkyConverter } from './sky-converter/sky-converter'; +import { Stader } from './stader/stader'; const LegacyDexes = [ CurveV2, @@ -113,6 +114,7 @@ const LegacyDexes = [ ]; const Dexes = [ + Stader, Bebop, Dexalot, CurveV1, diff --git a/src/dex/stader/config.ts b/src/dex/stader/config.ts new file mode 100644 index 000000000..0ce146d92 --- /dev/null +++ b/src/dex/stader/config.ts @@ -0,0 +1,26 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const StaderConfig: DexConfigMap = { + Stader: { + [Network.MAINNET]: { + ETHx: '0xA35b1B31Ce002FBF2058D22F30f95D405200A15b', + SSPM: '0xcf5EA1b38380f6aF39068375516Daf40Ed70D299', + StaderOracle: '0xF64bAe65f6f2a5277571143A24FaaFDFC0C2a737', + }, + }, +}; + +export const Adapters: { + [chainId: number]: { [side: string]: { name: string; index: number }[] }; +} = { + [Network.MAINNET]: { + [SwapSide.SELL]: [ + { + name: 'Adapter05', + index: 1, + }, + ], + }, +}; diff --git a/src/dex/stader/stader-e2e.test.ts b/src/dex/stader/stader-e2e.test.ts new file mode 100644 index 000000000..c1aca3f3a --- /dev/null +++ b/src/dex/stader/stader-e2e.test.ts @@ -0,0 +1,37 @@ +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'; + + ['ETH', 'WETH'].forEach(token => { + [ContractMethod.swapExactAmountIn].forEach(contractMethod => { + it(`${contractMethod} - ${token} -> ETHx`, async () => { + await testE2E( + tokens[`${token}`], + tokens['ETHx'], + holders[`${token}`], + '1000000000000000000', + SwapSide.SELL, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); +}); diff --git a/src/dex/stader/stader-pool.ts b/src/dex/stader/stader-pool.ts new file mode 100644 index 000000000..68fbb43c6 --- /dev/null +++ b/src/dex/stader/stader-pool.ts @@ -0,0 +1,68 @@ +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 { + ETHxToETHRateFixed: BigInt((totalEth * this.DECIMALS) / ethxSupply), + }; + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + async generateState( + blockNumber: number, + ): Promise> { + const state = await getOnChainStateETHx( + this.dexHelper.multiContract, + this.poolAddress, + this.poolInterface, + blockNumber, + ); + + return state; + } + + getPrice(blockNumber: number, ethAmount: bigint): bigint { + const state = this.getState(blockNumber); + if (!state) throw new Error('Cannot compute price'); + const { ETHxToETHRateFixed } = state; + + // calculation in contract are made with UD60x18 precision + return (ethAmount * BI_POWS[18]) / ETHxToETHRateFixed; + } +} diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts new file mode 100644 index 000000000..729dbf6cf --- /dev/null +++ b/src/dex/stader/stader.ts @@ -0,0 +1,253 @@ +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { NumberAsString, SwapSide } from '@paraswap/core'; +import { + AdapterExchangeParam, + Address, + DexExchangeParam, + ExchangePrices, + Logger, + PoolLiquidity, + PoolPrices, + SimpleExchangeParam, + 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, Adapters } 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'; +import { ethers } from 'ethers'; + +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], + protected adapters = Adapters[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 getSimpleParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + data: StaderData, + side: SwapSide, + ): Promise { + const swapData = this.SSPMInterface.encodeFunctionData( + SSPMFunctions.deposit, + [NULL_ADDRESS], + ); + + return { + callees: [this.config.SSPM.toLowerCase()], + calldata: [swapData], + values: [srcAmount], + networkFee: '0', + }; + } + + async initializePricing(blockNumber: number) { + const data: { returnData: any[] } = + await this.dexHelper.multiContract.methods + .aggregate([ + { + target: this.SSPM_Address, + callData: this.SSPMInterface.encodeFunctionData( + 'getExchangeRate', + [], + ), + }, + ]) + .call({}, blockNumber); + + const decodedData = ethers.utils.defaultAbiCoder.decode( + ['uint256'], + data.returnData[0], + ); + + const ETHxToETHRateFixed = BigInt(decodedData[0].toString()); + + await this.ethxPool.initialize(blockNumber, { + state: { ETHxToETHRateFixed }, + }); + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amountsIn: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[] | undefined, + transferFees?: TransferFeeParams | undefined, + isFirstSwap?: boolean | undefined, + ): Promise | null> { + const pool = this.ethxPool; + if (!pool.getState(blockNumber)) return null; + + const unitIn = BI_POWS[18]; + const unitOut = pool.getPrice(blockNumber, unitIn); + const amountsOut = amountsIn.map(amountIn => + pool.getPrice(blockNumber, 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, + side: SwapSide, + ): boolean { + if (side === SwapSide.BUY) return false; + + 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 { + const swapData = this.SSPMInterface.encodeFunctionData( + SSPMFunctions.deposit, + ['0x000010036c0190e009a000d0fc3541100a07380a'], + ); + + return { + needWrapNative: this.needWrapNative, + dexFuncHasRecipient: false, + 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 (!this.isEligibleSwap(srcToken, destToken, side)) 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 this.adapters?.[side] || null; + } + + getTopPoolsForToken( + tokenAddress: string, + limit: number, + ): AsyncOrSync { + return []; + } +} diff --git a/src/dex/stader/types.ts b/src/dex/stader/types.ts new file mode 100644 index 000000000..e15c86eff --- /dev/null +++ b/src/dex/stader/types.ts @@ -0,0 +1,15 @@ +export type ETHxPoolState = { + ETHxToETHRateFixed: 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..d7d1d5a3f --- /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'; + +export async function getOnChainStateETHx( + multiContract: Contract, + poolAddress: string, + poolInterface: Interface, + blockNumber: number | 'latest', +): Promise { + const coder = new AbiCoder(); + const DECIMALS = BigInt(1000000000000000000n); + 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()); + const ETHxToETHRateFixed = (totalETHBalance * DECIMALS) / totalETHXSupply; + + return { + ETHxToETHRateFixed, + }; +} diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 258fdd5f0..29e337000 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -92,6 +92,10 @@ export const Tokens: { address: ETHER_ADDRESS, decimals: 18, }, + ETHx: { + address: '0xA35b1B31Ce002FBF2058D22F30f95D405200A15b', + decimals: 18, + }, SWETH: { address: '0xf951e335afb289353dc249e82926178eac7ded78', decimals: 18, @@ -1625,6 +1629,7 @@ export const Holders: { sUSDS: '0xd564B3aE673CAa49D054Bf185bD72a6853763eE7', SKY: '0x0ddda327A6614130CCb20bc0097313A282176A01', MKR: '0xe9aAA7A9DDc0877626C1779AbC29993aD89A6c1f', + ETHx: '0xFCC1A2c71F01B7f58Ed538a6B4AAa5A0724eB5A6', // Idle tokens AA_wstETH: '0xd7C1b48877A7dFA7D51cf1144c89C0A3F134F935', 'AA_idle_cpPOR-USDC': '0x085c8eaccA6911fE60aE3f8FbAe5F3012E3A05Ec', From 41bef4372fecb311db5649e5e6c114c8746cb9c6 Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Fri, 6 Dec 2024 15:55:31 +0530 Subject: [PATCH 02/18] fix: recipient address and remove legacy adaptor --- src/dex/stader/config.ts | 13 ------------- src/dex/stader/stader.ts | 9 ++++----- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/dex/stader/config.ts b/src/dex/stader/config.ts index 0ce146d92..1ab9ada74 100644 --- a/src/dex/stader/config.ts +++ b/src/dex/stader/config.ts @@ -11,16 +11,3 @@ export const StaderConfig: DexConfigMap = { }, }, }; - -export const Adapters: { - [chainId: number]: { [side: string]: { name: string; index: number }[] }; -} = { - [Network.MAINNET]: { - [SwapSide.SELL]: [ - { - name: 'Adapter05', - index: 1, - }, - ], - }, -}; diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts index 729dbf6cf..ccf864d68 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -22,7 +22,7 @@ import { BI_POWS } from '../../bigint-constants'; import { AsyncOrSync } from 'ts-essentials'; import { ETHxEventPool } from './stader-pool'; import { StaderData, SSPMFunctions } from './types'; -import { StaderConfig, Adapters } from './config'; +import { StaderConfig } from './config'; import { WethFunctions } from '../weth/types'; import ERC20ABI from '../../abi/erc20.json'; import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; @@ -55,7 +55,6 @@ export class Stader dexKey: string, dexHelper: IDexHelper, protected config = StaderConfig[dexKey][network], - protected adapters = Adapters[network], ) { super(dexHelper, 'Stader'); this.network = dexHelper.config.data.network; @@ -202,12 +201,12 @@ export class Stader ): DexExchangeParam { const swapData = this.SSPMInterface.encodeFunctionData( SSPMFunctions.deposit, - ['0x000010036c0190e009a000d0fc3541100a07380a'], + [_recipient], ); return { needWrapNative: this.needWrapNative, - dexFuncHasRecipient: false, + dexFuncHasRecipient: true, exchangeData: swapData, targetExchange: this.config.SSPM.toLowerCase(), preSwapUnwrapCalldata: this.isWETH(srcToken) @@ -241,7 +240,7 @@ export class Stader } getAdapters(side: SwapSide): { name: string; index: number }[] | null { - return this.adapters?.[side] || null; + return null; } getTopPoolsForToken( From 446cfcdec19c22d7cb442f90f4e4ce539e9d33cc Mon Sep 17 00:00:00 2001 From: blockgroot <170620375+blockgroot@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:26:15 +0530 Subject: [PATCH 03/18] test: add integration and event tests --- src/dex/stader/stader-events.test.ts | 64 +++++++++++++++++ src/dex/stader/stader-integration.test.ts | 86 +++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 src/dex/stader/stader-events.test.ts create mode 100644 src/dex/stader/stader-integration.test.ts 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..8475ba2c5 --- /dev/null +++ b/src/dex/stader/stader-integration.test.ts @@ -0,0 +1,86 @@ +/* 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 { Stader } from './stader'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +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]; + + // TODO: Put here token Symbol to check against + // Don't forget to update relevant tokens in constant-e2e.ts + 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], + ]; + + 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 function () { + 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); + }); + }); +}); From 82ec60fbd14d5ec97b3f599f3239d53852fe5fed Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Fri, 6 Dec 2024 21:54:22 +0300 Subject: [PATCH 04/18] improve pricing and tests --- src/dex/stader/stader-e2e.test.ts | 69 ++++++-- src/dex/stader/stader-integration.test.ts | 196 +++++++++++++++++++--- src/dex/stader/stader-pool.ts | 8 +- src/dex/stader/stader.ts | 64 +++---- src/dex/stader/types.ts | 3 +- src/dex/stader/utils.ts | 6 +- 6 files changed, 251 insertions(+), 95 deletions(-) diff --git a/src/dex/stader/stader-e2e.test.ts b/src/dex/stader/stader-e2e.test.ts index c1aca3f3a..37473da15 100644 --- a/src/dex/stader/stader-e2e.test.ts +++ b/src/dex/stader/stader-e2e.test.ts @@ -17,21 +17,58 @@ describe('ETHx', () => { ); const dexKey = 'Stader'; - ['ETH', 'WETH'].forEach(token => { - [ContractMethod.swapExactAmountIn].forEach(contractMethod => { - it(`${contractMethod} - ${token} -> ETHx`, async () => { - await testE2E( - tokens[`${token}`], - tokens['ETHx'], - holders[`${token}`], - '1000000000000000000', - SwapSide.SELL, - dexKey, - contractMethod, - network, - provider, - ); + 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-integration.test.ts b/src/dex/stader/stader-integration.test.ts index 8475ba2c5..ad78d5cff 100644 --- a/src/dex/stader/stader-integration.test.ts +++ b/src/dex/stader/stader-integration.test.ts @@ -2,17 +2,71 @@ 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 { Stader } from './stader'; -import { - checkPoolPrices, - checkPoolsLiquidity, - checkConstantPoolPrices, -} from '../../../tests/utils'; +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'; @@ -25,25 +79,6 @@ describe('Stader', function () { const tokens = Tokens[network]; - // TODO: Put here token Symbol to check against - // Don't forget to update relevant tokens in constant-e2e.ts - 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], - ]; - beforeAll(async () => { blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); stader = new Stader(network, dexKey, dexHelper); @@ -52,7 +87,24 @@ describe('Stader', function () { } }); - it('getPoolIdentifiers and getPricesVolume ETH -> ETHx SELL', async function () { + 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], @@ -81,6 +133,98 @@ describe('Stader', function () { 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); }); }); }); diff --git a/src/dex/stader/stader-pool.ts b/src/dex/stader/stader-pool.ts index 68fbb43c6..05b6934de 100644 --- a/src/dex/stader/stader-pool.ts +++ b/src/dex/stader/stader-pool.ts @@ -34,7 +34,8 @@ export class ETHxEventPool extends StatefulEventSubscriber { const totalEth = BigInt(event.args.totalEth); const ethxSupply = BigInt(event.args.ethxSupply); return { - ETHxToETHRateFixed: BigInt((totalEth * this.DECIMALS) / ethxSupply), + totalETHBalance: totalEth, + totalETHXSupply: ethxSupply, }; } } catch (e) { @@ -60,9 +61,8 @@ export class ETHxEventPool extends StatefulEventSubscriber { getPrice(blockNumber: number, ethAmount: bigint): bigint { const state = this.getState(blockNumber); if (!state) throw new Error('Cannot compute price'); - const { ETHxToETHRateFixed } = state; + const { totalETHBalance, totalETHXSupply } = state; - // calculation in contract are made with UD60x18 precision - return (ethAmount * BI_POWS[18]) / ETHxToETHRateFixed; + return (ethAmount * totalETHXSupply) / totalETHBalance; } } diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts index ccf864d68..237f4291d 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -91,51 +91,8 @@ export class Stader }; } - async getSimpleParam( - srcToken: Address, - destToken: Address, - srcAmount: NumberAsString, - destAmount: NumberAsString, - data: StaderData, - side: SwapSide, - ): Promise { - const swapData = this.SSPMInterface.encodeFunctionData( - SSPMFunctions.deposit, - [NULL_ADDRESS], - ); - - return { - callees: [this.config.SSPM.toLowerCase()], - calldata: [swapData], - values: [srcAmount], - networkFee: '0', - }; - } - async initializePricing(blockNumber: number) { - const data: { returnData: any[] } = - await this.dexHelper.multiContract.methods - .aggregate([ - { - target: this.SSPM_Address, - callData: this.SSPMInterface.encodeFunctionData( - 'getExchangeRate', - [], - ), - }, - ]) - .call({}, blockNumber); - - const decodedData = ethers.utils.defaultAbiCoder.decode( - ['uint256'], - data.returnData[0], - ); - - const ETHxToETHRateFixed = BigInt(decodedData[0].toString()); - - await this.ethxPool.initialize(blockNumber, { - state: { ETHxToETHRateFixed }, - }); + await this.ethxPool.initialize(blockNumber); } async getPricesVolume( @@ -247,6 +204,23 @@ export class Stader tokenAddress: string, limit: number, ): AsyncOrSync { - return []; + if ( + !(isETHAddress(tokenAddress) || this.isWETH(tokenAddress.toLowerCase())) + ) + return []; + + return [ + { + exchange: this.dexKey, + address: this.config.ETHx, + connectorTokens: [ + { + decimals: 18, + address: this.config.ETHx, + }, + ], + liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred + }, + ]; } } diff --git a/src/dex/stader/types.ts b/src/dex/stader/types.ts index e15c86eff..3dcf7b530 100644 --- a/src/dex/stader/types.ts +++ b/src/dex/stader/types.ts @@ -1,5 +1,6 @@ export type ETHxPoolState = { - ETHxToETHRateFixed: bigint; + totalETHBalance: bigint; + totalETHXSupply: bigint; }; export type StaderData = {}; diff --git a/src/dex/stader/utils.ts b/src/dex/stader/utils.ts index d7d1d5a3f..4bf0f9a2b 100644 --- a/src/dex/stader/utils.ts +++ b/src/dex/stader/utils.ts @@ -1,6 +1,7 @@ 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, @@ -9,7 +10,6 @@ export async function getOnChainStateETHx( blockNumber: number | 'latest', ): Promise { const coder = new AbiCoder(); - const DECIMALS = BigInt(1000000000000000000n); const data: { returnData: any[] } = await multiContract.methods .aggregate([ { @@ -26,9 +26,9 @@ export async function getOnChainStateETHx( const totalETHBalance = BigInt(decodedData[1].toString()); const totalETHXSupply = BigInt(decodedData[2].toString()); - const ETHxToETHRateFixed = (totalETHBalance * DECIMALS) / totalETHXSupply; return { - ETHxToETHRateFixed, + totalETHBalance, + totalETHXSupply, }; } From 15f9767aad25339c636dd01b609239fd3ab5472c Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Fri, 6 Dec 2024 21:55:39 +0300 Subject: [PATCH 05/18] 3.11.8-stader.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b634df154..e21abde2e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.7", + "version": "3.11.8-stader.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From a5a8195900480e0d8ffba92755cb46e649d3353f Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 19:35:10 +0300 Subject: [PATCH 06/18] add fix --- src/dex/stader/stader.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts index 237f4291d..188abbba1 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -8,7 +8,6 @@ import { Logger, PoolLiquidity, PoolPrices, - SimpleExchangeParam, Token, TransferFeeParams, } from '../../types'; @@ -29,7 +28,6 @@ import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; import _ from 'lodash'; import { extractReturnAmountPosition } from '../../executor/utils'; import { getDexKeysWithNetwork, isETHAddress } from '../../utils'; -import { ethers } from 'ethers'; export class Stader extends SimpleExchange @@ -105,6 +103,8 @@ export class Stader transferFees?: TransferFeeParams | undefined, isFirstSwap?: boolean | undefined, ): Promise | null> { + if (side === SwapSide.BUY) return null; + const pool = this.ethxPool; if (!pool.getState(blockNumber)) return null; @@ -132,8 +132,6 @@ export class Stader destToken: Token | string, side: SwapSide, ): boolean { - if (side === SwapSide.BUY) return false; - const srcTokenAddress = ( typeof srcToken === 'string' ? srcToken : srcToken.address ).toLowerCase(); @@ -156,6 +154,8 @@ export class Stader _data: StaderData, _side: SwapSide, ): DexExchangeParam { + if (_side === SwapSide.BUY) throw new Error(`Buy not supported`); + const swapData = this.SSPMInterface.encodeFunctionData( SSPMFunctions.deposit, [_recipient], @@ -187,6 +187,8 @@ export class Stader side: SwapSide, blockNumber: number, ): Promise { + if (side === SwapSide.BUY) return []; + if (!this.isEligibleSwap(srcToken, destToken, side)) return []; return [`${ETHER_ADDRESS}_${destToken.address}`.toLowerCase()]; From 8a4e2e3c902570b9a430f6a1f36d9d9615abf2cd Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 19:35:40 +0300 Subject: [PATCH 07/18] 3.11.8-stader.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e21abde2e..3175586a3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.8-stader.0", + "version": "3.11.8-stader.1", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From ce72fa89bb2f5dc62e988d1cbe19832337b4e5fb Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 19:46:45 +0300 Subject: [PATCH 08/18] add fix --- src/dex/stader/stader.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts index 188abbba1..e883dd492 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -104,6 +104,7 @@ export class Stader isFirstSwap?: boolean | undefined, ): Promise | null> { if (side === SwapSide.BUY) return null; + if (!this.isEligibleSwap(srcToken, destToken)) return null; const pool = this.ethxPool; if (!pool.getState(blockNumber)) return null; @@ -127,11 +128,7 @@ export class Stader ]; } - isEligibleSwap( - srcToken: Token | string, - destToken: Token | string, - side: SwapSide, - ): boolean { + isEligibleSwap(srcToken: Token | string, destToken: Token | string): boolean { const srcTokenAddress = ( typeof srcToken === 'string' ? srcToken : srcToken.address ).toLowerCase(); @@ -189,7 +186,7 @@ export class Stader ): Promise { if (side === SwapSide.BUY) return []; - if (!this.isEligibleSwap(srcToken, destToken, side)) return []; + if (!this.isEligibleSwap(srcToken, destToken)) return []; return [`${ETHER_ADDRESS}_${destToken.address}`.toLowerCase()]; } From aa7ea5a73e53b26ccaa7005544cc0447c7125216 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 19:47:30 +0300 Subject: [PATCH 09/18] 3.11.8-stader.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3175586a3..04a5b1e30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.8-stader.1", + "version": "3.11.8-stader.2", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From f72ffb732b995cea30d3957142082721a50f2eba Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 20:24:14 +0300 Subject: [PATCH 10/18] remove getTopPoolsForToken --- src/dex/stader/stader-integration.test.ts | 20 -------------------- src/dex/stader/stader.ts | 19 +------------------ 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/dex/stader/stader-integration.test.ts b/src/dex/stader/stader-integration.test.ts index ad78d5cff..e8bad2f49 100644 --- a/src/dex/stader/stader-integration.test.ts +++ b/src/dex/stader/stader-integration.test.ts @@ -206,25 +206,5 @@ describe('Stader', function () { 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); - }); }); }); diff --git a/src/dex/stader/stader.ts b/src/dex/stader/stader.ts index e883dd492..5dedf2264 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -203,23 +203,6 @@ export class Stader tokenAddress: string, limit: number, ): AsyncOrSync { - if ( - !(isETHAddress(tokenAddress) || this.isWETH(tokenAddress.toLowerCase())) - ) - return []; - - return [ - { - exchange: this.dexKey, - address: this.config.ETHx, - connectorTokens: [ - { - decimals: 18, - address: this.config.ETHx, - }, - ], - liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred - }, - ]; + return []; } } From e8336c690364407377e64d913126d88f31813539 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 20:25:03 +0300 Subject: [PATCH 11/18] 3.11.8-stader.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 04a5b1e30..58224756e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.8-stader.2", + "version": "3.11.8-stader.3", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From ff34777c030b83248654f59def5b6c95da83184b Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 9 Dec 2024 20:30:19 +0300 Subject: [PATCH 12/18] 3.11.8-stader.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 58224756e..4a144ee14 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.8-stader.3", + "version": "3.11.8-stader.4", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From d53388295d899d196ac4a7c47687ba7f9f6fa851 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 10 Dec 2024 12:00:38 +0300 Subject: [PATCH 13/18] implement getTopPoolsForToken and add integration tests --- src/dex/stader/stader-integration.test.ts | 30 +++++++++++++++++++++ src/dex/stader/stader.ts | 32 +++++++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/src/dex/stader/stader-integration.test.ts b/src/dex/stader/stader-integration.test.ts index e8bad2f49..3c27bc505 100644 --- a/src/dex/stader/stader-integration.test.ts +++ b/src/dex/stader/stader-integration.test.ts @@ -206,5 +206,35 @@ describe('Stader', function () { 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.ts b/src/dex/stader/stader.ts index 5dedf2264..783d43580 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -203,6 +203,38 @@ export class Stader 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 []; } } From 2144ef1f9d59004b53182f6dd41a259df346a441 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 10 Dec 2024 12:02:05 +0300 Subject: [PATCH 14/18] 3.11.10-stader.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4a144ee14..33ca7b290 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.8-stader.4", + "version": "3.11.10-stader.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From a38c4dc5fb2b08d68a425359f82e717f291f2044 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 10 Dec 2024 12:38:53 +0300 Subject: [PATCH 15/18] 3.11.11-stader.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 507d3aed1..42f48636f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.10", + "version": "3.11.11-stader.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 3aef9b22502ea3369aab1e908ab7e8ef4d6ea7f3 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 10 Dec 2024 14:39:24 +0300 Subject: [PATCH 16/18] fix requested changes --- src/dex/stader/config.ts | 2 +- src/dex/stader/stader-pool.ts | 15 ++++++++++++--- src/dex/stader/stader.ts | 6 +++--- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/dex/stader/config.ts b/src/dex/stader/config.ts index 1ab9ada74..265c88cf6 100644 --- a/src/dex/stader/config.ts +++ b/src/dex/stader/config.ts @@ -1,6 +1,6 @@ import { DexParams } from './types'; import { DexConfigMap } from '../../types'; -import { Network, SwapSide } from '../../constants'; +import { Network } from '../../constants'; export const StaderConfig: DexConfigMap = { Stader: { diff --git a/src/dex/stader/stader-pool.ts b/src/dex/stader/stader-pool.ts index 05b6934de..6c8db35c2 100644 --- a/src/dex/stader/stader-pool.ts +++ b/src/dex/stader/stader-pool.ts @@ -45,6 +45,17 @@ export class ETHxEventPool extends StatefulEventSubscriber { 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> { @@ -58,9 +69,7 @@ export class ETHxEventPool extends StatefulEventSubscriber { return state; } - getPrice(blockNumber: number, ethAmount: bigint): bigint { - const state = this.getState(blockNumber); - if (!state) throw new Error('Cannot compute price'); + 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 index 783d43580..409ecc30f 100644 --- a/src/dex/stader/stader.ts +++ b/src/dex/stader/stader.ts @@ -107,12 +107,12 @@ export class Stader if (!this.isEligibleSwap(srcToken, destToken)) return null; const pool = this.ethxPool; - if (!pool.getState(blockNumber)) return null; + const state = await pool.getOrGenerateState(blockNumber); const unitIn = BI_POWS[18]; - const unitOut = pool.getPrice(blockNumber, unitIn); + const unitOut = pool.getPrice(state, unitIn); const amountsOut = amountsIn.map(amountIn => - pool.getPrice(blockNumber, amountIn), + pool.getPrice(state, amountIn), ); return [ From 1be6f147ae52aec82d605a7e0d8ed4742143e044 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 10 Dec 2024 14:40:11 +0300 Subject: [PATCH 17/18] 3.11.11-stader.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 42f48636f..b38f92026 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.11-stader.0", + "version": "3.11.11-stader.1", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 578d4925ffbf3049b8e81b611e142ecad11ff59c Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 10 Dec 2024 14:52:59 +0300 Subject: [PATCH 18/18] 3.11.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b38f92026..76c37ac19 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "3.11.11-stader.1", + "version": "3.11.11", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib",