From 60240006afb2aa3f60e62545cd251044c09d7989 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Tue, 7 Jan 2025 19:10:57 +0200 Subject: [PATCH 1/9] 4.0.17-0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bb623d4bb..ed213e143 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.16", + "version": "4.0.17-0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 7d8f57ee0b99d3cdbf0d28a2f48c43c1c34bb3a7 Mon Sep 17 00:00:00 2001 From: Yevhen Shulha Date: Tue, 7 Jan 2025 19:49:43 +0200 Subject: [PATCH 2/9] fix: minor --- src/dex/fluid-dex/fluid-dex-e2e.test.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/dex/fluid-dex/fluid-dex-e2e.test.ts b/src/dex/fluid-dex/fluid-dex-e2e.test.ts index 41ca4f4b5..035909d3c 100644 --- a/src/dex/fluid-dex/fluid-dex-e2e.test.ts +++ b/src/dex/fluid-dex/fluid-dex-e2e.test.ts @@ -19,16 +19,6 @@ import { FluidDex } from './fluid-dex'; README ====== - This test script should add e2e tests for FluidDex. The tests - should cover as many cases as possible. Most of the DEXes follow - the following test structure: - - DexName - - ForkName + Network - - ContractMethod - - ETH -> Token swap - - Token -> ETH swap - - Token -> Token swap - The template already enumerates the basic structure which involves testing simpleSwap, multiSwap, megaSwap contract methods for ETH <> TOKEN and TOKEN <> TOKEN swaps. You should replace tokenA and From 39744862956324efb2beb7f65e61cfee96f20f20 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 18:49:32 +0200 Subject: [PATCH 3/9] fix: use correct address for poolIdenfier on Usual --- src/dex/usual/usual.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dex/usual/usual.ts b/src/dex/usual/usual.ts index b17019e5d..4f9bd683f 100644 --- a/src/dex/usual/usual.ts +++ b/src/dex/usual/usual.ts @@ -72,7 +72,7 @@ export class Usual extends SimpleExchange implements IDex { } if (this.isValidTokens(srcTokenAddress, destTokenAddress)) { - return [`${this.dexKey}_${this.config.toToken}`]; + return [`${this.dexKey}_${this.config.toToken.address}`]; } return []; From 492654ca802eb2606596bd5ed23d4b078470a254 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 18:58:23 +0200 Subject: [PATCH 4/9] feat: implement UsualPP --- src/abi/usual-pp/abi.json | 750 +++++++++++++++++++++ src/dex/index.ts | 2 + src/dex/usual-pp/config.ts | 18 + src/dex/usual-pp/pool.ts | 77 +++ src/dex/usual-pp/types.ts | 12 + src/dex/usual-pp/usual-events.test.ts | 70 ++ src/dex/usual-pp/usual-integration.test.ts | 143 ++++ src/dex/usual-pp/usual-pp.e2e.test.ts | 76 +++ src/dex/usual-pp/usual-pp.ts | 208 ++++++ src/dex/usual-pp/utils.ts | 27 + 10 files changed, 1383 insertions(+) create mode 100644 src/abi/usual-pp/abi.json create mode 100644 src/dex/usual-pp/config.ts create mode 100644 src/dex/usual-pp/pool.ts create mode 100644 src/dex/usual-pp/types.ts create mode 100644 src/dex/usual-pp/usual-events.test.ts create mode 100644 src/dex/usual-pp/usual-integration.test.ts create mode 100644 src/dex/usual-pp/usual-pp.e2e.test.ts create mode 100644 src/dex/usual-pp/usual-pp.ts create mode 100644 src/dex/usual-pp/utils.ts diff --git a/src/abi/usual-pp/abi.json b/src/abi/usual-pp/abi.json new file mode 100644 index 000000000..8f9c7e874 --- /dev/null +++ b/src/abi/usual-pp/abi.json @@ -0,0 +1,750 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "address", "name": "target", "type": "address" } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { "inputs": [], "name": "AmountIsZero", "type": "error" }, + { "inputs": [], "name": "AmountMustBeGreaterThanZero", "type": "error" }, + { "inputs": [], "name": "AmountTooBig", "type": "error" }, + { "inputs": [], "name": "ApprovalFailed", "type": "error" }, + { "inputs": [], "name": "Blacklisted", "type": "error" }, + { "inputs": [], "name": "BondFinished", "type": "error" }, + { "inputs": [], "name": "BondNotFinished", "type": "error" }, + { "inputs": [], "name": "BondNotStarted", "type": "error" }, + { "inputs": [], "name": "ECDSAInvalidSignature", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "length", "type": "uint256" } + ], + "name": "ECDSAInvalidSignatureLength", + "type": "error" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "s", "type": "bytes32" }], + "name": "ECDSAInvalidSignatureS", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "allowance", "type": "uint256" }, + { "internalType": "uint256", "name": "needed", "type": "uint256" } + ], + "name": "ERC20InsufficientAllowance", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "uint256", "name": "balance", "type": "uint256" }, + { "internalType": "uint256", "name": "needed", "type": "uint256" } + ], + "name": "ERC20InsufficientBalance", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "approver", "type": "address" } + ], + "name": "ERC20InvalidApprover", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "receiver", "type": "address" } + ], + "name": "ERC20InvalidReceiver", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "sender", "type": "address" } + ], + "name": "ERC20InvalidSender", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "ERC20InvalidSpender", + "type": "error" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "ERC2612ExpiredSignature", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "signer", "type": "address" }, + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "ERC2612InvalidSigner", + "type": "error" + }, + { "inputs": [], "name": "EnforcedPause", "type": "error" }, + { "inputs": [], "name": "ExpectedPause", "type": "error" }, + { "inputs": [], "name": "FailedInnerCall", "type": "error" }, + { "inputs": [], "name": "FloorPriceNotSet", "type": "error" }, + { "inputs": [], "name": "FloorPriceTooHigh", "type": "error" }, + { "inputs": [], "name": "InsufficientUsd0ppBalance", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "currentNonce", "type": "uint256" } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { "inputs": [], "name": "InvalidInitialization", "type": "error" }, + { "inputs": [], "name": "InvalidInput", "type": "error" }, + { "inputs": [], "name": "InvalidInputArraysLength", "type": "error" }, + { "inputs": [], "name": "MathOverflowedMulDiv", "type": "error" }, + { "inputs": [], "name": "NotAuthorized", "type": "error" }, + { "inputs": [], "name": "NotInitializing", "type": "error" }, + { "inputs": [], "name": "NotPermittedToEarlyUnlock", "type": "error" }, + { "inputs": [], "name": "NullAddress", "type": "error" }, + { "inputs": [], "name": "OutOfBounds", "type": "error" }, + { "inputs": [], "name": "OutsideEarlyUnlockTimeframe", "type": "error" }, + { "inputs": [], "name": "PARNotRequired", "type": "error" }, + { "inputs": [], "name": "PARNotSuccessful", "type": "error" }, + { "inputs": [], "name": "PARUSD0InputExceedsBalance", "type": "error" }, + { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + } + ], + "name": "BondEarlyUnlockDisabled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "BondUnwrapped", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "BondUnwrappedDuringEarlyUnlock", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address[]", + "name": "addressesToAllocateTo", + "type": "address[]" + }, + { + "indexed": false, + "internalType": "uint256[]", + "name": "earlyUnlockBalances", + "type": "uint256[]" + } + ], + "name": "EarlyUnlockBalancesSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "earlyUnlockStart", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "earlyUnlockEnd", + "type": "uint256" + } + ], + "name": "EarlyUnlockPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "account", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "balance", + "type": "uint256" + } + ], + "name": "EmergencyWithdraw", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newFloorPrice", + "type": "uint256" + } + ], + "name": "FloorPriceUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "PARMechanismActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "user", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "usd0ppAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "usd0Amount", + "type": "uint256" + } + ], + "name": "Usd0ppUnlockedFloorPrice", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "Usd0PPStorageV0Location", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "addressesToAllocateTo", + "type": "address[]" + }, + { + "internalType": "uint256[]", + "name": "balancesToAllocate", + "type": "uint256[]" + } + ], + "name": "allocateEarlyUnlockBalance", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" } + ], + "name": "allowance", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "approve", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "balanceOf", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "decimals", + "outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "internalType": "bytes1", "name": "fields", "type": "bytes1" }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, + { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "safeAccount", "type": "address" } + ], + "name": "emergencyWithdraw", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "addressToCheck", "type": "address" } + ], + "name": "getAllocationEarlyUnlock", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "getBondEarlyUnlockDisabled", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getEndTime", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getFloorPrice", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStartTime", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTemporaryUnlockEndTime", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getTemporaryUnlockStartTime", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "initializeV1", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountUsd0", "type": "uint256" } + ], + "name": "mint", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountUsd0", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "mintWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" }, + { "internalType": "address", "name": "spender", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "permit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "user", "type": "address" } + ], + "name": "setBondEarlyUnlockDisabled", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "bondEarlyUnlockStart", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "bondEarlyUnlockEnd", + "type": "uint256" + } + ], + "name": "setupEarlyUnlockPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "symbol", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amountToUnwrap", "type": "uint256" } + ], + "name": "temporaryOneToOneExitUnwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "totalBondTimes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "totalSupply", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "transfer", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "from", "type": "address" }, + { "internalType": "address", "name": "to", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" } + ], + "name": "transferFrom", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "parUsd0Amount", "type": "uint256" }, + { + "internalType": "uint256", + "name": "minimumPARMechanismGainedAmount", + "type": "uint256" + } + ], + "name": "triggerPARMechanismCurvepool", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "usd0ppAmount", "type": "uint256" } + ], + "name": "unlockUsd0ppFloorPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unwrap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "unwrapPegMaintainer", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "newFloorPrice", "type": "uint256" } + ], + "name": "updateFloorPrice", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/dex/index.ts b/src/dex/index.ts index 89e9008f8..e2de5338a 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -100,6 +100,7 @@ import { Stader } from './stader/stader'; import { UsualBond } from './usual/usual-bond'; import { UsualMWrappedM } from './usual/usual-m-wrapped-m'; import { UsualMUsd0 } from './usual/usual-m-usd0'; +import { UsualPP } from './usual-pp/usual-pp'; const LegacyDexes = [ CurveV2, @@ -194,6 +195,7 @@ const Dexes = [ FluidDex, UsualMWrappedM, UsualMUsd0, + UsualPP, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< diff --git a/src/dex/usual-pp/config.ts b/src/dex/usual-pp/config.ts new file mode 100644 index 000000000..58ecf8ddf --- /dev/null +++ b/src/dex/usual-pp/config.ts @@ -0,0 +1,18 @@ +import { Network } from '../../constants'; +import { DexConfigMap } from '../../types'; +import { DexParams } from './types'; + +export const Config: DexConfigMap = { + UsualPP: { + [Network.MAINNET]: { + USD0: { + address: '0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5', + decimals: 18, + }, + USD0PP: { + address: '0x35d8949372d46b7a3d5a56006ae77b215fc69bc0', + decimals: 18, + }, + }, + }, +}; diff --git a/src/dex/usual-pp/pool.ts b/src/dex/usual-pp/pool.ts new file mode 100644 index 000000000..b78467b38 --- /dev/null +++ b/src/dex/usual-pp/pool.ts @@ -0,0 +1,77 @@ +import _ from 'lodash'; +import { Interface } from '@ethersproject/abi'; +import { IDexHelper } from '../../dex-helper'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { Address, Log, Logger } from '../../types'; +import { AsyncOrSync, DeepReadonly } from 'ts-essentials'; +import { PoolState } from './types'; +import { getOnChainState } from './utils'; + +export class UsualPool extends StatefulEventSubscriber { + decoder = (log: Log) => this.poolInterface.parseLog(log); + + constructor( + parentName: string, + protected dexHelper: IDexHelper, + private poolAddress: Address, + private poolInterface: Interface, + logger: Logger, + ) { + super(parentName, 'UsualPool', dexHelper, logger); + this.addressesSubscribed = [poolAddress]; + } + + protected processLog( + state: DeepReadonly, + log: Readonly, + ): AsyncOrSync | null> { + const event = this.decoder(log); + const _state: PoolState = _.cloneDeep(state); + if (event.name === 'FloorPriceUpdated') { + _state.price = BigInt(event.args.newFloorPrice); + return _state; + } + return null; + } + + async generateState( + blockNumber: number | 'latest' = 'latest', + ): Promise> { + const state = await getOnChainState( + this.dexHelper.multiContract, + this.poolAddress, + this.poolInterface, + blockNumber, + ); + return state; + } + + async getPrice(blockNumber: number): Promise { + const state = await this.getOrGenerateState(blockNumber); + if (!state) throw new Error('Cannot compute state'); + return state.price; + } + + async getOrGenerateState( + blockNumber: number, + ): Promise | null> { + const state = this.getState(blockNumber); + if (state) { + return state; + } + + this.logger.debug( + `No state found for ${this.addressesSubscribed[0]}, generating new one`, + ); + const newState = await this.generateState(blockNumber); + + if (!newState) { + this.logger.debug( + `Could not regenerate state for ${this.addressesSubscribed[0]}`, + ); + return null; + } + this.setState(newState, blockNumber); + return newState; + } +} diff --git a/src/dex/usual-pp/types.ts b/src/dex/usual-pp/types.ts new file mode 100644 index 000000000..1fe3048b7 --- /dev/null +++ b/src/dex/usual-pp/types.ts @@ -0,0 +1,12 @@ +import { Address } from '../../types'; + +export type PoolState = { + price: bigint; +}; + +export type UsualPPData = {}; + +export type DexParams = { + USD0: { address: Address; decimals: number }; + USD0PP: { address: Address; decimals: number }; +}; diff --git a/src/dex/usual-pp/usual-events.test.ts b/src/dex/usual-pp/usual-events.test.ts new file mode 100644 index 000000000..1e6510122 --- /dev/null +++ b/src/dex/usual-pp/usual-events.test.ts @@ -0,0 +1,70 @@ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Network } from '../../constants'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { testEventSubscriber } from '../../../tests/utils-events'; +import { PoolState } from './types'; +import { Config } from './config'; +import { UsualPool } from './pool'; +import { PoolConfig } from '../curve-v1/types'; +import UsualPoolAbi from '../../abi/usual-pp/abi.json'; +import { Interface } from '@ethersproject/abi'; + +jest.setTimeout(50 * 1000); +const dexKey = 'UsualPP'; +const network = Network.MAINNET; +const config = Config[dexKey][network]; +const dexHelper = new DummyDexHelper(network); +const logger = dexHelper.getLogger(dexKey); + +async function fetchPoolState( + usualPool: UsualPool, + blockNumber: number, +): Promise { + return usualPool.generateState(blockNumber); +} + +function compareState(state: PoolState, expectedState: PoolState) { + expect(state).toEqual(expectedState); +} + +describe('UsualPP Event', function () { + const blockNumbers: { [eventName: string]: number[] } = { + FloorPriceUpdated: [21589702], + }; + + describe('UsualPool', function () { + let usualPool: UsualPool; + let blockNumber: number; + + const usualPoolIface = new Interface(UsualPoolAbi); + + beforeAll(async function () { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + }); + + Object.keys(blockNumbers).forEach((event: string) => { + blockNumbers[event].forEach((blockNumber: number) => { + it(`Should return the correct state after the ${blockNumber}:${event}`, async function () { + usualPool = new UsualPool( + dexKey, + dexHelper, + config.USD0PP.address, + usualPoolIface, + logger, + ); + await testEventSubscriber( + usualPool, + usualPool.addressesSubscribed, + (_blockNumber: number) => fetchPoolState(usualPool, _blockNumber), + blockNumber, + `${dexKey}_${config.USD0PP.address}`, + dexHelper.provider, + compareState, + ); + }); + }); + }); + }); +}); diff --git a/src/dex/usual-pp/usual-integration.test.ts b/src/dex/usual-pp/usual-integration.test.ts new file mode 100644 index 000000000..85e2a90a8 --- /dev/null +++ b/src/dex/usual-pp/usual-integration.test.ts @@ -0,0 +1,143 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { + checkPoolPrices, + checkConstantPoolPrices, + checkPoolsLiquidity, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { UsualPP } from './usual-pp'; + +async function testPricingOnNetwork( + usual: UsualPP, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + console.log(amounts); + + const pools = await usual.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usual.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + // Check if onchain pricing equals to calculated ones +} + +describe('UsualPP', function () { + const dexKey = 'UsualPP'; + let blockNumber: number; + let usualPP: UsualPP; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualPP = new UsualPP(network, dexKey, dexHelper); + if (usualPP.initializePricing) { + await usualPP.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualPP, + network, + dexKey, + blockNumber, + 'USD0++', + 'USD0', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: USD0', async function () { + const tokenA = Tokens[network]['USD0']; + const dexHelper = new DummyDexHelper(network); + const usualPP = new UsualPP(network, dexKey, dexHelper); + + const poolLiquidity = await usualPP.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: USD0++', async function () { + const tokenA = Tokens[network]['USD0++']; + const dexHelper = new DummyDexHelper(network); + const usualPP = new UsualPP(network, dexKey, dexHelper); + + const poolLiquidity = await usualPP.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); diff --git a/src/dex/usual-pp/usual-pp.e2e.test.ts b/src/dex/usual-pp/usual-pp.e2e.test.ts new file mode 100644 index 000000000..14ce0dee2 --- /dev/null +++ b/src/dex/usual-pp/usual-pp.e2e.test.ts @@ -0,0 +1,76 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { testE2E } from '../../../tests/utils-e2e'; +import { Tokens, Holders } from '../../../tests/constants-e2e'; +import { Network, ContractMethod, SwapSide } from '../../constants'; +import { StaticJsonRpcProvider } from '@ethersproject/providers'; +import { generateConfig } from '../../config'; + +function testForNetwork( + network: Network, + dexKey: string, + tokenASymbol: string, + tokenBSymbol: string, + tokenAAmount: string, + tokenBAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + + const sideToContractMethods = new Map([ + [SwapSide.SELL, [ContractMethod.swapExactAmountIn]], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + +describe('UsualPP E2E', () => { + const dexKey = 'UsualPP'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'USD0++'; + const tokenBSymbol: string = 'USD0'; + + const tokenAAmount: string = '1000000000000000000'; + const tokenBAmount: string = '1000000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/usual-pp/usual-pp.ts b/src/dex/usual-pp/usual-pp.ts new file mode 100644 index 000000000..c605c5d87 --- /dev/null +++ b/src/dex/usual-pp/usual-pp.ts @@ -0,0 +1,208 @@ +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + Logger, + PoolLiquidity, + DexExchangeParam, + NumberAsString, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { SimpleExchange } from '../simple-exchange'; +import { BI_POWS } from '../../bigint-constants'; +import { DexParams, UsualPPData } from './types'; +import { UsualPool } from './pool'; +import { Interface } from '@ethersproject/abi'; +import UsualPoolAbi from '../../abi/usual-pp/abi.json'; +import { Config } from './config'; +import { getDexKeysWithNetwork } from '../../utils'; +import { UsualBondData } from '../usual/types'; + +export class UsualPP extends SimpleExchange implements IDex { + readonly hasConstantPriceLargeAmounts = false; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + + private usualPool: UsualPool; + private usualPoolIface: Interface; + private config: DexParams; + + logger: Logger; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + protected adapters = {}, + ) { + super(dexHelper, dexKey); + this.config = Config[dexKey][network]; + this.logger = dexHelper.getLogger(dexKey); + this.usualPoolIface = new Interface(UsualPoolAbi); + this.usualPool = new UsualPool( + this.dexKey, + dexHelper, + this.config.USD0PP.address, + this.usualPoolIface, + this.logger, + ); + } + + async initializePricing(blockNumber: number) { + await this.usualPool.initialize(blockNumber); + } + + isUsd0PP(token: string) { + return token.toLowerCase() === this.config.USD0PP.address.toLowerCase(); + } + + isUsd0(token: string) { + return token.toLowerCase() === this.config.USD0.address.toLowerCase(); + } + + isValidTokens(srcToken: string, destToken: string) { + return this.isUsd0PP(srcToken) && this.isUsd0(destToken); + } + + getAdapters() { + return null; + } + + async getPoolIdentifiers( + srcToken: Token, + destToken: Token, + side: SwapSide, + blockNumber: number, + ): Promise { + if (!srcToken || !destToken) { + this.logger.error('Source or destination token is undefined'); + return []; + } + + const srcTokenAddress = srcToken.address?.toLowerCase(); + const destTokenAddress = destToken.address?.toLowerCase(); + + if (!srcTokenAddress || !destTokenAddress) { + this.logger.error('Source or destination token address is undefined'); + return []; + } + + if (this.isValidTokens(srcTokenAddress, destTokenAddress)) { + return [`${this.dexKey}_${this.config.USD0.address}`]; + } + + return []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + if (side === SwapSide.BUY) { + return null; + } + + const isValidSwap = this.isValidTokens(srcToken.address, destToken.address); + + if (!isValidSwap) { + return null; + } + + const price = await this.usualPool.getPrice(blockNumber); + const unitOut = price; + + const amountsOut = amounts.map( + amount => (amount * price) / BI_POWS[this.config.USD0PP.decimals], + ); + + return [ + { + unit: unitOut, + prices: amountsOut, + data: {}, + poolAddresses: [this.config.USD0.address], + exchange: this.dexKey, + gasCost: 70000, + poolIdentifier: this.dexKey, + }, + ]; + } + + getCalldataGasCost(poolPrices: PoolPrices): number | number[] { + return CALLDATA_GAS_COST.DEX_NO_PAYLOAD; + } + + getAdapterParam( + srcToken: string, + destToken: string, + srcAmount: string, + destAmount: string, + data: UsualPPData, + side: SwapSide, + ): AdapterExchangeParam { + const payload = '0x'; + + return { + targetExchange: this.config.USD0.address, + payload, + networkFee: '0', + }; + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const isUsd0PP = this.isUsd0PP(tokenAddress); + const isUsd0 = this.isUsd0(tokenAddress); + + if (!(isUsd0PP || isUsd0)) return []; + + return [ + { + exchange: this.dexKey, + address: this.config.USD0PP.address, + connectorTokens: [isUsd0PP ? this.config.USD0 : this.config.USD0PP], + liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred + }, + ]; + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: UsualBondData, + side: SwapSide, + ): Promise { + if (this.isUsd0PP(srcToken) && this.isUsd0(destToken)) { + const exchangeData = this.usualPoolIface.encodeFunctionData( + 'unlockUsd0ppFloorPrice', + [srcAmount], + ); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: this.config.USD0PP.address, + returnAmountPos: undefined, + }; + } + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual-pp/utils.ts b/src/dex/usual-pp/utils.ts new file mode 100644 index 000000000..78eda170c --- /dev/null +++ b/src/dex/usual-pp/utils.ts @@ -0,0 +1,27 @@ +import { Contract } from 'web3-eth-contract'; +import { Interface } from '@ethersproject/abi'; +import { PoolState } from './types'; + +export async function getOnChainState( + multiContract: Contract, + poolAddress: string, + poolInterface: Interface, + blockNumber: number | 'latest', +): Promise { + const data: { returnData: any[] } = await multiContract.methods + .aggregate([ + { + target: poolAddress, + callData: poolInterface.encodeFunctionData('getFloorPrice', []), + }, + ]) + .call({}, blockNumber); + + const price = BigInt( + poolInterface.decodeFunctionResult('getFloorPrice', data.returnData[0])[0], + ); + + return { + price, + }; +} From 70a6d64541903739ef518ac0b118a9f6eb74f404 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 18:59:00 +0200 Subject: [PATCH 5/9] 4.0.17-usual-pp.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ed213e143..fbc4930cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.17-0", + "version": "4.0.17-usual-pp.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 1cd346d392b4d11f092689e4e6e2549f48638dde Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 19:30:15 +0200 Subject: [PATCH 6/9] 4.0.17 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 231ba14a5..3f96588c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.17-usual-pp.0", + "version": "4.0.17", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 202f9a874c2f91b9d02c0b59b548435b1cc2f7e7 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 19:32:51 +0200 Subject: [PATCH 7/9] chore: use `UsualPPData` type in getDexParam on UsualPP --- src/dex/usual-pp/usual-pp.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dex/usual-pp/usual-pp.ts b/src/dex/usual-pp/usual-pp.ts index c605c5d87..4e34e2303 100644 --- a/src/dex/usual-pp/usual-pp.ts +++ b/src/dex/usual-pp/usual-pp.ts @@ -21,7 +21,6 @@ import { Interface } from '@ethersproject/abi'; import UsualPoolAbi from '../../abi/usual-pp/abi.json'; import { Config } from './config'; import { getDexKeysWithNetwork } from '../../utils'; -import { UsualBondData } from '../usual/types'; export class UsualPP extends SimpleExchange implements IDex { readonly hasConstantPriceLargeAmounts = false; @@ -186,7 +185,7 @@ export class UsualPP extends SimpleExchange implements IDex { srcAmount: NumberAsString, destAmount: NumberAsString, recipient: Address, - data: UsualBondData, + data: UsualPPData, side: SwapSide, ): Promise { if (this.isUsd0PP(srcToken) && this.isUsd0(destToken)) { From f08fbb73685b23e26cd508a75c02aa867e470bd2 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 19:34:14 +0200 Subject: [PATCH 8/9] 4.0.18 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3f96588c3..d075d3bb0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.17", + "version": "4.0.18", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 32bae6e89e130f91e37a4f7b75a50957f31f55c6 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Fri, 10 Jan 2025 19:37:04 +0200 Subject: [PATCH 9/9] 4.0.19 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d075d3bb0..3c8f5778d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.18", + "version": "4.0.19", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib",