diff --git a/package.json b/package.json index cdf92d74b..f6d5694b3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.42.14", + "version": "2.42.16", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", diff --git a/src/abi/dodo-v3/D3MM.abi.json b/src/abi/dodo-v3/D3MM.abi.json new file mode 100644 index 000000000..cdd5690bd --- /dev/null +++ b/src/abi/dodo-v3/D3MM.abi.json @@ -0,0 +1,1020 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "MakerDeposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "MakerWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferPrepared", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "payFromAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "receiveToAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "swapFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "mtFee", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "sellOrNot", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { + "inputs": [], + "name": "_CREATOR_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_NEW_OWNER_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_OWNER_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "allFlag", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "borrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "quoteAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPayAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "buyToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "checkBorrowSafe", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "checkCanBeLiquidated", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "checkSafe", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finishLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "getD3MMInfo", + "outputs": [ + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRateModel", + "type": "address" + }, + { + "internalType": "address", + "name": "maintainer", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getDepositedTokenList", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getFeeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "feeRate", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPoolTokenlist", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + } + ], + "name": "getRangeOrderState", + "outputs": [ + { + "components": [ + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "askDownPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "askUpPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidDownPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidUpPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "askAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "kAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "kBid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeBid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapFeeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mtFeeRate", + "type": "uint256" + } + ], + "internalType": "struct Types.TokenMMInfo", + "name": "fromTokenMMInfo", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "uint256", + "name": "askDownPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "askUpPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidDownPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidUpPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "askAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "kAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "kBid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeBid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapFeeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mtFeeRate", + "type": "uint256" + } + ], + "internalType": "struct Types.TokenMMInfo", + "name": "toTokenMMInfo", + "type": "tuple" + } + ], + "internalType": "struct Types.RangeOrderState", + "name": "roState", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTokenMMOtherInfoForRead", + "outputs": [ + { + "internalType": "uint256", + "name": "askAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "kAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "kBid", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeBid", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTokenMMPriceInfoForRead", + "outputs": [ + { + "internalType": "uint256", + "name": "askDownPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "askUpPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidDownPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bidUpPrice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapFee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTokenReserve", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "creator", + "type": "address" + }, + { + "internalType": "address", + "name": "maker", + "type": "address" + }, + { + "internalType": "address", + "name": "vault", + "type": "address" + }, + { + "internalType": "address", + "name": "oracle", + "type": "address" + }, + { + "internalType": "address", + "name": "feeRateModel", + "type": "address" + }, + { + "internalType": "address", + "name": "maintainer", + "type": "address" + } + ], + "name": "init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "initOwner", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isInLiquidation", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "makerDeposit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "makerWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "toAmount", + "type": "uint256" + } + ], + "name": "queryBuyTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "payFromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receiveToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vusdAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mtFee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + } + ], + "name": "querySellTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "payFromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "receiveToAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "vusdAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "swapFee", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "mtFee", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "repay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "repayAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minReceiveAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "sellToken", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newFlag", + "type": "uint256" + } + ], + "name": "setNewAllFlag", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newMaker", + "type": "address" + } + ], + "name": "setNewMaker", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "startLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokenCumMap", + "outputs": [ + { + "internalType": "uint256", + "name": "cumulativeAsk", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "cumulativeBid", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "updateReserveByVault", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "stateMutability": "pure", + "type": "function" + } +] diff --git a/src/abi/dodo-v3/D3Proxy.abi.json b/src/abi/dodo-v3/D3Proxy.abi.json new file mode 100644 index 000000000..9c4bcfa5a --- /dev/null +++ b/src/abi/dodo-v3/D3Proxy.abi.json @@ -0,0 +1,343 @@ +[ + { + "inputs": [ + { + "internalType": "address", + "name": "approveProxy", + "type": "address" + }, + { + "internalType": "address", + "name": "weth", + "type": "address" + }, + { + "internalType": "address", + "name": "d3Vault", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, + { + "inputs": [], + "name": "_D3_VAULT_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_DODO_APPROVE_PROXY_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_ETH_ADDRESS_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_WETH_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "quoteAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxPayAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "deadLine", + "type": "uint256" + } + ], + "name": "buyTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "payFromAmount", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "_data", + "type": "bytes" + } + ], + "name": "d3MMSwapCallBack", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "makerDeposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "data", + "type": "bytes[]" + } + ], + "name": "multicall", + "outputs": [ + { + "internalType": "bytes[]", + "name": "results", + "type": "bytes[]" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "refundETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minReceiveAmount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + }, + { + "internalType": "uint256", + "name": "deadLine", + "type": "uint256" + } + ], + "name": "sellTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "receiveToAmount", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minDtokenAmount", + "type": "uint256" + } + ], + "name": "userDeposit", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "dTokenAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "minReceiveAmount", + "type": "uint256" + } + ], + "name": "userWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "minAmount", + "type": "uint256" + } + ], + "name": "withdrawWETH", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } +] diff --git a/src/abi/dodo-v3/D3Vault.abi.json b/src/abi/dodo-v3/D3Vault.abi.json new file mode 100644 index 000000000..fdb8481d5 --- /dev/null +++ b/src/abi/dodo-v3/D3Vault.abi.json @@ -0,0 +1,2071 @@ +[ + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "AddLiquidator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "AddPool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "AddRouter", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "AddToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "FinishLiquidation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "collateral", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "collateralAmount", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "debt", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "debtAmount", + "type": "uint256" + } + ], + "name": "Liquidate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "interests", + "type": "uint256" + } + ], + "name": "PoolBorrow", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "interests", + "type": "uint256" + } + ], + "name": "PoolRepay", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "RemoveLiquidator", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "RemovePool", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "RemoveRouter", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "cloneFactory", + "type": "address" + } + ], + "name": "SetCloneFactory", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "factory", + "type": "address" + } + ], + "name": "SetD3Factory", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "poolQuota", + "type": "address" + } + ], + "name": "SetD3PoolQuota", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "userQuota", + "type": "address" + } + ], + "name": "SetD3UserQuota", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "template", + "type": "address" + } + ], + "name": "SetDTokenTemplate", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "discount", + "type": "uint256" + } + ], + "name": "SetDiscount", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "IM", + "type": "uint256" + } + ], + "name": "SetIM", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "MM", + "type": "uint256" + } + ], + "name": "SetMM", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "maintainer", + "type": "address" + } + ], + "name": "SetMaintainer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "oracle", + "type": "address" + } + ], + "name": "SetOracle", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "rateManager", + "type": "address" + } + ], + "name": "SetRateManager", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "SetToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "StartLiquidation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dTokenAmount", + "type": "uint256" + } + ], + "name": "UserDeposit", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "msgSender", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "dTokenAmount", + "type": "uint256" + } + ], + "name": "UserWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "WithdrawReserves", + "type": "event" + }, + { + "inputs": [], + "name": "DISCOUNT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "IM", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MM", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_CLONE_FACTORY_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_D3TOKEN_LOGIC_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_D3_FACTORY_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_MAINTAINER_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_ORACLE_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_PENDING_REMOVE_POOL_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_POOL_QUOTA_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_RATE_MANAGER_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "_USER_QUOTA_", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "accrualTimestampMap", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "accrueInterest", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "accrueInterestForRead", + "outputs": [ + { + "internalType": "uint256", + "name": "totalBorrowsNew", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalReservesNew", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowIndexNew", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "accrualTime", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "accrueInterests", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "addD3Pool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "addD3PoolByFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "addLiquidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxDeposit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxCollateral", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateralWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveFactor", + "type": "uint256" + } + ], + "name": "addNewToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "addRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allPoolAddrMap", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowedLiquidator", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowedRouter", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "assetInfo", + "outputs": [ + { + "internalType": "address", + "name": "dToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "accrualTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawnReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxDepositAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxCollateralAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateralWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtWeight", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "checkBadDebt", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "checkBadDebtAfterAccrue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "checkBorrowSafe", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "checkCanBeLiquidated", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "checkCanBeLiquidatedAfterAccrue", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "checkSafe", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "creatorPoolMap", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "finishLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finishPoolRemove", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getAssetInfo", + "outputs": [ + { + "internalType": "address", + "name": "dToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "totalBorrows", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "totalReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveFactor", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "borrowIndex", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "accrualTime", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxDepositAmount", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateralWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawnReserves", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getBalanceAndBorrows", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getBorrowRate", + "outputs": [ + { + "internalType": "uint256", + "name": "rate", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getCash", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getCollateralRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getCollateralRatioBorrow", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "r", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "t", + "type": "uint256" + } + ], + "name": "getCompoundInterestRate", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getCumulativeBorrowRate", + "outputs": [ + { + "internalType": "uint256", + "name": "cumulativeRate", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "currentAmount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getExchangeRate", + "outputs": [ + { + "internalType": "uint256", + "name": "exchangeRate", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getIMMM", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getLatestBorrowIndex", + "outputs": [ + { + "internalType": "uint256", + "name": "borrowIndex", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getPoolBorrowAmount", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getPoolLeftQuota", + "outputs": [ + { + "internalType": "uint256", + "name": "leftQuota", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getReservesInVault", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTokenList", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getTotalAssetsValue", + "outputs": [ + { + "internalType": "uint256", + "name": "totalValue", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getTotalBorrows", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "getTotalDebtValue", + "outputs": [ + { + "internalType": "uint256", + "name": "totalDebt", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "getUtilizationRatio", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "internalType": "address", + "name": "collateral", + "type": "address" + }, + { + "internalType": "uint256", + "name": "collateralAmount", + "type": "uint256" + }, + { + "internalType": "address", + "name": "debt", + "type": "address" + }, + { + "internalType": "uint256", + "name": "debtToCover", + "type": "uint256" + } + ], + "name": "liquidate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "fromToken", + "type": "address" + }, + { + "internalType": "address", + "name": "toToken", + "type": "address" + }, + { + "internalType": "uint256", + "name": "fromAmount", + "type": "uint256" + } + ], + "internalType": "struct LiquidationOrder", + "name": "order", + "type": "tuple" + }, + { + "internalType": "bytes", + "name": "routeData", + "type": "bytes" + }, + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "liquidateByDODO", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "liquidationTarget", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "pendingRemovePoolRepayAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "poolBorrow", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "poolRepay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "poolRepayAll", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "removeD3Pool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "liquidator", + "type": "address" + } + ], + "name": "removeLiquidator", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "router", + "type": "address" + } + ], + "name": "removeRouter", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "cloneFactory", + "type": "address" + } + ], + "name": "setCloneFactory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "dTokenTemplate", + "type": "address" + } + ], + "name": "setDTokenTemplate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "discount", + "type": "uint256" + } + ], + "name": "setDiscount", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newIM", + "type": "uint256" + } + ], + "name": "setIM", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newMM", + "type": "uint256" + } + ], + "name": "setMM", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "maintainer", + "type": "address" + } + ], + "name": "setMaintainer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newFactory", + "type": "address" + } + ], + "name": "setNewD3Factory", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newQuota", + "type": "address" + } + ], + "name": "setNewD3PoolQuota", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newQuota", + "type": "address" + } + ], + "name": "setNewD3UserQuota", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOracle", + "type": "address" + } + ], + "name": "setNewOracle", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newRateManager", + "type": "address" + } + ], + "name": "setNewRateManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "maxDeposit", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "maxCollateral", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "collateralWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "debtWeight", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "reserveFactor", + "type": "uint256" + } + ], + "name": "setToken", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "pool", + "type": "address" + } + ], + "name": "startLiquidation", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "tokenList", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "tokens", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + } + ], + "name": "userDeposit", + "outputs": [ + { + "internalType": "uint256", + "name": "dTokenAmount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "dTokenAmount", + "type": "uint256" + } + ], + "name": "userWithdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "withdrawReserves", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/dex/algebra/algebra.ts b/src/dex/algebra/algebra.ts index 4788e5e72..a145f03b0 100644 --- a/src/dex/algebra/algebra.ts +++ b/src/dex/algebra/algebra.ts @@ -45,8 +45,8 @@ type PoolPairsInfo = { token1: Address; }; -const ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days -const ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day +// const ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS = 3 * 24 * 60 * 60 * 1000; // 3 days +// const ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS = 24 * 60 * 60 * 1000; // Once in a day const ALGEBRA_EFFICIENCY_FACTOR = 3; const ALGEBRA_TICK_GAS_COST = 24_000; // Ceiled const ALGEBRA_TICK_BASE_OVERHEAD = 75_000; @@ -64,6 +64,8 @@ export class Algebra extends SimpleExchange implements IDex { readonly isFeeOnTransferSupported: boolean = false; protected eventPools: Record = {}; + private newlyCreatedPoolKeys: Set = new Set(); + readonly hasConstantPriceLargeAmounts = false; readonly needWrapNative = true; @@ -139,22 +141,23 @@ export class Algebra extends SimpleExchange implements IDex { // Init listening to new pools creation await this.factory.initialize(blockNumber); - if (!this.dexHelper.config.isSlave) { - const cleanExpiredNotExistingPoolsKeys = async () => { - const maxTimestamp = - Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; - await this.dexHelper.cache.zremrangebyscore( - this.notExistingPoolSetKey, - 0, - maxTimestamp, - ); - }; - - this.intervalTask = setInterval( - cleanExpiredNotExistingPoolsKeys.bind(this), - ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, - ); - } + //// COMMENTING DEPRECATED LOGIC: as we now invalidate pools on creation this is not needed anymore + // if (!this.dexHelper.config.isSlave) { + // const cleanExpiredNotExistingPoolsKeys = async () => { + // const maxTimestamp = + // Date.now() - ALGEBRA_CLEAN_NOT_EXISTING_POOL_TTL_MS; + // await this.dexHelper.cache.zremrangebyscore( + // this.notExistingPoolSetKey, + // 0, + // maxTimestamp, + // ); + // }; + + // this.intervalTask = setInterval( + // cleanExpiredNotExistingPoolsKeys.bind(this), + // ALGEBRA_CLEAN_NOT_EXISTING_POOL_INTERVAL_MS, + // ); + // } } /* @@ -167,7 +170,9 @@ export class Algebra extends SimpleExchange implements IDex { }) => { const logPrefix = '[onPoolCreatedDeleteFromNonExistingSet]'; const [_token0, _token1] = this._sortTokens(token0, token1); - const poolKey = `${_token0}_${_token1}`; + const poolKey = `${_token0}_${_token1}`.toLowerCase(); + + this.newlyCreatedPoolKeys.add(poolKey); // consider doing it only from master pool for less calls to distant cache @@ -290,27 +295,43 @@ export class Algebra extends SimpleExchange implements IDex { pool!.initRetryAttemptCount = 0; }, }); + + if (this.newlyCreatedPoolKeys.has(key)) { + this.newlyCreatedPoolKeys.delete(key); + } } catch (e) { if (e instanceof Error && e.message.endsWith('Pool does not exist')) { - // no need to await we want the set to have the pool key but it's not blocking - this.dexHelper.cache.zadd( - this.notExistingPoolSetKey, - [Date.now(), key], - 'NX', - ); + /* + protection against 2 race conditions + 1/ if pool.initialize() promise rejects after the Pool creation event got treated + 2/ if the rpc node we hit on the http request is lagging behind the one we got event from (websocket) + */ + if (this.newlyCreatedPoolKeys.has(key)) { + this.logger.warn( + `[block=${blockNumber}][Pool=${key}] newly created pool failed to initialise, trying on next request`, + ); + } else { + this.logger.info( + `[block=${blockNumber}][Pool=${key}] pool failed to initialize so it's marked as non existing`, + e, + ); - // Pool does not exist for this pair, so we can set it to null - // to prevent more requests for this pool - pool = null; - this.logger.trace( - `${this.dexHelper}: Pool: srcAddress=${srcAddress}, destAddress=${destAddress} not found`, - e, - ); + // no need to await we want the set to have the pool key but it's not blocking + this.dexHelper.cache.zadd( + this.notExistingPoolSetKey, + [Date.now(), key], + 'NX', + ); + + // Pool does not exist for this pair, so we can set it to null + // to prevent more requests for this pool + pool = null; + } } else { // on unknown error mark as failed and increase retryCount for retry init strategy // note: state would be null by default which allows to fallback this.logger.warn( - `${this.dexKey}: Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`, + `[block=${blockNumber}][Pool=${key}] Can not generate pool state for srcAddress=${srcAddress}, destAddress=${destAddress} pool fallback to rpc and retry every ${this.config.initRetryFrequency} times, initRetryAttemptCount=${pool.initRetryAttemptCount}`, e, ); pool.initFailed = true; diff --git a/src/dex/algebra/lib/AlgebraMath.ts b/src/dex/algebra/lib/AlgebraMath.ts index c6f24cecb..fa1dea21a 100644 --- a/src/dex/algebra/lib/AlgebraMath.ts +++ b/src/dex/algebra/lib/AlgebraMath.ts @@ -652,15 +652,10 @@ class AlgebraMathClass { } _require( - currentPrice == newSqrtPriceX96, - `LOGIC ERROR: calculated currentPrice and price from event ('newSqrtPriceX96') should always be equal at the end`, - { currentPrice, newSqrtPriceX96 }, - ); - - _require( - currentTick == newTick, - `LOGIC ERROR: calculated currentTick and tick from event ('newTick') should always be equal at the end`, - { currentTick, newTick }, + currentPrice === newSqrtPriceX96 && currentTick === newTick, + 'LOGIC ERROR: calculated (currentPrice,currentTick) and (newSqrtPriceX96, newTick) from event should always be equal at the end', + { currentPrice, newSqrtPriceX96, currentTick, newTick }, + 'currentPrice === newSqrtPriceX96 && currentTick === newTick', ); let [amount0, amount1] = diff --git a/src/dex/dodo-v3/config.ts b/src/dex/dodo-v3/config.ts new file mode 100644 index 000000000..27b141e34 --- /dev/null +++ b/src/dex/dodo-v3/config.ts @@ -0,0 +1,84 @@ +import { DexParams } from './types'; +import { DexConfigMap, AdapterMappings } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const MAX_POOL_CNT = 1000; +export const POOL_CACHE_TTL = 60 * 60; // 1hr + +export const DodoV3Config: DexConfigMap = { + DodoV3: { + [Network.MAINNET]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/46336/dodoex_d3mm_eth/version/latest', + D3Proxy: '0x411ec324598EF53b1E8663e335e9094464523e6B', + D3Vault: '0x49186E32fEd50fd6B5604A2618c7B0b03Cd41414', + }, + [Network.BSC]: { + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/yongjun925/dodoex_d3mm_bsc', + D3Proxy: '0x8fb36F4CF67EF12Cc0b63CF951ca0b4f9a8F1953', + D3Vault: '0x3f4eF3763E0b6edB2b3237e29BD7e23Bd168bD46', + }, + [Network.POLYGON]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/46336/dodoex_d3mm_polygon/version/latest', + D3Proxy: '0x1c29eFa924770154fD44569c5B2bF8103feA45A1', + D3Vault: '0x224fEce8104771478a3A4CE6D92ab1538d3659ee', + }, + [Network.AVALANCHE]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/2860/dodoex_d3mm_avax/version/latest', + D3Proxy: '0xa71415675F68f29259ddD63215E5518d2735bf0a', + D3Vault: '0xEAC4BFef7D1c872Ed705B01856af7f9802adC596', + }, + [Network.ARBITRUM]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/46336/dodoex_d3mm_arbitrum/version/latest', + D3Proxy: '0xbe9ec3C4825D87d77E0F049aA586449cF1d1E31b', + D3Vault: '0xBAf350b14ed48429A7772F7D05B2CFc6620744D9', + }, + [Network.OPTIMISM]: { + subgraphURL: + 'https://api.studio.thegraph.com/query/2860/dodoex_d3mm_optimism/version/latest', + D3Proxy: '0xCb3dC90E800C961d4a206BeAAFd92A6d2E06495e', + D3Vault: '0x0fcB5237A1997C4700Ffa2BB4522EA38d4F851Fc', + }, + }, +}; + +export const Adapters: Record = { + [Network.POLYGON]: { + [SwapSide.SELL]: [ + { + name: '', + index: 0, + }, + ], + }, +}; + +export const SUBGRAPH_FETCH_ALL_POOOLS_RQ = `query Pools($where: Pool_filter, $first: Int) { + pools(where: $where, orderBy: totalAssetsUSD, orderDirection: desc, first: $first) { + id + blockNumber + totalAssetsUSD + vault { + id + } + } +}`; + +export const SUBGRAPH_FETCH_TOP_POOOLS_RQ = `query Pools($where: Pool_filter, $first: Int) { + pools(where: $where, orderBy: totalAssetsUSD, orderDirection: desc, first: $first) { + id + blockNumber + totalAssetsUSD + tokenList { + token { + symbol + id + decimals + } + } + } +}`; diff --git a/src/dex/dodo-v3/dodo-v3-e2e.test.ts b/src/dex/dodo-v3/dodo-v3-e2e.test.ts new file mode 100644 index 000000000..24d31da4b --- /dev/null +++ b/src/dex/dodo-v3/dodo-v3-e2e.test.ts @@ -0,0 +1,163 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { + Tokens, + Holders, + NativeTokenSymbols, +} from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +/* + README + ====== + + This test script should add e2e tests for DodoV3. The tests + should cover as many cases as possible. Most of the DEXes follow + the following test structure: + - DexName + - ForkName + Network + - ContractMethod + - ETH -> Token swap + - Token -> ETH swap + - Token -> Token swap + + The template already enumerates the basic structure which involves + testing simpleSwap, multiSwap, megaSwap contract methods for + ETH <> TOKEN and TOKEN <> TOKEN swaps. You should replace tokenA and + tokenB with any two highly liquid tokens on DodoV3 for the tests + to work. If the tokens that you would like to use are not defined in + Tokens or Holders map, you can update the './tests/constants-e2e' + + Other than the standard cases that are already added by the template + it is highly recommended to add test cases which could be specific + to testing DodoV3 (Eg. Tests based on poolType, special tokens, + etc). + + You can run this individual test script by running: + `npx jest src/dex//-e2e.test.ts` + + e2e tests use the Tenderly fork api. Please add the following to your + .env file: + TENDERLY_TOKEN=Find this under Account>Settings>Authorization. + TENDERLY_ACCOUNT_ID=Your Tenderly account name. + TENDERLY_PROJECT=Name of a Tenderly project you have created in your + dashboard. + + (This comment should be removed from the final implementation) +*/ + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, + nativeTokenAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + // ContractMethod.multiSwap, + // ContractMethod.megaSwap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + // ContractMethod.buy + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + // it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + // await testE2E( + // tokens[nativeTokenSymbol], + // tokens[tokenASymbol], + // holders[nativeTokenSymbol], + // side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + // side, + // dexKey, + // contractMethod, + // network, + // provider, + // ); + // }); + // it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + // await testE2E( + // tokens[tokenASymbol], + // tokens[nativeTokenSymbol], + // holders[tokenASymbol], + // side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + // side, + // dexKey, + // contractMethod, + // network, + // provider, + // ); + // }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + +describe('DodoV3 E2E', () => { + const dexKey = 'DodoV3'; + + describe('Mainnet', () => { + const network = Network.POLYGON; + + const tokenASymbol: string = 'WETH'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000'; + const nativeTokenAmount = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); +}); diff --git a/src/dex/dodo-v3/dodo-v3-events.test.ts b/src/dex/dodo-v3/dodo-v3-events.test.ts new file mode 100644 index 000000000..a0315ed9b --- /dev/null +++ b/src/dex/dodo-v3/dodo-v3-events.test.ts @@ -0,0 +1,144 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { DodoV3EventPool } from './dodo-v3-pool'; +import { Network } from '../../constants'; +import { Address } from '../../types'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { D3VaultState, PoolState } from './types'; +import { DodoV3Vault } from './dodo-v3-vault'; +import { DeepReadonly } from 'ts-essentials'; +import { DodoV3Config } from './config'; + +/* + README + ====== + + This test script adds unit tests for DodoV3 event based + system. This is done by fetching the state on-chain before the + event block, manually pushing the block logs to the event-subscriber, + comparing the local state with on-chain state. + + Most of the logic for testing is abstracted by `testEventSubscriber`. + You need to do two things to make the tests work: + + 1. Fetch the block numbers where certain events were released. You + can modify the `./scripts/fetch-event-blocknumber.ts` to get the + block numbers for different events. Make sure to get sufficient + number of blockNumbers to cover all possible cases for the event + mutations. + + 2. Complete the implementation for fetchPoolState function. The + function should fetch the on-chain state of the event subscriber + using just the blocknumber. + + The template tests only include the test for a single event + subscriber. There can be cases where multiple event subscribers + exist for a single DEX. In such cases additional tests should be + added. + + You can run this individual test script by running: + `npx jest src/dex//-events.test.ts` + + (This comment should be removed from the final implementation) +*/ + +jest.setTimeout(50 * 1000); + +async function fetchPoolState( + dodoV3Pools: DodoV3EventPool, + blockNumber: number, + poolAddress: string, +): Promise { + // TODO: complete me! + return {}; +} + +async function fetchVaultState( + dodoV3Vault: DodoV3Vault, + blockNumber: number, + poolAddress: string, +): Promise> { + const message = `DodoV3: ${poolAddress} blockNumber ${blockNumber}`; + console.log(`Fetching state ${message}`); + const state = dodoV3Vault.generateState(blockNumber); + console.log(`Done ${message}`); + return state; +} + +// eventName -> blockNumbers +type EventMappings = Record; + +describe('DodoV3 EventPool Mainnet', function () { + const dexKey = 'DodoV3'; + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const config = DodoV3Config[dexKey][network]; + const logger = dexHelper.getLogger(dexKey); + let dodoV3Pool: DodoV3EventPool; + let dodoV3Vault: DodoV3Vault; + + // poolAddress -> EventMappings + const eventsToTest: Record = { + [config.D3Vault]: { + AddToken: [140962617], + }, + }; + + beforeEach(async () => { + // dodoV3Pool = new DodoV3EventPool( + // dexKey, + // network, + // dexHelper, + // logger, + // /* TODO: Put here additional constructor arguments if needed */ + // ); + + dodoV3Vault = new DodoV3Vault( + dexHelper, + dexKey, + config.D3Vault, + logger, + async () => {}, + async () => {}, + ); + }); + + Object.entries(eventsToTest).forEach( + ([poolAddress, events]: [string, EventMappings]) => { + describe(`Events for ${poolAddress}`, () => { + Object.entries(events).forEach( + ([eventName, blockNumbers]: [string, number[]]) => { + describe(`${eventName}`, () => { + blockNumbers.forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async function () { + // await testEventSubscriber( + // dodoV3Pool, + // dodoV3Pool.addressesSubscribed, + // (_blockNumber: number) => + // fetchPoolState(dodoV3Pool, _blockNumber, poolAddress), + // blockNumber, + // `${dexKey}_${poolAddress}`, + // dexHelper.provider, + // ); + + await testEventSubscriber( + dodoV3Vault, + dodoV3Vault.addressesSubscribed, + (_blockNumber: number) => + fetchVaultState(dodoV3Vault, _blockNumber, poolAddress), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }, + ); + }); + }, + ); +}); diff --git a/src/dex/dodo-v3/dodo-v3-integration.test.ts b/src/dex/dodo-v3/dodo-v3-integration.test.ts new file mode 100644 index 000000000..3baed9e9f --- /dev/null +++ b/src/dex/dodo-v3/dodo-v3-integration.test.ts @@ -0,0 +1,266 @@ +/* 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 { DodoV3 } from './dodo-v3'; +import { + checkPoolPrices, + checkPoolsLiquidity, + checkConstantPoolPrices, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import D3MMABI from '../../abi/dodo-v3/D3MM.abi.json'; +import { Token } from '../../types'; + +/* + README + ====== + + This test script adds tests for DodoV3 general integration + with the DEX interface. The test cases below are example tests. + It is recommended to add tests which cover DodoV3 specific + logic. + + You can run this individual test script by running: + `npx jest src/dex//-integration.test.ts` + + (This comment should be removed from the final implementation) +*/ + +function getReaderCalldata( + exchangeAddress: string, + readerIface: Interface, + amounts: bigint[], + funcName: string, + _srcAddress: string, + _destAddress: string, +) { + return amounts.map(amount => ({ + target: exchangeAddress, + callData: readerIface.encodeFunctionData(funcName, [ + _srcAddress, + _destAddress, + amount.toString(), + ]), + })); +} + +function decodeReaderResult( + results: Result, + readerIface: Interface, + funcName: string, + side: SwapSide, +) { + return results.map(result => { + const parsed = readerIface.decodeFunctionResult(funcName, result); + return BigInt(side === SwapSide.BUY ? parsed[0]._hex : parsed[1]._hex); + }); +} + +async function checkOnChainPricing( + dodoV3: DodoV3, + funcName: string, + blockNumber: number, + prices: bigint[], + amounts: bigint[], + srcToken: Token, + destToken: Token, + side: SwapSide, +) { + const exchangeAddress = '0x7c03d8369390969b7df325d67ad57c6ddef9d16b'; + + const _srcAddress = srcToken.address.toLowerCase(); + const _destAddress = destToken.address.toLowerCase(); + + // Normally you can get it from dodoV3.Iface or from eventPool. + // It depends on your implementation + const readerIface = new Interface(D3MMABI); + + const readerCallData = getReaderCalldata( + exchangeAddress, + readerIface, + amounts.slice(1), + funcName, + _srcAddress, + _destAddress, + ); + const readerResult = ( + await dodoV3.dexHelper.multiContract.methods + .aggregate(readerCallData) + .call({}, blockNumber) + ).returnData; + + const expectedPrices = [0n].concat( + decodeReaderResult(readerResult, readerIface, funcName, side), + ); + + expect(prices).toEqual(expectedPrices); +} + +async function testPricingOnNetwork( + dodoV3: DodoV3, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + const srcToken = networkTokens[srcTokenSymbol]; + const destToken = networkTokens[destTokenSymbol]; + + const pools = await dodoV3.getPoolIdentifiers( + srcToken, + destToken, + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await dodoV3.getPricesVolume( + srcToken, + destToken, + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (dodoV3.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + // Check if onchain pricing equals to calculated ones + await checkOnChainPricing( + dodoV3, + funcNameToCheck, + blockNumber, + poolPrices![0].prices, + amounts, + srcToken, + destToken, + side, + ); +} + +describe('DodoV3', function () { + const dexKey = 'DodoV3'; + let blockNumber: number; + let dodoV3: DodoV3; + + describe('Mainnet', () => { + const network = Network.POLYGON; + const dexHelper = new DummyDexHelper(network); + + const tokens = Tokens[network]; + + // Don't forget to update relevant tokens in constant-e2e.ts + const srcTokenSymbol = 'WETH'; + const destTokenSymbol = 'USDT'; + + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals], + ]; + + const amountsForBuy = [ + 0n, + 1n * BI_POWS[tokens[destTokenSymbol].decimals], + 2n * BI_POWS[tokens[destTokenSymbol].decimals], + 3n * BI_POWS[tokens[destTokenSymbol].decimals], + 4n * BI_POWS[tokens[destTokenSymbol].decimals], + 5n * BI_POWS[tokens[destTokenSymbol].decimals], + 6n * BI_POWS[tokens[destTokenSymbol].decimals], + 7n * BI_POWS[tokens[destTokenSymbol].decimals], + 8n * BI_POWS[tokens[destTokenSymbol].decimals], + 9n * BI_POWS[tokens[destTokenSymbol].decimals], + 10n * BI_POWS[tokens[destTokenSymbol].decimals], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + dodoV3 = new DodoV3(network, dexKey, dexHelper); + if (dodoV3.initializePricing) { + await dodoV3.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + dodoV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + 'querySellTokens', + ); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + await testPricingOnNetwork( + dodoV3, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.BUY, + amountsForBuy, + 'queryBuyTokens', + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newDodoV3 = new DodoV3(network, dexKey, dexHelper); + if (newDodoV3.updatePoolState) { + await newDodoV3.updatePoolState(); + } + const poolLiquidity = await newDodoV3.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newDodoV3.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); +}); diff --git a/src/dex/dodo-v3/dodo-v3-pool.ts b/src/dex/dodo-v3/dodo-v3-pool.ts new file mode 100644 index 000000000..7da6225fe --- /dev/null +++ b/src/dex/dodo-v3/dodo-v3-pool.ts @@ -0,0 +1,130 @@ +import { Interface } from '@ethersproject/abi'; +import { DeepReadonly, assert } from 'ts-essentials'; +import D3MMABI from '../../abi/dodo-v3/D3MM.abi.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, BlockHeader, Log, Logger } from '../../types'; +import { catchParseLogError } from '../../utils'; +import { addressArrayDecode } from './dodo-v3-vault'; +import { PoolState } from './types'; + +export class DodoV3EventPool extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ) => DeepReadonly | null; + } = {}; + + logDecoder: (log: Log) => any; + + readonly D3MMAddress: Address; + + public readonly D3MMIface = new Interface(D3MMABI); + + addressesSubscribed: string[]; + + constructor( + readonly parentName: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + newD3Address: string, + ) { + super(parentName, newD3Address, dexHelper, logger); + + this.D3MMAddress = newD3Address; + + this.logDecoder = (log: Log) => this.D3MMIface.parseLog(log); + this.addressesSubscribed = [newD3Address]; + + // The tokens deposited into d3mm by makerDeposit, which are not in the vault, can participate in the transaction after SetNewToken is called by the maker. + // However, SetNewToken is thrown in the maker contract and cannot be listened to. We assume that the user will definitely call SetNewToken after makerDeposit, so we only need to listen to the makerDeposit event to get the token. If the user only calls makerDeposit and does not call SetNewToken, it will lead to getTokenMMInfoForPool returning an invalid token, i.e., the price cannot be obtained. + this.handlers['MakerDeposit'] = this.handleMakerDeposit.bind(this); + } + + /** + * The function is called every time any of the subscribed + * addresses release log. The function accepts the current + * state, updates the state according to the log, and returns + * the updated state. + * @param state - Current state of event subscriber + * @param log - Log released by one of the subscribed addresses + * @returns Updates state of the event subscriber after the log + */ + protected processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + try { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + return this.handlers[event.name](event, state, log, blockHeader); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + /** + * The function generates state using on-chain calls. This + * function is called to regenerate state if the event based + * system fails to fetch events and the local state is no + * more correct. + * @param blockNumber - Blocknumber for which the state should + * should be generated + * @returns state of the event subscriber at blocknumber + */ + async generateState(blockNumber: number): Promise> { + const [resTokenList] = await this.dexHelper.multiWrapper.tryAggregate< + Array
+ >( + false, + [ + { + target: this.D3MMAddress, + callData: this.D3MMIface.encodeFunctionData( + 'getDepositedTokenList', + [], + ), + decodeFunction: addressArrayDecode, + }, + ], + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + false, + ); + this.logger.info(`${this.D3MMAddress} getDepositedTokenList`, resTokenList); + + assert(resTokenList.success, 'Failed to fetch token list'); + const depositedTokenList = resTokenList.returnData; + + return { + D3MMAddress: this.D3MMAddress, + depositedTokenList, + }; + } + + handleMakerDeposit( + event: any, + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + const token = event.args.token.toLowerCase(); + + if (state.depositedTokenList.indexOf(token) === -1) { + return { + ...state, + depositedTokenList: [...state.depositedTokenList, token], + }; + } + + return state; + } +} diff --git a/src/dex/dodo-v3/dodo-v3-vault.ts b/src/dex/dodo-v3/dodo-v3-vault.ts new file mode 100644 index 000000000..55e12dc54 --- /dev/null +++ b/src/dex/dodo-v3/dodo-v3-vault.ts @@ -0,0 +1,161 @@ +import { Interface } from '@ethersproject/abi'; +import { BytesLike, LogDescription } from 'ethers/lib/utils'; +import { DeepReadonly, assert } from 'ts-essentials'; +import D3VaultABI from '../../abi/dodo-v3/D3Vault.abi.json'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { generalDecoder } from '../../lib/decoders'; +import { MultiResult } from '../../lib/multi-wrapper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, BlockHeader, Log, Logger } from '../../types'; +import { catchParseLogError } from '../../utils'; +import { D3VaultState } from './types'; + +export const addressArrayDecode = ( + result: MultiResult | BytesLike, +): string[] => { + return generalDecoder(result, ['address[]'], [], v => + v[0].map((a: string) => a.toLowerCase()), + ); +}; + +export type OnPoolCreatedOrRemovedCallback = ({ + pool, + blockNumber, +}: { + pool: string; + blockNumber: number; +}) => Promise; + +/* + * "Stateless" event subscriber in order to capture "PoolCreated" event on new pools created. + * State is present, but it's a placeholder to actually make the events reach handlers (if there's no previous state - `processBlockLogs` is not called) + */ +export class DodoV3Vault extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + blockHeader: Readonly, + ) => DeepReadonly | null; + } = {}; + + logDecoder: (log: Log) => any; + + readonly vaultAddress: Address; + + public readonly D3VaultIface = new Interface(D3VaultABI); + + constructor( + readonly dexHelper: IDexHelper, + parentName: string, + protected readonly D3VaultAddress: Address, + logger: Logger, + protected readonly onPoolCreated: OnPoolCreatedOrRemovedCallback, + protected readonly onPoolRemoved: OnPoolCreatedOrRemovedCallback, + mapKey: string = '', + ) { + super(parentName, `${parentName} D3Vault`, dexHelper, logger, true, mapKey); + this.vaultAddress = D3VaultAddress; + this.addressesSubscribed = [D3VaultAddress]; + + this.logDecoder = (log: Log) => this.D3VaultIface.parseLog(log); + + this.handlers['AddPool'] = this.handleNewPool.bind(this); + this.handlers['RemovePool'] = this.handleRemovePool.bind(this); + + this.handlers['AddToken'] = this.handleAddToken.bind(this); + } + + /** + * The function generates state using on-chain calls. This + * function is called to regenerate state if the event based + * system fails to fetch events and the local state is no + * more correct. + * @param blockNumber - Blocknumber for which the state should + * should be generated + * @returns state of the event subscriber at blocknumber + */ + async generateState( + blockNumber: number, + ): Promise> { + const [resTokenList] = await this.dexHelper.multiWrapper.tryAggregate< + Array
+ >( + false, + [ + { + target: this.vaultAddress, + callData: this.D3VaultIface.encodeFunctionData('getTokenList', []), + decodeFunction: addressArrayDecode, + }, + ], + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + false, + ); + this.logger.info(`${this.vaultAddress} getTokenList`, resTokenList); + + assert(resTokenList.success, 'Failed to fetch token list'); + const tokenList = resTokenList.returnData; + + return { + tokenList, + }; + } + + protected processLog( + state: DeepReadonly, + log: Readonly, + blockHeader: Readonly, + ): DeepReadonly | null { + try { + const event = this.logDecoder(log); + if (event.name in this.handlers) { + return this.handlers[event.name](event, state, blockHeader); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + handleNewPool( + event: LogDescription, + state: DeepReadonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + const pool = event.args.pool.toLowerCase(); + + this.onPoolCreated({ pool, blockNumber: blockHeader.number }); + return state; + } + + handleRemovePool( + event: LogDescription, + state: DeepReadonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + const pool = event.args.pool.toLowerCase(); + + this.onPoolRemoved({ pool, blockNumber: blockHeader.number }); + return state; + } + + handleAddToken( + event: LogDescription, + state: DeepReadonly, + blockHeader: BlockHeader, + ): DeepReadonly | null { + const newToken = event.args.token.toLowerCase(); + + if (state.tokenList.indexOf(newToken) === -1) { + return { + ...state, + tokenList: [...state.tokenList, newToken], + }; + } + + return state; + } +} diff --git a/src/dex/dodo-v3/dodo-v3.ts b/src/dex/dodo-v3/dodo-v3.ts new file mode 100644 index 000000000..a99429223 --- /dev/null +++ b/src/dex/dodo-v3/dodo-v3.ts @@ -0,0 +1,585 @@ +import { Interface } from '@ethersproject/abi'; +import { BytesLike } from 'ethers/lib/ethers'; +import _ from 'lodash'; +import { AsyncOrSync, DeepReadonly, assert } from 'ts-essentials'; +import D3ProxyABI from '../../abi/dodo-v3/D3Proxy.abi.json'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { Network, SUBGRAPH_TIMEOUT, SwapSide } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { IDex } from '../../dex/idex'; +import { generalDecoder } from '../../lib/decoders'; +import { MultiCallParams, MultiResult } from '../../lib/multi-wrapper'; +import { + AdapterExchangeParam, + Address, + ExchangePrices, + ExchangeTxInfo, + Logger, + OptimalSwapExchange, + PoolLiquidity, + PoolPrices, + PreprocessTransactionOptions, + SimpleExchangeParam, + Token, +} from '../../types'; +import { getBigIntPow, getDexKeysWithNetwork } from '../../utils'; +import { + SimpleExchange, + getLocalDeadlineAsFriendlyPlaceholder, +} from '../simple-exchange'; +import { + Adapters, + DodoV3Config, + POOL_CACHE_TTL, + SUBGRAPH_FETCH_ALL_POOOLS_RQ, + SUBGRAPH_FETCH_TOP_POOOLS_RQ, +} from './config'; +import { DodoV3EventPool } from './dodo-v3-pool'; +import { DodoV3Vault, OnPoolCreatedOrRemovedCallback } from './dodo-v3-vault'; +import { + D3ProxyFunctions, + D3ProxySwapTokensParams, + DexParams, + DodoV3Data, + PoolState, + QuerySellOrBuyTokensResult, +} from './types'; + +function querySellOrBuyTokensResultDecoder( + result: MultiResult | BytesLike, +): QuerySellOrBuyTokensResult { + return generalDecoder( + result, + ['uint256', 'uint256', 'uint256', 'uint256', 'uint256'], + undefined, + value => ({ + payFromAmount: value[0].toBigInt(), + receiveToAmount: value[1].toBigInt(), + vusdAmount: value[2].toBigInt(), + swapFee: value[3].toBigInt(), + mtFee: value[4].toBigInt(), + }), + ); +} + +export class DodoV3 extends SimpleExchange implements IDex { + protected config: DexParams; + + private readonly D3Vault: DodoV3Vault; + + readonly eventPools: Record = {}; + + readonly hasConstantPriceLargeAmounts = false; + // TODO: set true here if protocols works only with wrapped asset + readonly needWrapNative = true; + + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(DodoV3Config, ['DodoV3'])); + + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + protected adapters = Adapters[network] || {}, + readonly proxyIface = new Interface(D3ProxyABI), + ) { + super(dexHelper, dexKey); + this.config = DodoV3Config[dexKey][network]; + this.logger = dexHelper.getLogger(dexKey); + + this.D3Vault = new DodoV3Vault( + dexHelper, + dexKey, + this.config.D3Vault, + this.logger, + this.onPoolCreated, + this.onPoolRemoved, + ); + } + + // Initialize pricing is called once in the start of + // pricing service. It is intended to setup the integration + // for pricing requests. It is optional for a DEX to + // implement this function + async initializePricing(blockNumber: number) { + // Init listening to new pools creation + await this.D3Vault.initialize(blockNumber); + + const allPools = await this.fetchAllSubgraphPools(blockNumber); + for (const pool of allPools) { + await this.addPool(pool.id, blockNumber); + } + } + + async addPool(newD3Address: string, blockNumber: number) { + const pool = new DodoV3EventPool( + this.dexKey, + this.network, + this.dexHelper, + this.logger, + newD3Address, + ); + + await pool.initialize(blockNumber); + this.eventPools[this.getPoolIdentifier(newD3Address)] = pool; + } + + onPoolCreated: OnPoolCreatedOrRemovedCallback = async ({ + pool, + blockNumber, + }) => { + this.logger.info( + `[onPoolCreated] ${this.dexKey}_${this.network} key=${pool}`, + ); + await this.addPool(pool, blockNumber); + }; + + onPoolRemoved: OnPoolCreatedOrRemovedCallback = async ({ + pool, + blockNumber, + }) => { + this.logger.info( + `[onPoolRemoved] ${this.dexKey}_${this.network} key=${pool}`, + ); + const poolIdentifier = this.getPoolIdentifier(pool); + const eventPool = this.eventPools[poolIdentifier]; + if (eventPool) { + eventPool.invalidate(); + delete this.eventPools[poolIdentifier]; + } + }; + + // Returns the list of contract adapters (name and index) + // for a buy/sell. Return null if there are no adapters. + getAdapters(side: SwapSide): { name: string; index: number }[] | null { + return this.adapters[side] ? this.adapters[side] : null; + } + + protected getPoolIdentifier(poolAddress: Address): string { + return `${this.dexKey}_${poolAddress.toLowerCase()}`; + } + + // Returns list of pool identifiers that can be used + // for a given swap. poolIdentifiers must be unique + // across DEXes. It is recommended to use + // ${dexKey}_${poolAddress} as a poolIdentifier + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (!this.eventPools) { + return []; + } + + const srcTokenAddress = srcToken.address.toLowerCase(); + const destTokenAddress = destToken.address.toLowerCase(); + + return Object.keys(this.eventPools).filter(poolIdentifier => { + const pool = this.eventPools[poolIdentifier]; + const state = pool.getState(blockNumber); + if (state) { + const { depositedTokenList } = state; + + // Even if the fromToken or toToken is in the vault, it is still not possible to inquire or swap without setting a new token for the pool. Therefore, both fromToken and toToken must be in the pool in order to use this pool for inquiry without causing an error. + return ( + depositedTokenList.includes(destTokenAddress) && + depositedTokenList.includes(srcTokenAddress) + ); + } + return false; + }); + } + + // Returns pool prices for amounts. + // If limitPools is defined only pools in limitPools + // should be used. If limitPools is undefined then + // any pools can be used. + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + const _srcToken = this.dexHelper.config.wrapETH(srcToken); + const _destToken = this.dexHelper.config.wrapETH(destToken); + + const _srcAddress = _srcToken.address.toLowerCase(); + const _destAddress = _destToken.address.toLowerCase(); + + let selectedPools: DodoV3EventPool[] = []; + if (limitPools) { + selectedPools = limitPools.map( + poolIdentifier => this.eventPools[poolIdentifier], + ); + } else { + selectedPools = ( + await this.getPoolIdentifiers(srcToken, destToken, side, blockNumber) + ).map(poolIdentifier => this.eventPools[poolIdentifier]); + } + + if (selectedPools.length === 0) { + return null; + } + + const calls: MultiCallParams[] = []; + + const unitVolume = getBigIntPow( + (side === SwapSide.SELL ? _srcToken : _destToken).decimals, + ); + + const generateCall = ( + state: DeepReadonly, + pool: DodoV3EventPool, + amount: bigint, + ) => { + return { + target: state.D3MMAddress, + callData: pool.D3MMIface.encodeFunctionData( + side === SwapSide.BUY ? 'queryBuyTokens' : 'querySellTokens', + [_srcAddress, _destAddress, amount.toString()], + ), + decodeFunction: querySellOrBuyTokensResultDecoder, + }; + }; + + selectedPools.forEach(pool => { + const state = pool.getState(blockNumber); + if (!state) { + return; + } + calls.push(generateCall(state, pool, unitVolume)); + amounts.forEach(amount => { + if (amount <= 1000) { + return; + } + calls.push(generateCall(state, pool, amount)); + }); + }); + if (calls.length < 0) { + return null; + } + const resQuerySwapTokens = + await this.dexHelper.multiWrapper.tryAggregate( + false, + calls, + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + true, + ); + + const result: ExchangePrices = []; + let i = 0; + selectedPools.forEach(pool => { + const state = pool.getState(blockNumber); + if (!state) { + return; + } + + const unitRes = resQuerySwapTokens[i++]; + const unit = unitRes.success + ? side === SwapSide.BUY + ? unitRes.returnData.payFromAmount + : unitRes.returnData.receiveToAmount + : 0n; + + const prices: bigint[] = []; + amounts.forEach(amount => { + if (amount <= 1000) { + prices.push(0n); + return; + } + const amountRes = resQuerySwapTokens[i++]; + prices.push( + amountRes.success + ? side === SwapSide.BUY + ? amountRes.returnData.payFromAmount + : amountRes.returnData.receiveToAmount + : 0n, + ); + }); + result.push({ + prices, + unit, + data: { + exchange: state.D3MMAddress, + }, + poolIdentifier: this.getPoolIdentifier(state.D3MMAddress), + exchange: this.dexKey, + gasCost: CALLDATA_GAS_COST.DEX_NO_PAYLOAD, + poolAddresses: [state.D3MMAddress], + }); + }); + + if (result.length === 0) { + return null; + } + + return result; + } + + // Returns estimated gas cost of calldata for this DEX in multiSwap + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + // TODO: update if there is any payload in getAdapterParam + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + // Encode params required by the exchange adapter + // Used for multiSwap, buy & megaSwap + // Hint: abiCoder.encodeParameter() could be useful + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: DodoV3Data, + side: SwapSide, + ): AdapterExchangeParam { + // TODO: complete me! + const { exchange } = data; + + // Encode here the payload for adapter + const payload = ''; + + return { + targetExchange: exchange, + payload, + networkFee: '0', + }; + } + + getTokenFromAddress(address: Address): Token { + // In this Dex decimals are not used + return { address, decimals: 0 }; + } + + async preProcessTransaction( + optimalSwapExchange: OptimalSwapExchange, + srcToken: Token, + destToken: Token, + side: SwapSide, + options: PreprocessTransactionOptions, + ): Promise<[OptimalSwapExchange, ExchangeTxInfo]> { + assert( + optimalSwapExchange.data !== undefined, + `preProcessTransaction: data field is missing`, + ); + + return [ + { + ...optimalSwapExchange, + data: { + ...optimalSwapExchange.data, + slippageFactor: options.slippageFactor, + }, + }, + { deadline: BigInt(getLocalDeadlineAsFriendlyPlaceholder()) }, + ]; + } + + // Encode call data used by simpleSwap like routers + // Used for simpleSwap & simpleBuy + // Hint: this.buildSimpleParamWithoutWETHConversion + // could be useful + async getSimpleParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: DodoV3Data, + side: SwapSide, + ): Promise { + const { exchange, slippageFactor } = data; + + assert( + slippageFactor !== undefined, + `${this.dexKey}-${this.network}: slippageFactor undefined`, + ); + + const isSell = side === SwapSide.SELL; + const swapFunction = isSell + ? D3ProxyFunctions.sellTokens + : D3ProxyFunctions.buyTokens; + const minReceiveAmount = slippageFactor.times(destAmount).toFixed(0); + const maxPayAmount = slippageFactor.times(srcAmount).toFixed(0); + const d3MMSwapCallBackData = this.abiCoder.encodeParameters([], []); + const swapFunctionParams: D3ProxySwapTokensParams = isSell + ? [ + exchange, + this.augustusAddress, + srcToken, + destToken, + srcAmount, + minReceiveAmount, + d3MMSwapCallBackData, + getLocalDeadlineAsFriendlyPlaceholder(), + ] + : [ + exchange, + this.augustusAddress, + srcToken, + destToken, + destAmount, + maxPayAmount, + d3MMSwapCallBackData, + getLocalDeadlineAsFriendlyPlaceholder(), + ]; + const swapData = this.proxyIface.encodeFunctionData(swapFunction, [ + ...swapFunctionParams, + ]); + + return this.buildSimpleParamWithoutWETHConversion( + srcToken, + srcAmount, + destToken, + destAmount, + swapData, + this.config.D3Proxy, + ); + } + + // This is called once before getTopPoolsForToken is + // called for multiple tokens. This can be helpful to + // update common state required for calculating + // getTopPoolsForToken. It is optional for a DEX + // to implement this + async updatePoolState(): Promise {} + + // Returns list of top pools based on liquidity. Max + // limit number pools should be returned. + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const _tokenAddress = tokenAddress.toLowerCase(); + + const data = await this._querySubgraph<{ + pools: Array<{ + id: string; + totalAssetsUSD: string; + tokenList: Array<{ + token: { + id: string; + decimals: string; + symbol: string; + }; + }>; + }> | null; + } | null>(SUBGRAPH_FETCH_TOP_POOOLS_RQ, { + where: { + isRemove: false, + vault: this.config.D3Vault.toLowerCase(), + tokenList_: { + token: _tokenAddress, + }, + totalAssetsUSD_gt: 0, + }, + first: limit, + }); + + if (!data || !data.pools) { + this.logger.error( + `Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`, + ); + return []; + } + return data.pools + .map(pool => { + return { + exchange: this.dexKey, + address: pool.id.toLowerCase(), + connectorTokens: pool.tokenList + .map(t => t.token) + .filter(t => t.id.toLowerCase() !== _tokenAddress) + .map(t => { + return { + address: t.id.toLowerCase(), + decimals: parseInt(t.decimals), + symbol: t.symbol, + }; + }), + liquidityUSD: parseFloat(pool.totalAssetsUSD), + }; + }) + .slice(0, limit); + } + + async fetchAllSubgraphPools(blockNumber: number): Promise< + Array<{ + id: string; + }> + > { + const cacheKey = 'AllSubgraphPools'; + const cachedPools = await this.dexHelper.cache.get( + this.dexKey, + this.network, + cacheKey, + ); + if (cachedPools) { + const allPools = JSON.parse(cachedPools); + this.logger.info( + `Got ${allPools.length} ${this.dexKey}_${this.network} pools from cache`, + ); + return allPools; + } + + this.logger.info( + `Fetching ${this.dexKey}_${this.network} Pools from subgraph`, + ); + const variables = { + where: { + isRemove: false, + vault: this.config.D3Vault.toLowerCase(), + }, + first: 1000, + }; + const data = await this._querySubgraph<{ + pools: Array<{ + id: string; + }> | null; + } | null>(SUBGRAPH_FETCH_ALL_POOOLS_RQ, variables); + if (!data || !data.pools) { + this.logger.error( + `Error_${this.dexKey}_Subgraph: couldn't fetch the pools from the subgraph`, + ); + return []; + } + + this.dexHelper.cache.setex( + this.dexKey, + this.network, + cacheKey, + POOL_CACHE_TTL, + JSON.stringify(data.pools), + ); + + this.logger.info( + `Got ${data.pools.length} ${this.dexKey}_${this.network} pools from subgraph`, + ); + + return data.pools; + } + + private async _querySubgraph(query: string, variables: Object) { + try { + const res = await this.dexHelper.httpRequest.post<{ + data: K; + }>(this.config.subgraphURL, { query, variables }, SUBGRAPH_TIMEOUT); + return res.data; + } catch (e) { + this.logger.error(`${this.dexKey}: can not query subgraph: `, e); + return null; + } + } + + // This is optional function in case if your implementation has acquired any resources + // you need to release for graceful shutdown. For example, it may be any interval timer + releaseResources(): AsyncOrSync {} +} diff --git a/src/dex/dodo-v3/types.ts b/src/dex/dodo-v3/types.ts new file mode 100644 index 000000000..a757ecd41 --- /dev/null +++ b/src/dex/dodo-v3/types.ts @@ -0,0 +1,55 @@ +import { Address, NumberAsString } from '../../types'; +import BigNumber from 'bignumber.js'; + +export type PoolState = { + D3MMAddress: Address; + depositedTokenList: Array
; +}; + +export type D3VaultState = { + tokenList: Array
; +}; + +export type DodoV3Data = { + exchange: Address; + slippageFactor?: BigNumber; +}; + +export type DexParams = { + D3Proxy: Address; + D3Vault: Address; + subgraphURL: string; +}; + +export type QuerySellOrBuyTokensResult = { + payFromAmount: bigint; + receiveToAmount: bigint; + vusdAmount: bigint; + swapFee: bigint; + mtFee: bigint; +}; + +export enum D3ProxyFunctions { + buyTokens = 'buyTokens', + sellTokens = 'sellTokens', +} + +export type D3MMsellTokenParams = { + to: Address; + fromToken: Address; + toToken: Address; + fromAmount: NumberAsString; + minReceiveAmount: NumberAsString; + data: string; +}; + +export type D3ProxySwapTokensParams = [ + pool: Address, + to: Address, + fromToken: Address, + toToken: Address, + fromAmount: NumberAsString, + minReceiveAmount: NumberAsString, + data: string, + deadLine: string, +]; diff --git a/src/dex/index.ts b/src/dex/index.ts index a4f58e878..403f1fb8c 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -80,6 +80,7 @@ import { Algebra } from './algebra/algebra'; import { QuickPerps } from './quick-perps/quick-perps'; import { NomiswapV2 } from './uniswap-v2/nomiswap-v2'; import { Dexalot } from './dexalot/dexalot'; +import { DodoV3 } from './dodo-v3/dodo-v3'; const LegacyDexes = [ CurveV2, @@ -157,6 +158,7 @@ const Dexes = [ SwaapV2, QuickPerps, NomiswapV2, + DodoV3, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder<