From 2108e064319bf14f98c321a8acd4762d3e9e3560 Mon Sep 17 00:00:00 2001 From: Bob Baxley Date: Mon, 21 Oct 2024 11:26:55 -0400 Subject: [PATCH 01/27] swap past max tick activeTick correction --- src/dex/maverick-v2/maverick-math/maverick-pool-math.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dex/maverick-v2/maverick-math/maverick-pool-math.ts b/src/dex/maverick-v2/maverick-math/maverick-pool-math.ts index f487fe7ef..02e0e956b 100644 --- a/src/dex/maverick-v2/maverick-math/maverick-pool-math.ts +++ b/src/dex/maverick-v2/maverick-math/maverick-pool-math.ts @@ -634,7 +634,7 @@ export class MaverickPoolMath { activeTick += delta.tokenAIn ? 1n : -1n; if (MaverickDeltaMath.pastMaxTick(delta, activeTick)) { - this.state.activeTick += delta.tokenAIn ? 1n : -1n; + this.state.activeTick += delta.tokenAIn ? -1n : 1n; return delta; } } From e730040a7b4fd03aa00880bb9ed4ab0c5635b1cb Mon Sep 17 00:00:00 2001 From: toninorair Date: Tue, 26 Nov 2024 00:34:35 -0500 Subject: [PATCH 02/27] Add SmartM-->UsualM-->USD0 --- src/abi/usual-m-smart-m/usualM.abi.json | 483 +++++++++++++ .../usual-m-usd0/usualCollateralDao.abi.json | 681 ++++++++++++++++++ src/dex/index.ts | 4 + src/dex/usual-m-smart-m/config.ts | 12 + src/dex/usual-m-smart-m/types.ts | 10 + .../usual-m-smart-m-e2e.test.ts | 76 ++ .../usual-m-smart-m-integration.test.ts | 144 ++++ src/dex/usual-m-smart-m/usual-m-smart-m.ts | 216 ++++++ src/dex/usual-m-usd0/config.ts | 13 + src/dex/usual-m-usd0/types.ts | 11 + src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts | 76 ++ .../usual-m-usd0-integration.test.ts | 144 ++++ src/dex/usual-m-usd0/usual-m-usd0.ts | 215 ++++++ 13 files changed, 2085 insertions(+) create mode 100644 src/abi/usual-m-smart-m/usualM.abi.json create mode 100644 src/abi/usual-m-usd0/usualCollateralDao.abi.json create mode 100644 src/dex/usual-m-smart-m/config.ts create mode 100644 src/dex/usual-m-smart-m/types.ts create mode 100644 src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts create mode 100644 src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts create mode 100644 src/dex/usual-m-smart-m/usual-m-smart-m.ts create mode 100644 src/dex/usual-m-usd0/config.ts create mode 100644 src/dex/usual-m-usd0/types.ts create mode 100644 src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts create mode 100644 src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts create mode 100644 src/dex/usual-m-usd0/usual-m-usd0.ts diff --git a/src/abi/usual-m-smart-m/usualM.abi.json b/src/abi/usual-m-smart-m/usualM.abi.json new file mode 100644 index 000000000..96818b251 --- /dev/null +++ b/src/abi/usual-m-smart-m/usualM.abi.json @@ -0,0 +1,483 @@ +[ + { "type": "constructor", "inputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "DOMAIN_SEPARATOR", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UsualMStorageV0Location", + "inputs": [], + "outputs": [{ "name": "", "type": "bytes32", "internalType": "bytes32" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "allowance", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "approve", + "inputs": [ + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "balanceOf", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "blacklist", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "decimals", + "inputs": [], + "outputs": [{ "name": "", "type": "uint8", "internalType": "uint8" }], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "eip712Domain", + "inputs": [], + "outputs": [ + { "name": "fields", "type": "bytes1", "internalType": "bytes1" }, + { "name": "name", "type": "string", "internalType": "string" }, + { "name": "version", "type": "string", "internalType": "string" }, + { "name": "chainId", "type": "uint256", "internalType": "uint256" }, + { + "name": "verifyingContract", + "type": "address", + "internalType": "address" + }, + { "name": "salt", "type": "bytes32", "internalType": "bytes32" }, + { + "name": "extensions", + "type": "uint256[]", + "internalType": "uint256[]" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { "name": "smartM_", "type": "address", "internalType": "address" }, + { + "name": "registryAccess_", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "isBlacklisted", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "name", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "nonces", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "paused", + "inputs": [], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "permit", + "inputs": [ + { "name": "owner", "type": "address", "internalType": "address" }, + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "registryAccess", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "smartM", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "symbol", + "inputs": [], + "outputs": [{ "name": "", "type": "string", "internalType": "string" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "totalSupply", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, + { + "type": "function", + "name": "transfer", + "inputs": [ + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "transferFrom", + "inputs": [ + { "name": "from", "type": "address", "internalType": "address" }, + { "name": "to", "type": "address", "internalType": "address" }, + { "name": "value", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unBlacklist", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unpause", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unwrap", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "unwrap", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrap", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrap", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "wrapWithPermit", + "inputs": [ + { "name": "recipient", "type": "address", "internalType": "address" }, + { "name": "amount", "type": "uint256", "internalType": "uint256" }, + { "name": "deadline", "type": "uint256", "internalType": "uint256" }, + { "name": "v", "type": "uint8", "internalType": "uint8" }, + { "name": "r", "type": "bytes32", "internalType": "bytes32" }, + { "name": "s", "type": "bytes32", "internalType": "bytes32" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "nonpayable" + }, + { + "type": "event", + "name": "Approval", + "inputs": [ + { + "name": "owner", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "spender", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Blacklist", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "EIP712DomainChanged", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Paused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Transfer", + "inputs": [ + { + "name": "from", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "to", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "value", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "UnBlacklist", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Unpaused", + "inputs": [ + { + "name": "account", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { "type": "error", "name": "Blacklisted", "inputs": [] }, + { "type": "error", "name": "ECDSAInvalidSignature", "inputs": [] }, + { + "type": "error", + "name": "ECDSAInvalidSignatureLength", + "inputs": [ + { "name": "length", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ECDSAInvalidSignatureS", + "inputs": [{ "name": "s", "type": "bytes32", "internalType": "bytes32" }] + }, + { + "type": "error", + "name": "ERC20InsufficientAllowance", + "inputs": [ + { "name": "spender", "type": "address", "internalType": "address" }, + { "name": "allowance", "type": "uint256", "internalType": "uint256" }, + { "name": "needed", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ERC20InsufficientBalance", + "inputs": [ + { "name": "sender", "type": "address", "internalType": "address" }, + { "name": "balance", "type": "uint256", "internalType": "uint256" }, + { "name": "needed", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidApprover", + "inputs": [ + { "name": "approver", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidReceiver", + "inputs": [ + { "name": "receiver", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSender", + "inputs": [ + { "name": "sender", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC20InvalidSpender", + "inputs": [ + { "name": "spender", "type": "address", "internalType": "address" } + ] + }, + { + "type": "error", + "name": "ERC2612ExpiredSignature", + "inputs": [ + { "name": "deadline", "type": "uint256", "internalType": "uint256" } + ] + }, + { + "type": "error", + "name": "ERC2612InvalidSigner", + "inputs": [ + { "name": "signer", "type": "address", "internalType": "address" }, + { "name": "owner", "type": "address", "internalType": "address" } + ] + }, + { "type": "error", "name": "EnforcedPause", "inputs": [] }, + { "type": "error", "name": "ExpectedPause", "inputs": [] }, + { + "type": "error", + "name": "InvalidAccountNonce", + "inputs": [ + { "name": "account", "type": "address", "internalType": "address" }, + { "name": "currentNonce", "type": "uint256", "internalType": "uint256" } + ] + }, + { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { "type": "error", "name": "NotAuthorized", "inputs": [] }, + { "type": "error", "name": "NotInitializing", "inputs": [] }, + { "type": "error", "name": "SameValue", "inputs": [] }, + { "type": "error", "name": "ZeroAddress", "inputs": [] }, + { "type": "error", "name": "ZeroRegistryAccess", "inputs": [] }, + { "type": "error", "name": "ZeroSmartM", "inputs": [] } +] diff --git a/src/abi/usual-m-usd0/usualCollateralDao.abi.json b/src/abi/usual-m-usd0/usualCollateralDao.abi.json new file mode 100644 index 000000000..9fe20473a --- /dev/null +++ b/src/abi/usual-m-usd0/usualCollateralDao.abi.json @@ -0,0 +1,681 @@ +[ + { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, + { + "inputs": [ + { "internalType": "address", "name": "target", "type": "address" } + ], + "name": "AddressEmptyCode", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" } + ], + "name": "AddressInsufficientBalance", + "type": "error" + }, + { "inputs": [], "name": "AmountIsZero", "type": "error" }, + { "inputs": [], "name": "AmountTooBig", "type": "error" }, + { "inputs": [], "name": "AmountTooLow", "type": "error" }, + { "inputs": [], "name": "ApprovalFailed", "type": "error" }, + { "inputs": [], "name": "CBRIsNull", "type": "error" }, + { "inputs": [], "name": "CBRIsTooHigh", "type": "error" }, + { "inputs": [], "name": "EnforcedPause", "type": "error" }, + { "inputs": [], "name": "ExpectedPause", "type": "error" }, + { + "inputs": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" } + ], + "name": "ExpiredSignature", + "type": "error" + }, + { "inputs": [], "name": "FailedInnerCall", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "currentNonce", "type": "uint256" } + ], + "name": "InvalidAccountNonce", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "approvalDeadline", + "type": "uint256" + }, + { "internalType": "uint256", "name": "intentDeadline", "type": "uint256" } + ], + "name": "InvalidDeadline", + "type": "error" + }, + { "inputs": [], "name": "InvalidInitialization", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "account", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "InvalidOrderAmount", + "type": "error" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "InvalidSigner", + "type": "error" + }, + { "inputs": [], "name": "InvalidToken", "type": "error" }, + { "inputs": [], "name": "MathOverflowedMulDiv", "type": "error" }, + { "inputs": [], "name": "NoOrdersIdsProvided", "type": "error" }, + { "inputs": [], "name": "NotAuthorized", "type": "error" }, + { "inputs": [], "name": "NotInitializing", "type": "error" }, + { "inputs": [], "name": "NullContract", "type": "error" }, + { "inputs": [], "name": "RedeemFeeTooBig", "type": "error" }, + { "inputs": [], "name": "RedeemMustBePaused", "type": "error" }, + { "inputs": [], "name": "RedeemMustNotBePaused", "type": "error" }, + { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, + { + "inputs": [ + { "internalType": "address", "name": "token", "type": "address" } + ], + "name": "SafeERC20FailedOperation", + "type": "error" + }, + { "inputs": [], "name": "SameValue", "type": "error" }, + { "inputs": [], "name": "SwapMustBePaused", "type": "error" }, + { "inputs": [], "name": "SwapMustNotBePaused", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "cbrCoef", + "type": "uint256" + } + ], + "name": "CBRActivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "CBRDeactivated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [], + "name": "EIP712DomainChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint64", + "name": "version", + "type": "uint64" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenSwapped", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "totalAmountInTokenDecimals", + "type": "uint256" + } + ], + "name": "IntentConsumed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenSwapped", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountInTokenDecimals", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountInUSD", + "type": "uint256" + } + ], + "name": "IntentMatched", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "indexed": true, + "internalType": "uint256", + "name": "nonceInvalidated", + "type": "uint256" + } + ], + "name": "NonceInvalidated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "newThreshold", + "type": "uint256" + } + ], + "name": "NonceThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "redeemer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "rwaToken", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountRedeemed", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "returnedRwaAmount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "stableFeeAmount", + "type": "uint256" + } + ], + "name": "Redeem", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "redeemFee", + "type": "uint256" + } + ], + "name": "RedeemFeeUpdated", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "RedeemPaused", "type": "event" }, + { + "anonymous": false, + "inputs": [], + "name": "RedeemUnPaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "tokenSwapped", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountInUSD", + "type": "uint256" + } + ], + "name": "Swap", + "type": "event" + }, + { "anonymous": false, "inputs": [], "name": "SwapPaused", "type": "event" }, + { "anonymous": false, "inputs": [], "name": "SwapUnPaused", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DaoCollateralStorageV0Location", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "coefficient", "type": "uint256" } + ], + "name": "activateCBR", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "cbrCoef", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "deactivateCBR", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "eip712Domain", + "outputs": [ + { "internalType": "bytes1", "name": "fields", "type": "bytes1" }, + { "internalType": "string", "name": "name", "type": "string" }, + { "internalType": "string", "name": "version", "type": "string" }, + { "internalType": "uint256", "name": "chainId", "type": "uint256" }, + { + "internalType": "address", + "name": "verifyingContract", + "type": "address" + }, + { "internalType": "bytes32", "name": "salt", "type": "bytes32" }, + { "internalType": "uint256[]", "name": "extensions", "type": "uint256[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryContract", + "type": "address" + }, + { "internalType": "uint256", "name": "_redeemFee", "type": "uint256" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_registryContract", + "type": "address" + } + ], + "name": "initializeV1", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "invalidateNonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "newNonce", "type": "uint256" } + ], + "name": "invalidateUpToNonce", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isCBROn", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isRedeemPaused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isSwapPaused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "nonceThreshold", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "nonces", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "owner", "type": "address" } + ], + "name": "orderAmountTakenCurrentNonce", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseSwap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } + ], + "name": "redeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" } + ], + "name": "redeemDao", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "redeemFee", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "threshold", "type": "uint256" } + ], + "name": "setNonceThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "_redeemFee", "type": "uint256" } + ], + "name": "setRedeemFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } + ], + "name": "swap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { + "internalType": "uint256", + "name": "amountInTokenDecimals", + "type": "uint256" + }, + { "internalType": "bool", "name": "partialMatching", "type": "bool" }, + { + "internalType": "uint256[]", + "name": "orderIdsToTake", + "type": "uint256[]" + }, + { + "components": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct Approval", + "name": "approval", + "type": "tuple" + } + ], + "name": "swapRWAtoStbc", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256[]", + "name": "orderIdsToTake", + "type": "uint256[]" + }, + { + "components": [ + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "internalType": "struct Approval", + "name": "approval", + "type": "tuple" + }, + { + "components": [ + { "internalType": "address", "name": "recipient", "type": "address" }, + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { + "internalType": "uint256", + "name": "amountInTokenDecimals", + "type": "uint256" + }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "bytes", "name": "signature", "type": "bytes" } + ], + "internalType": "struct Intent", + "name": "intent", + "type": "tuple" + }, + { "internalType": "bool", "name": "partialMatching", "type": "bool" } + ], + "name": "swapRWAtoStbcIntent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "rwaToken", "type": "address" }, + { "internalType": "uint256", "name": "amount", "type": "uint256" }, + { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, + { "internalType": "uint256", "name": "deadline", "type": "uint256" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "swapWithPermit", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseRedeem", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "unpauseSwap", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/dex/index.ts b/src/dex/index.ts index 3b759aaf7..432b433dc 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -94,6 +94,8 @@ import { LitePsm } from './lite-psm/lite-psm'; import { UsualBond } from './usual-bond/usual-bond'; import { StkGHO } from './stkgho/stkgho'; import { SkyConverter } from './sky-converter/sky-converter'; +import { UsualMSmartM } from './usual-m-smart-m/usual-m-smart-m'; +import { UsualMUsd0 } from './usual-m-usd0/usual-m-usd0'; const LegacyDexes = [ CurveV2, @@ -183,6 +185,8 @@ const Dexes = [ StkGHO, SkyConverter, FluidDex, + UsualMSmartM, + UsualMUsd0, ]; export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder< diff --git a/src/dex/usual-m-smart-m/config.ts b/src/dex/usual-m-smart-m/config.ts new file mode 100644 index 000000000..ab8e3cde8 --- /dev/null +++ b/src/dex/usual-m-smart-m/config.ts @@ -0,0 +1,12 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const UsualMSmartMConfig: DexConfigMap = { + UsualMSmartM: { + [Network.MAINNET]: { + smartMAddress: '0x437cc33344a0B27A429f795ff6B469C72698B291', + usualMAddress: '0x0000000000000000000000000000000000000000', //TODO: replace with actual address + }, + }, +}; diff --git a/src/dex/usual-m-smart-m/types.ts b/src/dex/usual-m-smart-m/types.ts new file mode 100644 index 000000000..0684a394e --- /dev/null +++ b/src/dex/usual-m-smart-m/types.ts @@ -0,0 +1,10 @@ +import { Address } from '../../types'; + +export type PoolState = {}; + +export type UsualMSmartMData = {}; + +export type DexParams = { + smartMAddress: Address; + usualMAddress: Address; +}; diff --git a/src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts b/src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts new file mode 100644 index 000000000..530395578 --- /dev/null +++ b/src/dex/usual-m-smart-m/usual-m-smart-m-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('UsualMSmartM E2E', () => { + const dexKey = 'UsualMSmartM'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'SmartM'; + const tokenBSymbol: string = 'UsualM'; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts b/src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts new file mode 100644 index 000000000..a647324ca --- /dev/null +++ b/src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts @@ -0,0 +1,144 @@ +/* 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 { UsualMSmartM } from './usual-m-smart-m'; +import { + checkPoolPrices, + checkConstantPoolPrices, + checkPoolsLiquidity, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +async function testPricingOnNetwork( + usualMSmartM: UsualMSmartM, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenAddress: string, + destTokenAddress: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + console.log(amounts); + + const pools = await usualMSmartM.getPoolIdentifiers( + networkTokens['SmartM'], + networkTokens['UsualM'], + side, + blockNumber, + ); + console.log(`${'SmartM'} <> ${'UsualM'} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usualMSmartM.getPricesVolume( + networkTokens['SmartM'], + networkTokens['UsualM'], + amounts, + side, + blockNumber, + pools, + ); + console.log(`${'SmartM'} <> ${'UsualM'} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + if (usualMSmartM.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + // Check if onchain pricing equals to calculated ones + checkPoolPrices(poolPrices!, amounts, side, dexKey); +} + +describe('SmartM<>UsualM', function () { + const dexKey = 'UsualMSmartM'; + let blockNumber: number; + let usualMSmartM: UsualMSmartM; + + 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(); + usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + if (usualMSmartM.initializePricing) { + await usualMSmartM.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualMSmartM, + network, + dexKey, + blockNumber, + 'SmartM', + 'UsualM', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: SmartM', async function () { + const tokenA = Tokens[network]['SmartM']; + const dexHelper = new DummyDexHelper(network); + const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + + const poolLiquidity = await usualMSmartM.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: UsualM', async function () { + const tokenA = Tokens[network]['UsualM']; + const dexHelper = new DummyDexHelper(network); + const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + + const poolLiquidity = await usualMSmartM.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-m-smart-m/usual-m-smart-m.ts b/src/dex/usual-m-smart-m/usual-m-smart-m.ts new file mode 100644 index 000000000..a162d51a1 --- /dev/null +++ b/src/dex/usual-m-smart-m/usual-m-smart-m.ts @@ -0,0 +1,216 @@ +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + Logger, + NumberAsString, + DexExchangeParam, + PoolLiquidity, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getDexKeysWithNetwork } from '../../utils'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { UsualMSmartMData, DexParams } from './types'; +import { SimpleExchange } from '../simple-exchange'; +import { UsualMSmartMConfig } from './config'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import USUALM_ABI from '../../abi/usual-m-smart-m/usualM.abi.json'; +import { BI_POWS } from '../../bigint-constants'; + +export class UsualMSmartM + extends SimpleExchange + implements IDex +{ + protected config: DexParams; + + readonly hasConstantPriceLargeAmounts = true; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(UsualMSmartMConfig); + + usualMIface: Interface; + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(dexHelper, dexKey); + const config = UsualMSmartMConfig[dexKey][network]; + this.usualMIface = new Interface(USUALM_ABI as JsonFragment[]); + this.config = { + smartMAddress: config.smartMAddress.toLowerCase(), + usualMAddress: config.usualMAddress.toLowerCase(), + }; + this.logger = dexHelper.getLogger(dexKey); + } + + async initializePricing(blockNumber: number) { + // No initialization needed for constant price + } + + getConfig() { + return this.config; + } + + is_smartM(token: string) { + return token.toLowerCase() === this.config.smartMAddress.toLowerCase(); + } + + is_usualM(token: string) { + return token.toLowerCase() === this.config.usualMAddress.toLowerCase(); + } + + is_usualM_swap_token(srcToken: string, destToken: string) { + return this.is_smartM(srcToken) && this.is_usualM(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.is_usualM_swap_token(srcTokenAddress, destTokenAddress)) { + return [`${this.dexKey}_${this.config.usualMAddress}`]; + } + + return []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + // TODO: check is SwapSide is required + // if (side === SwapSide.BUY) { + // return null; + // } + + const isUsualMSwapToken = this.is_usualM_swap_token( + srcToken.address, + destToken.address, + ); + + if (!isUsualMSwapToken) { + return null; + } + + const unitOut = BI_POWS[18]; // 1:1 swap + const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input + + return [ + { + unit: unitOut, + prices: amountsOut, + data: {}, + poolAddresses: [this.config.usualMAddress], + 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: UsualMSmartMData, + side: SwapSide, + ): AdapterExchangeParam { + const payload = '0x'; + + return { + targetExchange: this.config.usualMAddress, + payload, + networkFee: '0', + }; + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: UsualMSmartMData, + side: SwapSide, + ): Promise { + if (this.is_smartM(srcToken) && this.is_usualM(destToken)) { + const exchangeData = this.usualMIface.encodeFunctionData('wrap', [ + recipient, + srcAmount, + ]); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: this.config.usualMAddress, + returnAmountPos: undefined, + }; + } + + throw new Error('LOGIC ERROR'); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const isSmartM = this.is_smartM(tokenAddress); + if (!isSmartM && !this.is_usualM(tokenAddress)) return []; + + return [ + { + exchange: this.dexKey, + address: this.config.usualMAddress, + connectorTokens: [ + { + decimals: 6, + address: isSmartM + ? this.config.usualMAddress + : this.config.smartMAddress, + }, + ], + liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred + }, + ]; + } +} diff --git a/src/dex/usual-m-usd0/config.ts b/src/dex/usual-m-usd0/config.ts new file mode 100644 index 000000000..5a2a636bd --- /dev/null +++ b/src/dex/usual-m-usd0/config.ts @@ -0,0 +1,13 @@ +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; +import { Network, SwapSide } from '../../constants'; + +export const UsualMUsd0Config: DexConfigMap = { + UsualMUsd0: { + [Network.MAINNET]: { + usualMAddress: '0x0000000000000000000000000000000000000000', //TODO: replace with actual address + usd0Address: '0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5', + usualDaoCollateralAddress: '0xde6e1F680C4816446C8D515989E2358636A38b04', + }, + }, +}; diff --git a/src/dex/usual-m-usd0/types.ts b/src/dex/usual-m-usd0/types.ts new file mode 100644 index 000000000..43ff7d8c8 --- /dev/null +++ b/src/dex/usual-m-usd0/types.ts @@ -0,0 +1,11 @@ +import { Address } from '../../types'; + +export type PoolState = {}; + +export type UsualMUsd0Data = {}; + +export type DexParams = { + usualMAddress: Address; + usd0Address: Address; + usualDaoCollateralAddress: Address; +}; diff --git a/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts b/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts new file mode 100644 index 000000000..031593d27 --- /dev/null +++ b/src/dex/usual-m-usd0/usual-m-usd0-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('UsualM<>Usd0 E2E', () => { + const dexKey = 'UsualMSmartM'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'UsualM'; + const tokenBSymbol: string = 'USD0'; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts b/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts new file mode 100644 index 000000000..f00074b60 --- /dev/null +++ b/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts @@ -0,0 +1,144 @@ +/* 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 { UsualMUsd0 } from './usual-m-usd0'; +import { + checkPoolPrices, + checkConstantPoolPrices, + checkPoolsLiquidity, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; + +async function testPricingOnNetwork( + usualMSmartM: UsualMUsd0, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenAddress: string, + destTokenAddress: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + console.log(amounts); + + const pools = await usualMSmartM.getPoolIdentifiers( + networkTokens['UsualM'], + networkTokens['USD0'], + side, + blockNumber, + ); + console.log(`${'UsualM'} <> ${'USD0'} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usualMSmartM.getPricesVolume( + networkTokens['UsualM'], + networkTokens['USD0'], + amounts, + side, + blockNumber, + pools, + ); + console.log(`${'UsualM'} <> ${'USD0'} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + if (usualMSmartM.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + // Check if onchain pricing equals to calculated ones + checkPoolPrices(poolPrices!, amounts, side, dexKey); +} + +describe('UsualM<>USD0', function () { + const dexKey = 'UsualMUSD0'; + let blockNumber: number; + let usualMUsd0: UsualMUsd0; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + if (usualMUsd0.initializePricing) { + await usualMUsd0.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualMUsd0, + network, + dexKey, + blockNumber, + 'UsualM', + 'USD0', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: UsualM', async function () { + const tokenA = Tokens[network]['UsualM']; + const dexHelper = new DummyDexHelper(network); + const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + + const poolLiquidity = await usualMUsd0.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: USD0', async function () { + const tokenA = Tokens[network]['USD0']; + const dexHelper = new DummyDexHelper(network); + const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + + const poolLiquidity = await usualMUsd0.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); diff --git a/src/dex/usual-m-usd0/usual-m-usd0.ts b/src/dex/usual-m-usd0/usual-m-usd0.ts new file mode 100644 index 000000000..a0028a572 --- /dev/null +++ b/src/dex/usual-m-usd0/usual-m-usd0.ts @@ -0,0 +1,215 @@ +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + Logger, + NumberAsString, + DexExchangeParam, + PoolLiquidity, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; +import { getDexKeysWithNetwork } from '../../utils'; +import { IDex } from '../idex'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { UsualMUsd0Data, DexParams } from './types'; +import { SimpleExchange } from '../simple-exchange'; +import { UsualMUsd0Config } from './config'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import USUAL_DAO_COLLATERAL_ABI from '../../abi/usual-m-usd0/usualCollateralDao.abi.json'; +import { BI_POWS } from '../../bigint-constants'; + +export class UsualMUsd0 extends SimpleExchange implements IDex { + protected config: DexParams; + + readonly hasConstantPriceLargeAmounts = true; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(UsualMUsd0Config); + + usualDaoCollateralIface: Interface; + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(dexHelper, dexKey); + const config = UsualMUsd0Config[dexKey][network]; + this.usualDaoCollateralIface = new Interface( + USUAL_DAO_COLLATERAL_ABI as JsonFragment[], + ); + this.config = { + usualMAddress: config.usualMAddress.toLowerCase(), + usd0Address: config.usd0Address.toLowerCase(), + }; + this.logger = dexHelper.getLogger(dexKey); + } + + async initializePricing(blockNumber: number) { + // No initialization needed for constant price + } + + getConfig() { + return this.config; + } + + is_usualM(token: string) { + return token.toLowerCase() === this.config.usualMAddress.toLowerCase(); + } + + is_usd0(token: string) { + return token.toLowerCase() === this.config.usd0Address.toLowerCase(); + } + + is_usd0_swap_token(srcToken: string, destToken: string) { + return this.is_usualM(srcToken) && this.is_usd0(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.is_usd0_swap_token(srcTokenAddress, destTokenAddress)) { + return [`${this.dexKey}_${this.config.usd0Address}`]; + } + + return []; + } + + async getPricesVolume( + srcToken: Token, + destToken: Token, + amounts: bigint[], + side: SwapSide, + blockNumber: number, + limitPools?: string[], + ): Promise> { + // TODO: check is SwapSide is required + // if (side === SwapSide.BUY) { + // return null; + // } + + const isUsd0SwapToken = this.is_usd0_swap_token( + srcToken.address, + destToken.address, + ); + + if (!isUsd0SwapToken) { + return null; + } + + const unitOut = BI_POWS[18]; // 1:1 swap + const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input + + return [ + { + unit: unitOut, + prices: amountsOut, + data: {}, + poolAddresses: [this.config.usd0Address], + 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: UsualMUsd0Data, + side: SwapSide, + ): AdapterExchangeParam { + const payload = '0x'; + + return { + targetExchange: this.config.usualMAddress, + payload, + networkFee: '0', + }; + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: UsualMUsd0Data, + side: SwapSide, + ): Promise { + if (this.is_usualM(srcToken) && this.is_usd0(destToken)) { + const exchangeData = this.usualDaoCollateralIface.encodeFunctionData( + 'swap', + [srcToken, srcAmount, destAmount], + ); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: this.config.usualDaoCollateralAddress, + returnAmountPos: undefined, + }; + } + + throw new Error('LOGIC ERROR'); + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const isUsualM = this.is_usualM(tokenAddress); + if (!isUsualM && !this.is_usd0(tokenAddress)) return []; + + return [ + { + exchange: this.dexKey, + address: this.config.usualMAddress, + connectorTokens: [ + { + decimals: isUsualM ? 6 : 18, // TODO: check this one + address: isUsualM + ? this.config.usd0Address + : this.config.usualMAddress, + }, + ], + liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred + }, + ]; + } +} From d52dfcb620fc7c755d70c2f86ba9cd8095047fab Mon Sep 17 00:00:00 2001 From: toninorair Date: Tue, 26 Nov 2024 00:35:25 -0500 Subject: [PATCH 03/27] Add SmartM-->UsualM-->USD0 --- src/dex/usual-m-usd0/usual-m-usd0.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dex/usual-m-usd0/usual-m-usd0.ts b/src/dex/usual-m-usd0/usual-m-usd0.ts index a0028a572..0a526da90 100644 --- a/src/dex/usual-m-usd0/usual-m-usd0.ts +++ b/src/dex/usual-m-usd0/usual-m-usd0.ts @@ -47,6 +47,7 @@ export class UsualMUsd0 extends SimpleExchange implements IDex { this.config = { usualMAddress: config.usualMAddress.toLowerCase(), usd0Address: config.usd0Address.toLowerCase(), + usualDaoCollateralAddress: config.usualDaoCollateralAddress.toLowerCase(), }; this.logger = dexHelper.getLogger(dexKey); } From 3e7bcc9f78546851f500bde5305fe7333d034558 Mon Sep 17 00:00:00 2001 From: toninorair Date: Tue, 26 Nov 2024 00:45:29 -0500 Subject: [PATCH 04/27] Uncomment sell side --- src/dex/usual-m-smart-m/usual-m-smart-m.ts | 7 +++---- src/dex/usual-m-usd0/usual-m-usd0.ts | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/dex/usual-m-smart-m/usual-m-smart-m.ts b/src/dex/usual-m-smart-m/usual-m-smart-m.ts index a162d51a1..60f001be5 100644 --- a/src/dex/usual-m-smart-m/usual-m-smart-m.ts +++ b/src/dex/usual-m-smart-m/usual-m-smart-m.ts @@ -110,10 +110,9 @@ export class UsualMSmartM blockNumber: number, limitPools?: string[], ): Promise> { - // TODO: check is SwapSide is required - // if (side === SwapSide.BUY) { - // return null; - // } + if (side === SwapSide.BUY) { + return null; + } const isUsualMSwapToken = this.is_usualM_swap_token( srcToken.address, diff --git a/src/dex/usual-m-usd0/usual-m-usd0.ts b/src/dex/usual-m-usd0/usual-m-usd0.ts index 0a526da90..9d3c89d82 100644 --- a/src/dex/usual-m-usd0/usual-m-usd0.ts +++ b/src/dex/usual-m-usd0/usual-m-usd0.ts @@ -110,10 +110,9 @@ export class UsualMUsd0 extends SimpleExchange implements IDex { blockNumber: number, limitPools?: string[], ): Promise> { - // TODO: check is SwapSide is required - // if (side === SwapSide.BUY) { - // return null; - // } + if (side === SwapSide.BUY) { + return null; + } const isUsd0SwapToken = this.is_usd0_swap_token( srcToken.address, From cc2c278e87a2b8566a95e9b2ec5a5287cbb376b5 Mon Sep 17 00:00:00 2001 From: toninorair Date: Tue, 26 Nov 2024 10:57:05 -0500 Subject: [PATCH 05/27] Add fix for recipient --- src/dex/usual-m-smart-m/usual-m-smart-m.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dex/usual-m-smart-m/usual-m-smart-m.ts b/src/dex/usual-m-smart-m/usual-m-smart-m.ts index 60f001be5..9cfd62d07 100644 --- a/src/dex/usual-m-smart-m/usual-m-smart-m.ts +++ b/src/dex/usual-m-smart-m/usual-m-smart-m.ts @@ -179,7 +179,7 @@ export class UsualMSmartM return { needWrapNative: false, - dexFuncHasRecipient: false, + dexFuncHasRecipient: true, exchangeData, targetExchange: this.config.usualMAddress, returnAmountPos: undefined, From a5f4f2d30f56aa00c5088353706a97db01d9c15c Mon Sep 17 00:00:00 2001 From: toninorair Date: Tue, 26 Nov 2024 21:22:57 -0500 Subject: [PATCH 06/27] Provide mainnet address of UsualM --- src/dex/usual-m-smart-m/config.ts | 2 +- src/dex/usual-m-usd0/config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/dex/usual-m-smart-m/config.ts b/src/dex/usual-m-smart-m/config.ts index ab8e3cde8..108c7f7b8 100644 --- a/src/dex/usual-m-smart-m/config.ts +++ b/src/dex/usual-m-smart-m/config.ts @@ -6,7 +6,7 @@ export const UsualMSmartMConfig: DexConfigMap = { UsualMSmartM: { [Network.MAINNET]: { smartMAddress: '0x437cc33344a0B27A429f795ff6B469C72698B291', - usualMAddress: '0x0000000000000000000000000000000000000000', //TODO: replace with actual address + usualMAddress: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', }, }, }; diff --git a/src/dex/usual-m-usd0/config.ts b/src/dex/usual-m-usd0/config.ts index 5a2a636bd..0ccdcabe9 100644 --- a/src/dex/usual-m-usd0/config.ts +++ b/src/dex/usual-m-usd0/config.ts @@ -5,7 +5,7 @@ import { Network, SwapSide } from '../../constants'; export const UsualMUsd0Config: DexConfigMap = { UsualMUsd0: { [Network.MAINNET]: { - usualMAddress: '0x0000000000000000000000000000000000000000', //TODO: replace with actual address + usualMAddress: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', usd0Address: '0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5', usualDaoCollateralAddress: '0xde6e1F680C4816446C8D515989E2358636A38b04', }, From 8a8dfcff55ce727a9cff49608c6f38c7c51e5c60 Mon Sep 17 00:00:00 2001 From: toninorair Date: Tue, 26 Nov 2024 22:11:50 -0500 Subject: [PATCH 07/27] Fix dexKey, add network tests --- src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts | 2 +- src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts | 2 +- tests/constants-e2e.ts | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts b/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts index 031593d27..7f42f6f75 100644 --- a/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts +++ b/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts @@ -53,7 +53,7 @@ function testForNetwork( } describe('UsualM<>Usd0 E2E', () => { - const dexKey = 'UsualMSmartM'; + const dexKey = 'UsualMUsd0'; describe('Mainnet', () => { const network = Network.MAINNET; diff --git a/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts b/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts index f00074b60..c4ce6881a 100644 --- a/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts +++ b/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts @@ -61,7 +61,7 @@ async function testPricingOnNetwork( } describe('UsualM<>USD0', function () { - const dexKey = 'UsualMUSD0'; + const dexKey = 'UsualMUsd0'; let blockNumber: number; let usualMUsd0: UsualMUsd0; diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 258fdd5f0..bf069309d 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -546,6 +546,16 @@ export const Tokens: { decimals: 18, symbol: 'USD0++', }, + SmartM: { + address: '0x437cc33344a0B27A429f795ff6B469C72698B291', + decimals: 6, + symbol: 'MSMART,', + }, + UsualM: { + address: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', + decimals: 6, + symbol: 'USUALM', + }, }, [Network.POLYGON]: { jGBP: { From 16ea0638b3dba914441defde55ea8e5704302123 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Tue, 3 Dec 2024 23:11:15 +0200 Subject: [PATCH 08/27] feat: combine Usual 1:1 pricing in one integration --- src/dex/index.ts | 6 +- src/dex/usual/types.ts | 10 + src/dex/usual/usual-bond.ts | 69 +++++ src/dex/usual/usual-e2e.test.ts | 122 +++++++++ src/dex/usual/usual-integration.test.ts | 319 ++++++++++++++++++++++++ src/dex/usual/usual-m-smart-m.ts | 71 ++++++ src/dex/usual/usual-m-usd0.ts | 76 ++++++ src/dex/usual/usual.ts | 156 ++++++++++++ tests/constants-e2e.ts | 3 +- 9 files changed, 828 insertions(+), 4 deletions(-) create mode 100644 src/dex/usual/types.ts create mode 100644 src/dex/usual/usual-bond.ts create mode 100644 src/dex/usual/usual-e2e.test.ts create mode 100644 src/dex/usual/usual-integration.test.ts create mode 100644 src/dex/usual/usual-m-smart-m.ts create mode 100644 src/dex/usual/usual-m-usd0.ts create mode 100644 src/dex/usual/usual.ts diff --git a/src/dex/index.ts b/src/dex/index.ts index 432b433dc..267d531cf 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -91,11 +91,11 @@ import { ConcentratorArusd } from './concentrator-arusd/concentrator-arusd'; import { FxProtocolRusd } from './fx-protocol-rusd/fx-protocol-rusd'; import { AaveGsm } from './aave-gsm/aave-gsm'; import { LitePsm } from './lite-psm/lite-psm'; -import { UsualBond } from './usual-bond/usual-bond'; import { StkGHO } from './stkgho/stkgho'; import { SkyConverter } from './sky-converter/sky-converter'; -import { UsualMSmartM } from './usual-m-smart-m/usual-m-smart-m'; -import { UsualMUsd0 } from './usual-m-usd0/usual-m-usd0'; +import { UsualBond } from './usual/usual-bond'; +import { UsualMSmartM } from './usual/usual-m-smart-m'; +import { UsualMUsd0 } from './usual/usual-m-usd0'; const LegacyDexes = [ CurveV2, diff --git a/src/dex/usual/types.ts b/src/dex/usual/types.ts new file mode 100644 index 000000000..ef8151986 --- /dev/null +++ b/src/dex/usual/types.ts @@ -0,0 +1,10 @@ +import { Address } from '../../types'; + +export type PoolState = {}; + +export type UsualBondData = {}; + +export type DexParams = { + fromToken: { address: Address; decimals: number }; + toToken: { address: Address; decimals: number }; +}; diff --git a/src/dex/usual/usual-bond.ts b/src/dex/usual/usual-bond.ts new file mode 100644 index 000000000..ac39a92bc --- /dev/null +++ b/src/dex/usual/usual-bond.ts @@ -0,0 +1,69 @@ +import { + Address, + NumberAsString, + DexExchangeParam, + DexConfigMap, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { UsualBondData, DexParams } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import USD0PP_ABI from '../../abi/usual-bond/usd0pp.abi.json'; +import { Usual } from './usual'; +import { getDexKeysWithNetwork } from '../../utils'; + +const Config: DexConfigMap = { + UsualBond: { + [Network.MAINNET]: { + fromToken: { + address: '0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5', + decimals: 18, + }, + toToken: { + address: '0x35d8949372d46b7a3d5a56006ae77b215fc69bc0', + decimals: 18, + }, + }, + }, +}; + +export class UsualBond extends Usual { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + usd0ppIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper, Config[dexKey][network]); + this.usd0ppIface = new Interface(USD0PP_ABI as JsonFragment[]); + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: UsualBondData, + side: SwapSide, + ): Promise { + if (this.isFromToken(srcToken) && this.isToToken(destToken)) { + const exchangeData = this.usd0ppIface.encodeFunctionData('mint', [ + srcAmount, + ]); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: this.config.toToken.address, + returnAmountPos: undefined, + }; + } + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual/usual-e2e.test.ts b/src/dex/usual/usual-e2e.test.ts new file mode 100644 index 000000000..3b918308b --- /dev/null +++ b/src/dex/usual/usual-e2e.test.ts @@ -0,0 +1,122 @@ +/* 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('UsualBond E2E', () => { + const dexKey = 'UsualBond'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'USD0'; + const tokenBSymbol: string = 'USD0++'; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); + +describe('UsualMSmartM E2E', () => { + const dexKey = 'UsualMSmartM'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'SmartM'; + const tokenBSymbol: string = 'UsualM'; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); + +describe('UsualM<>Usd0 E2E', () => { + const dexKey = 'UsualMUsd0'; + + describe('Mainnet', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'UsualM'; + const tokenBSymbol: string = 'USD0'; + + const tokenAAmount: string = '100000'; + const tokenBAmount: string = '100000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); +}); diff --git a/src/dex/usual/usual-integration.test.ts b/src/dex/usual/usual-integration.test.ts new file mode 100644 index 000000000..e3c741d85 --- /dev/null +++ b/src/dex/usual/usual-integration.test.ts @@ -0,0 +1,319 @@ +/* eslint-disable no-console */ +import dotenv from 'dotenv'; +dotenv.config(); + +import { Interface } from '@ethersproject/abi'; +import { DummyDexHelper } from '../../dex-helper/index'; +import { Network, SwapSide } from '../../constants'; +import { BI_POWS } from '../../bigint-constants'; +import { + checkPoolPrices, + checkConstantPoolPrices, + checkPoolsLiquidity, +} from '../../../tests/utils'; +import { Tokens } from '../../../tests/constants-e2e'; +import { UsualMSmartM } from './usual-m-smart-m'; +import { UsualMUsd0 } from './usual-m-usd0'; +import { Usual } from './usual'; +import { UsualBond } from './usual-bond'; + +async function testPricingOnNetwork( + usual: Usual, + network: Network, + dexKey: string, + blockNumber: number, + srcTokenSymbol: string, + destTokenSymbol: string, + side: SwapSide, + amounts: bigint[], + funcNameToCheck: string, +) { + const networkTokens = Tokens[network]; + + console.log(amounts); + + const pools = await usual.getPoolIdentifiers( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + side, + blockNumber, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await usual.getPricesVolume( + networkTokens[srcTokenSymbol], + networkTokens[destTokenSymbol], + amounts, + side, + blockNumber, + pools, + ); + console.log( + `${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + if (usual.hasConstantPriceLargeAmounts) { + checkConstantPoolPrices(poolPrices!, amounts, dexKey); + } else { + checkPoolPrices(poolPrices!, amounts, side, dexKey); + } + + // Check if onchain pricing equals to calculated ones + checkPoolPrices(poolPrices!, amounts, side, dexKey); +} + +describe('UsualBond', function () { + const dexKey = 'UsualBond'; + let blockNumber: number; + let usualBond: UsualBond; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualBond = new UsualBond(network, dexKey, dexHelper); + if (usualBond.initializePricing) { + await usualBond.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualBond, + network, + dexKey, + blockNumber, + 'USD0', + 'USD0++', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: USD0', async function () { + const tokenA = Tokens[network]['USD0']; + const dexHelper = new DummyDexHelper(network); + const usualBond = new UsualBond(network, dexKey, dexHelper); + + const poolLiquidity = await usualBond.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: USD0++', async function () { + const tokenA = Tokens[network]['USD0++']; + const dexHelper = new DummyDexHelper(network); + const usualBond = new UsualBond(network, dexKey, dexHelper); + + const poolLiquidity = await usualBond.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); + +describe('SmartM<>UsualM', function () { + const dexKey = 'UsualMSmartM'; + let blockNumber: number; + let usualMSmartM: UsualMSmartM; + + 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(); + usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + if (usualMSmartM.initializePricing) { + await usualMSmartM.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualMSmartM, + network, + dexKey, + blockNumber, + 'SmartM', + 'UsualM', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: SmartM', async function () { + const tokenA = Tokens[network]['SmartM']; + const dexHelper = new DummyDexHelper(network); + const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + + const poolLiquidity = await usualMSmartM.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: UsualM', async function () { + const tokenA = Tokens[network]['UsualM']; + const dexHelper = new DummyDexHelper(network); + const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + + const poolLiquidity = await usualMSmartM.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); + +describe('UsualM<>USD0', function () { + const dexKey = 'UsualMUsd0'; + let blockNumber: number; + let usualMUsd0: UsualMUsd0; + + describe('Mainnet', () => { + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + + // Don't forget to update relevant tokens in constant-e2e.ts + + const amountsForSell = [ + 0n, + 1n * BI_POWS[18], + 2n * BI_POWS[18], + 3n * BI_POWS[18], + 4n * BI_POWS[18], + 5n * BI_POWS[18], + 6n * BI_POWS[18], + 7n * BI_POWS[18], + 8n * BI_POWS[18], + 9n * BI_POWS[18], + 10n * BI_POWS[18], + ]; + + beforeAll(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + if (usualMUsd0.initializePricing) { + await usualMUsd0.initializePricing(blockNumber); + } + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + usualMUsd0, + network, + dexKey, + blockNumber, + 'UsualM', + 'USD0', + SwapSide.SELL, + amountsForSell, + '', + ); + }); + + it('getTopPoolsForToken: UsualM', async function () { + const tokenA = Tokens[network]['UsualM']; + const dexHelper = new DummyDexHelper(network); + const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + + const poolLiquidity = await usualMUsd0.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + + it('getTopPoolsForToken: USD0', async function () { + const tokenA = Tokens[network]['USD0']; + const dexHelper = new DummyDexHelper(network); + const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); + + const poolLiquidity = await usualMUsd0.getTopPoolsForToken( + tokenA.address, + 10, + ); + console.log( + `${tokenA.symbol} Top Pools:`, + JSON.stringify(poolLiquidity, null, 2), + ); + + checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); + }); + }); +}); diff --git a/src/dex/usual/usual-m-smart-m.ts b/src/dex/usual/usual-m-smart-m.ts new file mode 100644 index 000000000..316211e25 --- /dev/null +++ b/src/dex/usual/usual-m-smart-m.ts @@ -0,0 +1,71 @@ +import { + Address, + NumberAsString, + DexExchangeParam, + DexConfigMap, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { DexParams } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { Usual } from './usual'; +import { getDexKeysWithNetwork } from '../../utils'; +import USUALM_ABI from '../../abi/usual-m-smart-m/usualM.abi.json'; + +const Config: DexConfigMap = { + UsualMSmartM: { + [Network.MAINNET]: { + fromToken: { + address: '0x437cc33344a0b27a429f795ff6b469c72698b291', + decimals: 6, + }, + toToken: { + address: '0xfe274c305b365dc38e188e8f01c4fae2171ce927', + decimals: 6, + }, + }, + }, +}; + +export class UsualMSmartM extends Usual { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + usualMIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper, Config[dexKey][network]); + this.usualMIface = new Interface(USUALM_ABI as JsonFragment[]); + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: {}, + side: SwapSide, + ): Promise { + if (this.isFromToken(srcToken) && this.isToToken(destToken)) { + const exchangeData = this.usualMIface.encodeFunctionData( + 'wrap(address, uint256)', + [recipient, srcAmount], + ); + + return { + needWrapNative: false, + dexFuncHasRecipient: true, + exchangeData, + targetExchange: this.config.toToken.address, + returnAmountPos: undefined, + }; + } + + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual/usual-m-usd0.ts b/src/dex/usual/usual-m-usd0.ts new file mode 100644 index 000000000..a1649d344 --- /dev/null +++ b/src/dex/usual/usual-m-usd0.ts @@ -0,0 +1,76 @@ +import { + Address, + NumberAsString, + DexExchangeParam, + DexConfigMap, +} from '../../types'; +import { SwapSide, Network } from '../../constants'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { DexParams } from './types'; +import { Interface, JsonFragment } from '@ethersproject/abi'; +import { Usual } from './usual'; +import { getDexKeysWithNetwork } from '../../utils'; +import USUAL_DAO_COLLATERAL_ABI from '../../abi/usual-m-usd0/usualCollateralDao.abi.json'; + +const Config: DexConfigMap = + { + UsualMUsd0: { + [Network.MAINNET]: { + usualDaoCollateralAddress: '0xde6e1F680C4816446C8D515989E2358636A38b04', + fromToken: { + address: '0xfe274c305b365dc38e188e8f01c4fae2171ce927', + decimals: 6, + }, + toToken: { + address: '0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5', + decimals: 18, + }, + }, + }, + }; + +export class UsualMUsd0 extends Usual { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(Config); + + usualDaoCollateralIface: Interface; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + ) { + super(network, dexKey, dexHelper, Config[dexKey][network]); + this.usualDaoCollateralIface = new Interface( + USUAL_DAO_COLLATERAL_ABI as JsonFragment[], + ); + } + + async getDexParam( + srcToken: Address, + destToken: Address, + srcAmount: NumberAsString, + destAmount: NumberAsString, + recipient: Address, + data: {}, + side: SwapSide, + ): Promise { + if (this.isFromToken(srcToken) && this.isToToken(destToken)) { + const exchangeData = this.usualDaoCollateralIface.encodeFunctionData( + 'swap', + [srcToken, srcAmount, destAmount], + ); + + return { + needWrapNative: false, + dexFuncHasRecipient: false, + exchangeData, + targetExchange: + Config[this.dexKey][this.network].usualDaoCollateralAddress, + returnAmountPos: undefined, + }; + } + + throw new Error('LOGIC ERROR'); + } +} diff --git a/src/dex/usual/usual.ts b/src/dex/usual/usual.ts new file mode 100644 index 000000000..872b75904 --- /dev/null +++ b/src/dex/usual/usual.ts @@ -0,0 +1,156 @@ +import { + Token, + Address, + ExchangePrices, + PoolPrices, + AdapterExchangeParam, + Logger, + PoolLiquidity, +} 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 { UsualBondData, DexParams } from './types'; +import { SimpleExchange } from '../simple-exchange'; +import { BI_POWS } from '../../bigint-constants'; + +export class Usual extends SimpleExchange implements IDex { + readonly hasConstantPriceLargeAmounts = true; + readonly needWrapNative = false; + readonly isFeeOnTransferSupported = false; + + logger: Logger; + + constructor( + readonly network: Network, + readonly dexKey: string, + readonly dexHelper: IDexHelper, + readonly config: DexParams, + ) { + super(dexHelper, dexKey); + this.logger = dexHelper.getLogger(dexKey); + } + + async initializePricing(blockNumber: number) { + // No initialization needed for constant price + } + + isFromToken(token: string) { + return token.toLowerCase() === this.config.fromToken.address.toLowerCase(); + } + + isToToken(token: string) { + return token.toLowerCase() === this.config.toToken.address.toLowerCase(); + } + + isValidTokens(srcToken: string, destToken: string) { + return this.isFromToken(srcToken) && this.isToToken(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.toToken}`]; + } + + 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 unitOut = BI_POWS[18]; // 1:1 swap + const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input + + return [ + { + unit: unitOut, + prices: amountsOut, + data: {}, + poolAddresses: [this.config.toToken.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: UsualBondData, + side: SwapSide, + ): AdapterExchangeParam { + const payload = '0x'; + + return { + targetExchange: this.config.toToken.address, + payload, + networkFee: '0', + }; + } + + async getTopPoolsForToken( + tokenAddress: Address, + limit: number, + ): Promise { + const isFromToken = this.isFromToken(tokenAddress); + const isToToken = this.isToToken(tokenAddress); + + if (!(isFromToken || isToToken)) return []; + + return [ + { + exchange: this.dexKey, + address: this.config.toToken.address, + connectorTokens: [ + isFromToken ? this.config.toToken : this.config.fromToken, + ], + liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred + }, + ]; + } +} diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index bf069309d..030395e53 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -1738,7 +1738,8 @@ export const Holders: { weETH: '0x267ed5f71EE47D3E45Bb1569Aa37889a2d10f91e', rUSD: '0xEC2eda1C4F981E468ABF62424a10B69B738b498E', arUSD: '0xeFc24206053a452e2299BF3b8f964512b041Db4C', - USD0: '0x6A5d5Af0E266a24648a9d7E8D388EAEc7AbD8433', + USD0: '0x224762e69169E425239EeEE0012d1B0e041C123D', + SmartM: '0xE0663f2372cAa1459b7ade90812Dc737CE587FA6', 'USD0++': '0x2227b6806339906707b43F36a1f07B52FF7Fa776', USDM: '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812', wUSDM: '0x3B95bC951EE0f553ba487327278cAc44f29715E5', From 32cf1a564fa32356e8c2c9f06168a4a458d2901b Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Tue, 3 Dec 2024 23:24:12 +0200 Subject: [PATCH 09/27] feat: remove independant Usual integrations --- src/dex/usual-bond/config.ts | 12 - src/dex/usual-bond/types.ts | 10 - src/dex/usual-bond/usual-bond-e2e.test.ts | 80 ------- .../usual-bond/usual-bond-integration.test.ts | 144 ------------ src/dex/usual-bond/usual-bond.ts | 208 ----------------- src/dex/usual-m-smart-m/config.ts | 12 - src/dex/usual-m-smart-m/types.ts | 10 - .../usual-m-smart-m-e2e.test.ts | 76 ------- .../usual-m-smart-m-integration.test.ts | 144 ------------ src/dex/usual-m-smart-m/usual-m-smart-m.ts | 215 ------------------ src/dex/usual-m-usd0/config.ts | 13 -- src/dex/usual-m-usd0/types.ts | 11 - src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts | 76 ------- .../usual-m-usd0-integration.test.ts | 144 ------------ src/dex/usual-m-usd0/usual-m-usd0.ts | 215 ------------------ 15 files changed, 1370 deletions(-) delete mode 100644 src/dex/usual-bond/config.ts delete mode 100644 src/dex/usual-bond/types.ts delete mode 100644 src/dex/usual-bond/usual-bond-e2e.test.ts delete mode 100644 src/dex/usual-bond/usual-bond-integration.test.ts delete mode 100644 src/dex/usual-bond/usual-bond.ts delete mode 100644 src/dex/usual-m-smart-m/config.ts delete mode 100644 src/dex/usual-m-smart-m/types.ts delete mode 100644 src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts delete mode 100644 src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts delete mode 100644 src/dex/usual-m-smart-m/usual-m-smart-m.ts delete mode 100644 src/dex/usual-m-usd0/config.ts delete mode 100644 src/dex/usual-m-usd0/types.ts delete mode 100644 src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts delete mode 100644 src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts delete mode 100644 src/dex/usual-m-usd0/usual-m-usd0.ts diff --git a/src/dex/usual-bond/config.ts b/src/dex/usual-bond/config.ts deleted file mode 100644 index 09fe45eca..000000000 --- a/src/dex/usual-bond/config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DexParams } from './types'; -import { DexConfigMap } from '../../types'; -import { Network } from '../../constants'; - -export const UsualBondConfig: DexConfigMap = { - UsualBond: { - [Network.MAINNET]: { - usd0Address: '0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5', - usd0ppAddress: '0x35D8949372D46B7a3D5A56006AE77B215fc69bC0', - }, - }, -}; diff --git a/src/dex/usual-bond/types.ts b/src/dex/usual-bond/types.ts deleted file mode 100644 index ce90c4eb4..000000000 --- a/src/dex/usual-bond/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Address } from '../../types'; - -export type PoolState = {}; - -export type UsualBondData = {}; - -export type DexParams = { - usd0Address: Address; - usd0ppAddress: Address; -}; diff --git a/src/dex/usual-bond/usual-bond-e2e.test.ts b/src/dex/usual-bond/usual-bond-e2e.test.ts deleted file mode 100644 index f6d987514..000000000 --- a/src/dex/usual-bond/usual-bond-e2e.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -/* eslint-disable no-console */ -import dotenv from 'dotenv'; -dotenv.config(); - -import { testE2E } from '../../../tests/utils-e2e'; -import { - Tokens, - Holders, - NativeTokenSymbols, -} from '../../../tests/constants-e2e'; -import { Network, ContractMethod, SwapSide } from '../../constants'; -import { StaticJsonRpcProvider } from '@ethersproject/providers'; -import { generateConfig } from '../../config'; - -function testForNetwork( - network: Network, - dexKey: string, - tokenASymbol: string, - tokenBSymbol: string, - tokenAAmount: string, - tokenBAmount: string, -) { - 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('UsualBond E2E', () => { - const dexKey = 'UsualBond'; - - describe('Mainnet', () => { - const network = Network.MAINNET; - - const tokenASymbol: string = 'USD0'; - const tokenBSymbol: string = 'USD0++'; - - const tokenAAmount: string = '100000'; - const tokenBAmount: string = '100000'; - - testForNetwork( - network, - dexKey, - tokenASymbol, - tokenBSymbol, - tokenAAmount, - tokenBAmount, - ); - }); -}); diff --git a/src/dex/usual-bond/usual-bond-integration.test.ts b/src/dex/usual-bond/usual-bond-integration.test.ts deleted file mode 100644 index 3844bc200..000000000 --- a/src/dex/usual-bond/usual-bond-integration.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-disable no-console */ -import dotenv from 'dotenv'; -dotenv.config(); - -import { Interface } from '@ethersproject/abi'; -import { DummyDexHelper } from '../../dex-helper/index'; -import { Network, SwapSide } from '../../constants'; -import { BI_POWS } from '../../bigint-constants'; -import { UsualBond } from './usual-bond'; -import { - checkPoolPrices, - checkConstantPoolPrices, - checkPoolsLiquidity, -} from '../../../tests/utils'; -import { Tokens } from '../../../tests/constants-e2e'; - -async function testPricingOnNetwork( - usualBond: UsualBond, - network: Network, - dexKey: string, - blockNumber: number, - srcTokenAddress: string, - destTokenAddress: string, - side: SwapSide, - amounts: bigint[], - funcNameToCheck: string, -) { - const networkTokens = Tokens[network]; - - console.log(amounts); - - const pools = await usualBond.getPoolIdentifiers( - networkTokens['USD0'], - networkTokens['USD0++'], - side, - blockNumber, - ); - console.log(`${'USD0'} <> ${'USD0++'} Pool Identifiers: `, pools); - - expect(pools.length).toBeGreaterThan(0); - - const poolPrices = await usualBond.getPricesVolume( - networkTokens['USD0'], - networkTokens['USD0++'], - amounts, - side, - blockNumber, - pools, - ); - console.log(`${'USD0'} <> ${'USD0++'} Pool Prices: `, poolPrices); - - expect(poolPrices).not.toBeNull(); - if (usualBond.hasConstantPriceLargeAmounts) { - checkConstantPoolPrices(poolPrices!, amounts, dexKey); - } else { - checkPoolPrices(poolPrices!, amounts, side, dexKey); - } - - // Check if onchain pricing equals to calculated ones - checkPoolPrices(poolPrices!, amounts, side, dexKey); -} - -describe('UsualBond', function () { - const dexKey = 'UsualBond'; - let blockNumber: number; - let usualBond: UsualBond; - - describe('Mainnet', () => { - const network = Network.MAINNET; - const dexHelper = new DummyDexHelper(network); - - // Don't forget to update relevant tokens in constant-e2e.ts - - const amountsForSell = [ - 0n, - 1n * BI_POWS[18], - 2n * BI_POWS[18], - 3n * BI_POWS[18], - 4n * BI_POWS[18], - 5n * BI_POWS[18], - 6n * BI_POWS[18], - 7n * BI_POWS[18], - 8n * BI_POWS[18], - 9n * BI_POWS[18], - 10n * BI_POWS[18], - ]; - - beforeAll(async () => { - blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); - usualBond = new UsualBond(network, dexKey, dexHelper); - if (usualBond.initializePricing) { - await usualBond.initializePricing(blockNumber); - } - }); - - it('getPoolIdentifiers and getPricesVolume SELL', async function () { - await testPricingOnNetwork( - usualBond, - network, - dexKey, - blockNumber, - 'USD0', - 'USD0++', - SwapSide.SELL, - amountsForSell, - '', - ); - }); - - it('getTopPoolsForToken: USD0', async function () { - const tokenA = Tokens[network]['USD0']; - const dexHelper = new DummyDexHelper(network); - const usualBond = new UsualBond(network, dexKey, dexHelper); - - const poolLiquidity = await usualBond.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - - it('getTopPoolsForToken: USD0++', async function () { - const tokenA = Tokens[network]['USD0++']; - const dexHelper = new DummyDexHelper(network); - const usualBond = new UsualBond(network, dexKey, dexHelper); - - const poolLiquidity = await usualBond.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - }); -}); diff --git a/src/dex/usual-bond/usual-bond.ts b/src/dex/usual-bond/usual-bond.ts deleted file mode 100644 index 295dc0db4..000000000 --- a/src/dex/usual-bond/usual-bond.ts +++ /dev/null @@ -1,208 +0,0 @@ -import { - Token, - Address, - ExchangePrices, - PoolPrices, - AdapterExchangeParam, - Logger, - NumberAsString, - DexExchangeParam, - PoolLiquidity, -} from '../../types'; -import { SwapSide, Network } from '../../constants'; -import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; -import { getDexKeysWithNetwork } from '../../utils'; -import { IDex } from '../idex'; -import { IDexHelper } from '../../dex-helper/idex-helper'; -import { UsualBondData, DexParams } from './types'; -import { SimpleExchange } from '../simple-exchange'; -import { UsualBondConfig } from './config'; -import { Interface, JsonFragment } from '@ethersproject/abi'; -import USD0PP_ABI from '../../abi/usual-bond/usd0pp.abi.json'; -import { BI_POWS } from '../../bigint-constants'; - -export class UsualBond extends SimpleExchange implements IDex { - protected config: DexParams; - - readonly hasConstantPriceLargeAmounts = true; - readonly needWrapNative = false; - readonly isFeeOnTransferSupported = false; - - public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(UsualBondConfig); - - usd0ppIface: Interface; - logger: Logger; - - constructor( - readonly network: Network, - readonly dexKey: string, - readonly dexHelper: IDexHelper, - ) { - super(dexHelper, dexKey); - const config = UsualBondConfig[dexKey][network]; - this.usd0ppIface = new Interface(USD0PP_ABI as JsonFragment[]); - this.config = { - usd0Address: config.usd0Address.toLowerCase(), - usd0ppAddress: config.usd0ppAddress.toLowerCase(), - }; - this.logger = dexHelper.getLogger(dexKey); - } - - async initializePricing(blockNumber: number) { - // No initialization needed for constant price - } - - getConfig() { - return this.config; - } - - is_usd0(token: string) { - return token.toLowerCase() === this.config.usd0Address.toLowerCase(); - } - - is_usd0pp(token: string) { - return token.toLowerCase() === this.config.usd0ppAddress.toLowerCase(); - } - - is_usd0_swap_token(srcToken: string, destToken: string) { - return this.is_usd0(srcToken) && this.is_usd0pp(destToken); - } - - 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.is_usd0_swap_token(srcTokenAddress, destTokenAddress)) { - return [`${this.dexKey}_${this.config.usd0ppAddress}`]; - } - - return []; - } - - async getPricesVolume( - srcToken: Token, - destToken: Token, - amounts: bigint[], - side: SwapSide, - blockNumber: number, - limitPools?: string[], - ): Promise> { - if (side === SwapSide.BUY) { - return null; - } - - const isUSD0SwapToken = this.is_usd0_swap_token( - srcToken.address, - destToken.address, - ); - - if (!isUSD0SwapToken) { - return null; - } - - const unitOut = BI_POWS[18]; // 1:1 swap - const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input - - return [ - { - unit: unitOut, - prices: amountsOut, - data: {}, - poolAddresses: [this.config.usd0ppAddress], - 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: UsualBondData, - side: SwapSide, - ): AdapterExchangeParam { - const payload = '0x'; - - return { - targetExchange: this.config.usd0ppAddress, - payload, - networkFee: '0', - }; - } - - async getDexParam( - srcToken: Address, - destToken: Address, - srcAmount: NumberAsString, - destAmount: NumberAsString, - recipient: Address, - data: UsualBondData, - side: SwapSide, - ): Promise { - if (this.is_usd0(srcToken) && this.is_usd0pp(destToken)) { - const exchangeData = this.usd0ppIface.encodeFunctionData('mint', [ - srcAmount, - ]); - - return { - needWrapNative: false, - dexFuncHasRecipient: false, - exchangeData, - targetExchange: this.config.usd0ppAddress, - returnAmountPos: undefined, - }; - } - throw new Error('LOGIC ERROR'); - } - - async getTopPoolsForToken( - tokenAddress: Address, - limit: number, - ): Promise { - const isUsd0 = this.is_usd0(tokenAddress); - if (!isUsd0 && !this.is_usd0pp(tokenAddress)) return []; - - return [ - { - exchange: this.dexKey, - address: this.config.usd0ppAddress, - connectorTokens: [ - { - decimals: 18, - address: isUsd0 - ? this.config.usd0ppAddress - : this.config.usd0Address, - }, - ], - liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred - }, - ]; - } -} diff --git a/src/dex/usual-m-smart-m/config.ts b/src/dex/usual-m-smart-m/config.ts deleted file mode 100644 index 108c7f7b8..000000000 --- a/src/dex/usual-m-smart-m/config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { DexParams } from './types'; -import { DexConfigMap } from '../../types'; -import { Network, SwapSide } from '../../constants'; - -export const UsualMSmartMConfig: DexConfigMap = { - UsualMSmartM: { - [Network.MAINNET]: { - smartMAddress: '0x437cc33344a0B27A429f795ff6B469C72698B291', - usualMAddress: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', - }, - }, -}; diff --git a/src/dex/usual-m-smart-m/types.ts b/src/dex/usual-m-smart-m/types.ts deleted file mode 100644 index 0684a394e..000000000 --- a/src/dex/usual-m-smart-m/types.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { Address } from '../../types'; - -export type PoolState = {}; - -export type UsualMSmartMData = {}; - -export type DexParams = { - smartMAddress: Address; - usualMAddress: Address; -}; diff --git a/src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts b/src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts deleted file mode 100644 index 530395578..000000000 --- a/src/dex/usual-m-smart-m/usual-m-smart-m-e2e.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* 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('UsualMSmartM E2E', () => { - const dexKey = 'UsualMSmartM'; - - describe('Mainnet', () => { - const network = Network.MAINNET; - - const tokenASymbol: string = 'SmartM'; - const tokenBSymbol: string = 'UsualM'; - - const tokenAAmount: string = '100000'; - const tokenBAmount: string = '100000'; - - testForNetwork( - network, - dexKey, - tokenASymbol, - tokenBSymbol, - tokenAAmount, - tokenBAmount, - ); - }); -}); diff --git a/src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts b/src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts deleted file mode 100644 index a647324ca..000000000 --- a/src/dex/usual-m-smart-m/usual-m-smart-m-integration.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-disable no-console */ -import dotenv from 'dotenv'; -dotenv.config(); - -import { Interface } from '@ethersproject/abi'; -import { DummyDexHelper } from '../../dex-helper/index'; -import { Network, SwapSide } from '../../constants'; -import { BI_POWS } from '../../bigint-constants'; -import { UsualMSmartM } from './usual-m-smart-m'; -import { - checkPoolPrices, - checkConstantPoolPrices, - checkPoolsLiquidity, -} from '../../../tests/utils'; -import { Tokens } from '../../../tests/constants-e2e'; - -async function testPricingOnNetwork( - usualMSmartM: UsualMSmartM, - network: Network, - dexKey: string, - blockNumber: number, - srcTokenAddress: string, - destTokenAddress: string, - side: SwapSide, - amounts: bigint[], - funcNameToCheck: string, -) { - const networkTokens = Tokens[network]; - - console.log(amounts); - - const pools = await usualMSmartM.getPoolIdentifiers( - networkTokens['SmartM'], - networkTokens['UsualM'], - side, - blockNumber, - ); - console.log(`${'SmartM'} <> ${'UsualM'} Pool Identifiers: `, pools); - - expect(pools.length).toBeGreaterThan(0); - - const poolPrices = await usualMSmartM.getPricesVolume( - networkTokens['SmartM'], - networkTokens['UsualM'], - amounts, - side, - blockNumber, - pools, - ); - console.log(`${'SmartM'} <> ${'UsualM'} Pool Prices: `, poolPrices); - - expect(poolPrices).not.toBeNull(); - if (usualMSmartM.hasConstantPriceLargeAmounts) { - checkConstantPoolPrices(poolPrices!, amounts, dexKey); - } else { - checkPoolPrices(poolPrices!, amounts, side, dexKey); - } - - // Check if onchain pricing equals to calculated ones - checkPoolPrices(poolPrices!, amounts, side, dexKey); -} - -describe('SmartM<>UsualM', function () { - const dexKey = 'UsualMSmartM'; - let blockNumber: number; - let usualMSmartM: UsualMSmartM; - - 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(); - usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); - if (usualMSmartM.initializePricing) { - await usualMSmartM.initializePricing(blockNumber); - } - }); - - it('getPoolIdentifiers and getPricesVolume SELL', async function () { - await testPricingOnNetwork( - usualMSmartM, - network, - dexKey, - blockNumber, - 'SmartM', - 'UsualM', - SwapSide.SELL, - amountsForSell, - '', - ); - }); - - it('getTopPoolsForToken: SmartM', async function () { - const tokenA = Tokens[network]['SmartM']; - const dexHelper = new DummyDexHelper(network); - const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); - - const poolLiquidity = await usualMSmartM.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - - it('getTopPoolsForToken: UsualM', async function () { - const tokenA = Tokens[network]['UsualM']; - const dexHelper = new DummyDexHelper(network); - const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); - - const poolLiquidity = await usualMSmartM.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-m-smart-m/usual-m-smart-m.ts b/src/dex/usual-m-smart-m/usual-m-smart-m.ts deleted file mode 100644 index 9cfd62d07..000000000 --- a/src/dex/usual-m-smart-m/usual-m-smart-m.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - Token, - Address, - ExchangePrices, - PoolPrices, - AdapterExchangeParam, - Logger, - NumberAsString, - DexExchangeParam, - PoolLiquidity, -} from '../../types'; -import { SwapSide, Network } from '../../constants'; -import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; -import { getDexKeysWithNetwork } from '../../utils'; -import { IDex } from '../idex'; -import { IDexHelper } from '../../dex-helper/idex-helper'; -import { UsualMSmartMData, DexParams } from './types'; -import { SimpleExchange } from '../simple-exchange'; -import { UsualMSmartMConfig } from './config'; -import { Interface, JsonFragment } from '@ethersproject/abi'; -import USUALM_ABI from '../../abi/usual-m-smart-m/usualM.abi.json'; -import { BI_POWS } from '../../bigint-constants'; - -export class UsualMSmartM - extends SimpleExchange - implements IDex -{ - protected config: DexParams; - - readonly hasConstantPriceLargeAmounts = true; - readonly needWrapNative = false; - readonly isFeeOnTransferSupported = false; - - public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(UsualMSmartMConfig); - - usualMIface: Interface; - logger: Logger; - - constructor( - readonly network: Network, - readonly dexKey: string, - readonly dexHelper: IDexHelper, - ) { - super(dexHelper, dexKey); - const config = UsualMSmartMConfig[dexKey][network]; - this.usualMIface = new Interface(USUALM_ABI as JsonFragment[]); - this.config = { - smartMAddress: config.smartMAddress.toLowerCase(), - usualMAddress: config.usualMAddress.toLowerCase(), - }; - this.logger = dexHelper.getLogger(dexKey); - } - - async initializePricing(blockNumber: number) { - // No initialization needed for constant price - } - - getConfig() { - return this.config; - } - - is_smartM(token: string) { - return token.toLowerCase() === this.config.smartMAddress.toLowerCase(); - } - - is_usualM(token: string) { - return token.toLowerCase() === this.config.usualMAddress.toLowerCase(); - } - - is_usualM_swap_token(srcToken: string, destToken: string) { - return this.is_smartM(srcToken) && this.is_usualM(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.is_usualM_swap_token(srcTokenAddress, destTokenAddress)) { - return [`${this.dexKey}_${this.config.usualMAddress}`]; - } - - return []; - } - - async getPricesVolume( - srcToken: Token, - destToken: Token, - amounts: bigint[], - side: SwapSide, - blockNumber: number, - limitPools?: string[], - ): Promise> { - if (side === SwapSide.BUY) { - return null; - } - - const isUsualMSwapToken = this.is_usualM_swap_token( - srcToken.address, - destToken.address, - ); - - if (!isUsualMSwapToken) { - return null; - } - - const unitOut = BI_POWS[18]; // 1:1 swap - const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input - - return [ - { - unit: unitOut, - prices: amountsOut, - data: {}, - poolAddresses: [this.config.usualMAddress], - 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: UsualMSmartMData, - side: SwapSide, - ): AdapterExchangeParam { - const payload = '0x'; - - return { - targetExchange: this.config.usualMAddress, - payload, - networkFee: '0', - }; - } - - async getDexParam( - srcToken: Address, - destToken: Address, - srcAmount: NumberAsString, - destAmount: NumberAsString, - recipient: Address, - data: UsualMSmartMData, - side: SwapSide, - ): Promise { - if (this.is_smartM(srcToken) && this.is_usualM(destToken)) { - const exchangeData = this.usualMIface.encodeFunctionData('wrap', [ - recipient, - srcAmount, - ]); - - return { - needWrapNative: false, - dexFuncHasRecipient: true, - exchangeData, - targetExchange: this.config.usualMAddress, - returnAmountPos: undefined, - }; - } - - throw new Error('LOGIC ERROR'); - } - - async getTopPoolsForToken( - tokenAddress: Address, - limit: number, - ): Promise { - const isSmartM = this.is_smartM(tokenAddress); - if (!isSmartM && !this.is_usualM(tokenAddress)) return []; - - return [ - { - exchange: this.dexKey, - address: this.config.usualMAddress, - connectorTokens: [ - { - decimals: 6, - address: isSmartM - ? this.config.usualMAddress - : this.config.smartMAddress, - }, - ], - liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred - }, - ]; - } -} diff --git a/src/dex/usual-m-usd0/config.ts b/src/dex/usual-m-usd0/config.ts deleted file mode 100644 index 0ccdcabe9..000000000 --- a/src/dex/usual-m-usd0/config.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { DexParams } from './types'; -import { DexConfigMap } from '../../types'; -import { Network, SwapSide } from '../../constants'; - -export const UsualMUsd0Config: DexConfigMap = { - UsualMUsd0: { - [Network.MAINNET]: { - usualMAddress: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', - usd0Address: '0x73A15FeD60Bf67631dC6cd7Bc5B6e8da8190aCF5', - usualDaoCollateralAddress: '0xde6e1F680C4816446C8D515989E2358636A38b04', - }, - }, -}; diff --git a/src/dex/usual-m-usd0/types.ts b/src/dex/usual-m-usd0/types.ts deleted file mode 100644 index 43ff7d8c8..000000000 --- a/src/dex/usual-m-usd0/types.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Address } from '../../types'; - -export type PoolState = {}; - -export type UsualMUsd0Data = {}; - -export type DexParams = { - usualMAddress: Address; - usd0Address: Address; - usualDaoCollateralAddress: Address; -}; diff --git a/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts b/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts deleted file mode 100644 index 7f42f6f75..000000000 --- a/src/dex/usual-m-usd0/usual-m-usd0-e2e.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -/* 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('UsualM<>Usd0 E2E', () => { - const dexKey = 'UsualMUsd0'; - - describe('Mainnet', () => { - const network = Network.MAINNET; - - const tokenASymbol: string = 'UsualM'; - const tokenBSymbol: string = 'USD0'; - - const tokenAAmount: string = '100000'; - const tokenBAmount: string = '100000'; - - testForNetwork( - network, - dexKey, - tokenASymbol, - tokenBSymbol, - tokenAAmount, - tokenBAmount, - ); - }); -}); diff --git a/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts b/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts deleted file mode 100644 index c4ce6881a..000000000 --- a/src/dex/usual-m-usd0/usual-m-usd0-integration.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -/* eslint-disable no-console */ -import dotenv from 'dotenv'; -dotenv.config(); - -import { Interface } from '@ethersproject/abi'; -import { DummyDexHelper } from '../../dex-helper/index'; -import { Network, SwapSide } from '../../constants'; -import { BI_POWS } from '../../bigint-constants'; -import { UsualMUsd0 } from './usual-m-usd0'; -import { - checkPoolPrices, - checkConstantPoolPrices, - checkPoolsLiquidity, -} from '../../../tests/utils'; -import { Tokens } from '../../../tests/constants-e2e'; - -async function testPricingOnNetwork( - usualMSmartM: UsualMUsd0, - network: Network, - dexKey: string, - blockNumber: number, - srcTokenAddress: string, - destTokenAddress: string, - side: SwapSide, - amounts: bigint[], - funcNameToCheck: string, -) { - const networkTokens = Tokens[network]; - - console.log(amounts); - - const pools = await usualMSmartM.getPoolIdentifiers( - networkTokens['UsualM'], - networkTokens['USD0'], - side, - blockNumber, - ); - console.log(`${'UsualM'} <> ${'USD0'} Pool Identifiers: `, pools); - - expect(pools.length).toBeGreaterThan(0); - - const poolPrices = await usualMSmartM.getPricesVolume( - networkTokens['UsualM'], - networkTokens['USD0'], - amounts, - side, - blockNumber, - pools, - ); - console.log(`${'UsualM'} <> ${'USD0'} Pool Prices: `, poolPrices); - - expect(poolPrices).not.toBeNull(); - if (usualMSmartM.hasConstantPriceLargeAmounts) { - checkConstantPoolPrices(poolPrices!, amounts, dexKey); - } else { - checkPoolPrices(poolPrices!, amounts, side, dexKey); - } - - // Check if onchain pricing equals to calculated ones - checkPoolPrices(poolPrices!, amounts, side, dexKey); -} - -describe('UsualM<>USD0', function () { - const dexKey = 'UsualMUsd0'; - let blockNumber: number; - let usualMUsd0: UsualMUsd0; - - describe('Mainnet', () => { - const network = Network.MAINNET; - const dexHelper = new DummyDexHelper(network); - - // Don't forget to update relevant tokens in constant-e2e.ts - - const amountsForSell = [ - 0n, - 1n * BI_POWS[18], - 2n * BI_POWS[18], - 3n * BI_POWS[18], - 4n * BI_POWS[18], - 5n * BI_POWS[18], - 6n * BI_POWS[18], - 7n * BI_POWS[18], - 8n * BI_POWS[18], - 9n * BI_POWS[18], - 10n * BI_POWS[18], - ]; - - beforeAll(async () => { - blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); - usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); - if (usualMUsd0.initializePricing) { - await usualMUsd0.initializePricing(blockNumber); - } - }); - - it('getPoolIdentifiers and getPricesVolume SELL', async function () { - await testPricingOnNetwork( - usualMUsd0, - network, - dexKey, - blockNumber, - 'UsualM', - 'USD0', - SwapSide.SELL, - amountsForSell, - '', - ); - }); - - it('getTopPoolsForToken: UsualM', async function () { - const tokenA = Tokens[network]['UsualM']; - const dexHelper = new DummyDexHelper(network); - const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); - - const poolLiquidity = await usualMUsd0.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - - it('getTopPoolsForToken: USD0', async function () { - const tokenA = Tokens[network]['USD0']; - const dexHelper = new DummyDexHelper(network); - const usualMUsd0 = new UsualMUsd0(network, dexKey, dexHelper); - - const poolLiquidity = await usualMUsd0.getTopPoolsForToken( - tokenA.address, - 10, - ); - console.log( - `${tokenA.symbol} Top Pools:`, - JSON.stringify(poolLiquidity, null, 2), - ); - - checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey); - }); - }); -}); diff --git a/src/dex/usual-m-usd0/usual-m-usd0.ts b/src/dex/usual-m-usd0/usual-m-usd0.ts deleted file mode 100644 index 9d3c89d82..000000000 --- a/src/dex/usual-m-usd0/usual-m-usd0.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - Token, - Address, - ExchangePrices, - PoolPrices, - AdapterExchangeParam, - Logger, - NumberAsString, - DexExchangeParam, - PoolLiquidity, -} from '../../types'; -import { SwapSide, Network } from '../../constants'; -import * as CALLDATA_GAS_COST from '../../calldata-gas-cost'; -import { getDexKeysWithNetwork } from '../../utils'; -import { IDex } from '../idex'; -import { IDexHelper } from '../../dex-helper/idex-helper'; -import { UsualMUsd0Data, DexParams } from './types'; -import { SimpleExchange } from '../simple-exchange'; -import { UsualMUsd0Config } from './config'; -import { Interface, JsonFragment } from '@ethersproject/abi'; -import USUAL_DAO_COLLATERAL_ABI from '../../abi/usual-m-usd0/usualCollateralDao.abi.json'; -import { BI_POWS } from '../../bigint-constants'; - -export class UsualMUsd0 extends SimpleExchange implements IDex { - protected config: DexParams; - - readonly hasConstantPriceLargeAmounts = true; - readonly needWrapNative = false; - readonly isFeeOnTransferSupported = false; - - public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = - getDexKeysWithNetwork(UsualMUsd0Config); - - usualDaoCollateralIface: Interface; - logger: Logger; - - constructor( - readonly network: Network, - readonly dexKey: string, - readonly dexHelper: IDexHelper, - ) { - super(dexHelper, dexKey); - const config = UsualMUsd0Config[dexKey][network]; - this.usualDaoCollateralIface = new Interface( - USUAL_DAO_COLLATERAL_ABI as JsonFragment[], - ); - this.config = { - usualMAddress: config.usualMAddress.toLowerCase(), - usd0Address: config.usd0Address.toLowerCase(), - usualDaoCollateralAddress: config.usualDaoCollateralAddress.toLowerCase(), - }; - this.logger = dexHelper.getLogger(dexKey); - } - - async initializePricing(blockNumber: number) { - // No initialization needed for constant price - } - - getConfig() { - return this.config; - } - - is_usualM(token: string) { - return token.toLowerCase() === this.config.usualMAddress.toLowerCase(); - } - - is_usd0(token: string) { - return token.toLowerCase() === this.config.usd0Address.toLowerCase(); - } - - is_usd0_swap_token(srcToken: string, destToken: string) { - return this.is_usualM(srcToken) && this.is_usd0(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.is_usd0_swap_token(srcTokenAddress, destTokenAddress)) { - return [`${this.dexKey}_${this.config.usd0Address}`]; - } - - return []; - } - - async getPricesVolume( - srcToken: Token, - destToken: Token, - amounts: bigint[], - side: SwapSide, - blockNumber: number, - limitPools?: string[], - ): Promise> { - if (side === SwapSide.BUY) { - return null; - } - - const isUsd0SwapToken = this.is_usd0_swap_token( - srcToken.address, - destToken.address, - ); - - if (!isUsd0SwapToken) { - return null; - } - - const unitOut = BI_POWS[18]; // 1:1 swap - const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input - - return [ - { - unit: unitOut, - prices: amountsOut, - data: {}, - poolAddresses: [this.config.usd0Address], - 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: UsualMUsd0Data, - side: SwapSide, - ): AdapterExchangeParam { - const payload = '0x'; - - return { - targetExchange: this.config.usualMAddress, - payload, - networkFee: '0', - }; - } - - async getDexParam( - srcToken: Address, - destToken: Address, - srcAmount: NumberAsString, - destAmount: NumberAsString, - recipient: Address, - data: UsualMUsd0Data, - side: SwapSide, - ): Promise { - if (this.is_usualM(srcToken) && this.is_usd0(destToken)) { - const exchangeData = this.usualDaoCollateralIface.encodeFunctionData( - 'swap', - [srcToken, srcAmount, destAmount], - ); - - return { - needWrapNative: false, - dexFuncHasRecipient: false, - exchangeData, - targetExchange: this.config.usualDaoCollateralAddress, - returnAmountPos: undefined, - }; - } - - throw new Error('LOGIC ERROR'); - } - - async getTopPoolsForToken( - tokenAddress: Address, - limit: number, - ): Promise { - const isUsualM = this.is_usualM(tokenAddress); - if (!isUsualM && !this.is_usd0(tokenAddress)) return []; - - return [ - { - exchange: this.dexKey, - address: this.config.usualMAddress, - connectorTokens: [ - { - decimals: isUsualM ? 6 : 18, // TODO: check this one - address: isUsualM - ? this.config.usd0Address - : this.config.usualMAddress, - }, - ], - liquidityUSD: 1000000000, // Just returning a big number so this DEX will be preferred - }, - ]; - } -} From aca00dadeefce8ffac0978e6ddc8bc14f3dce662 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Thu, 12 Dec 2024 15:11:37 +0200 Subject: [PATCH 10/27] feat: rename UsualMSmartM to UsualMWrappedM --- .../usualM.abi.json | 0 src/dex/index.ts | 4 +-- src/dex/usual/usual-e2e.test.ts | 6 ++-- src/dex/usual/usual-integration.test.ts | 30 +++++++++---------- ...sual-m-smart-m.ts => usual-m-wrapped-m.ts} | 6 ++-- tests/constants-e2e.ts | 6 ++-- 6 files changed, 26 insertions(+), 26 deletions(-) rename src/abi/{usual-m-smart-m => usual-m-wrapped-m}/usualM.abi.json (100%) rename src/dex/usual/{usual-m-smart-m.ts => usual-m-wrapped-m.ts} (92%) diff --git a/src/abi/usual-m-smart-m/usualM.abi.json b/src/abi/usual-m-wrapped-m/usualM.abi.json similarity index 100% rename from src/abi/usual-m-smart-m/usualM.abi.json rename to src/abi/usual-m-wrapped-m/usualM.abi.json diff --git a/src/dex/index.ts b/src/dex/index.ts index 267d531cf..ccaab6054 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -94,7 +94,7 @@ import { LitePsm } from './lite-psm/lite-psm'; import { StkGHO } from './stkgho/stkgho'; import { SkyConverter } from './sky-converter/sky-converter'; import { UsualBond } from './usual/usual-bond'; -import { UsualMSmartM } from './usual/usual-m-smart-m'; +import { UsualMWrappedM } from './usual/usual-m-wrapped-m'; import { UsualMUsd0 } from './usual/usual-m-usd0'; const LegacyDexes = [ @@ -185,7 +185,7 @@ const Dexes = [ StkGHO, SkyConverter, FluidDex, - UsualMSmartM, + UsualMWrappedM, UsualMUsd0, ]; diff --git a/src/dex/usual/usual-e2e.test.ts b/src/dex/usual/usual-e2e.test.ts index 3b918308b..d75673ee8 100644 --- a/src/dex/usual/usual-e2e.test.ts +++ b/src/dex/usual/usual-e2e.test.ts @@ -75,13 +75,13 @@ describe('UsualBond E2E', () => { }); }); -describe('UsualMSmartM E2E', () => { - const dexKey = 'UsualMSmartM'; +describe('UsualMWrappedM E2E', () => { + const dexKey = 'UsualMWrappedM'; describe('Mainnet', () => { const network = Network.MAINNET; - const tokenASymbol: string = 'SmartM'; + const tokenASymbol: string = 'WrappedM'; const tokenBSymbol: string = 'UsualM'; const tokenAAmount: string = '100000'; diff --git a/src/dex/usual/usual-integration.test.ts b/src/dex/usual/usual-integration.test.ts index e3c741d85..3041c157c 100644 --- a/src/dex/usual/usual-integration.test.ts +++ b/src/dex/usual/usual-integration.test.ts @@ -12,7 +12,7 @@ import { checkPoolsLiquidity, } from '../../../tests/utils'; import { Tokens } from '../../../tests/constants-e2e'; -import { UsualMSmartM } from './usual-m-smart-m'; +import { UsualMWrappedM } from './usual-m-wrapped-m'; import { UsualMUsd0 } from './usual-m-usd0'; import { Usual } from './usual'; import { UsualBond } from './usual-bond'; @@ -152,10 +152,10 @@ describe('UsualBond', function () { }); }); -describe('SmartM<>UsualM', function () { - const dexKey = 'UsualMSmartM'; +describe('WrappedM<>UsualM', function () { + const dexKey = 'UsualMWrappedM'; let blockNumber: number; - let usualMSmartM: UsualMSmartM; + let usualMWrappedM: UsualMWrappedM; describe('Mainnet', () => { const network = Network.MAINNET; @@ -179,19 +179,19 @@ describe('SmartM<>UsualM', function () { beforeAll(async () => { blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); - usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); - if (usualMSmartM.initializePricing) { - await usualMSmartM.initializePricing(blockNumber); + usualMWrappedM = new UsualMWrappedM(network, dexKey, dexHelper); + if (usualMWrappedM.initializePricing) { + await usualMWrappedM.initializePricing(blockNumber); } }); it('getPoolIdentifiers and getPricesVolume SELL', async function () { await testPricingOnNetwork( - usualMSmartM, + usualMWrappedM, network, dexKey, blockNumber, - 'SmartM', + 'WrappedM', 'UsualM', SwapSide.SELL, amountsForSell, @@ -199,12 +199,12 @@ describe('SmartM<>UsualM', function () { ); }); - it('getTopPoolsForToken: SmartM', async function () { - const tokenA = Tokens[network]['SmartM']; + it('getTopPoolsForToken: WrappedM', async function () { + const tokenA = Tokens[network]['WrappedM']; const dexHelper = new DummyDexHelper(network); - const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + const usualMWrappedM = new UsualMWrappedM(network, dexKey, dexHelper); - const poolLiquidity = await usualMSmartM.getTopPoolsForToken( + const poolLiquidity = await usualMWrappedM.getTopPoolsForToken( tokenA.address, 10, ); @@ -219,9 +219,9 @@ describe('SmartM<>UsualM', function () { it('getTopPoolsForToken: UsualM', async function () { const tokenA = Tokens[network]['UsualM']; const dexHelper = new DummyDexHelper(network); - const usualMSmartM = new UsualMSmartM(network, dexKey, dexHelper); + const usualMWrappedM = new UsualMWrappedM(network, dexKey, dexHelper); - const poolLiquidity = await usualMSmartM.getTopPoolsForToken( + const poolLiquidity = await usualMWrappedM.getTopPoolsForToken( tokenA.address, 10, ); diff --git a/src/dex/usual/usual-m-smart-m.ts b/src/dex/usual/usual-m-wrapped-m.ts similarity index 92% rename from src/dex/usual/usual-m-smart-m.ts rename to src/dex/usual/usual-m-wrapped-m.ts index 316211e25..b4a423f30 100644 --- a/src/dex/usual/usual-m-smart-m.ts +++ b/src/dex/usual/usual-m-wrapped-m.ts @@ -10,10 +10,10 @@ import { DexParams } from './types'; import { Interface, JsonFragment } from '@ethersproject/abi'; import { Usual } from './usual'; import { getDexKeysWithNetwork } from '../../utils'; -import USUALM_ABI from '../../abi/usual-m-smart-m/usualM.abi.json'; +import USUALM_ABI from '../../abi/usual-m-wrapped-m/usualM.abi.json'; const Config: DexConfigMap = { - UsualMSmartM: { + UsualMWrappedM: { [Network.MAINNET]: { fromToken: { address: '0x437cc33344a0b27a429f795ff6b469c72698b291', @@ -27,7 +27,7 @@ const Config: DexConfigMap = { }, }; -export class UsualMSmartM extends Usual { +export class UsualMWrappedM extends Usual { public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork(Config); diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 030395e53..ac161e99b 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -546,10 +546,10 @@ export const Tokens: { decimals: 18, symbol: 'USD0++', }, - SmartM: { + WrappedM: { address: '0x437cc33344a0B27A429f795ff6B469C72698B291', decimals: 6, - symbol: 'MSMART,', + symbol: 'wM', }, UsualM: { address: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', @@ -1739,7 +1739,7 @@ export const Holders: { rUSD: '0xEC2eda1C4F981E468ABF62424a10B69B738b498E', arUSD: '0xeFc24206053a452e2299BF3b8f964512b041Db4C', USD0: '0x224762e69169E425239EeEE0012d1B0e041C123D', - SmartM: '0xE0663f2372cAa1459b7ade90812Dc737CE587FA6', + WrappedM: '0xE0663f2372cAa1459b7ade90812Dc737CE587FA6', 'USD0++': '0x2227b6806339906707b43F36a1f07B52FF7Fa776', USDM: '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812', wUSDM: '0x3B95bC951EE0f553ba487327278cAc44f29715E5', From 9bea8e88c5a052a956ebddedb828b4fdfa2269b1 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Thu, 12 Dec 2024 15:17:00 +0200 Subject: [PATCH 11/27] feat: use latest UsualM deployment --- src/abi/usual-m-wrapped-m/usualM.abi.json | 84 +++++++++++++++-------- src/dex/usual/usual-m-usd0.ts | 2 +- src/dex/usual/usual-m-wrapped-m.ts | 2 +- tests/constants-e2e.ts | 2 +- 4 files changed, 58 insertions(+), 32 deletions(-) diff --git a/src/abi/usual-m-wrapped-m/usualM.abi.json b/src/abi/usual-m-wrapped-m/usualM.abi.json index 96818b251..53ea08cd6 100644 --- a/src/abi/usual-m-wrapped-m/usualM.abi.json +++ b/src/abi/usual-m-wrapped-m/usualM.abi.json @@ -1,5 +1,12 @@ [ { "type": "constructor", "inputs": [], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "DECIMALS_NUMBER", + "inputs": [], + "outputs": [{ "name": "", "type": "uint8", "internalType": "uint8" }], + "stateMutability": "view" + }, { "type": "function", "name": "DOMAIN_SEPARATOR", @@ -74,19 +81,24 @@ "internalType": "address" }, { "name": "salt", "type": "bytes32", "internalType": "bytes32" }, - { - "name": "extensions", - "type": "uint256[]", - "internalType": "uint256[]" - } + { "name": "extensions", "type": "uint256[]", "internalType": "uint256[]" } ], "stateMutability": "view" }, + { + "type": "function", + "name": "getWrappableAmount", + "inputs": [ + { "name": "amount", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, { "type": "function", "name": "initialize", "inputs": [ - { "name": "smartM_", "type": "address", "internalType": "address" }, + { "name": "wrappedM_", "type": "address", "internalType": "address" }, { "name": "registryAccess_", "type": "address", @@ -105,6 +117,13 @@ "outputs": [{ "name": "", "type": "bool", "internalType": "bool" }], "stateMutability": "view" }, + { + "type": "function", + "name": "mintCap", + "inputs": [], + "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], + "stateMutability": "view" + }, { "type": "function", "name": "name", @@ -159,10 +178,12 @@ }, { "type": "function", - "name": "smartM", - "inputs": [], - "outputs": [{ "name": "", "type": "address", "internalType": "address" }], - "stateMutability": "view" + "name": "setMintCap", + "inputs": [ + { "name": "newMintCap", "type": "uint256", "internalType": "uint256" } + ], + "outputs": [], + "stateMutability": "nonpayable" }, { "type": "function", @@ -225,24 +246,6 @@ "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "nonpayable" }, - { - "type": "function", - "name": "unwrap", - "inputs": [ - { "name": "recipient", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "wrap", - "inputs": [ - { "name": "recipient", "type": "address", "internalType": "address" } - ], - "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], - "stateMutability": "nonpayable" - }, { "type": "function", "name": "wrap", @@ -267,6 +270,13 @@ "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }], "stateMutability": "nonpayable" }, + { + "type": "function", + "name": "wrappedM", + "inputs": [], + "outputs": [{ "name": "", "type": "address", "internalType": "address" }], + "stateMutability": "view" + }, { "type": "event", "name": "Approval", @@ -324,6 +334,19 @@ ], "anonymous": false }, + { + "type": "event", + "name": "MintCapSet", + "inputs": [ + { + "name": "newMintCap", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, { "type": "event", "name": "Paused", @@ -473,11 +496,14 @@ { "name": "currentNonce", "type": "uint256", "internalType": "uint256" } ] }, + { "type": "error", "name": "InvalidAmount", "inputs": [] }, { "type": "error", "name": "InvalidInitialization", "inputs": [] }, + { "type": "error", "name": "InvalidUInt96", "inputs": [] }, + { "type": "error", "name": "MintCapExceeded", "inputs": [] }, { "type": "error", "name": "NotAuthorized", "inputs": [] }, { "type": "error", "name": "NotInitializing", "inputs": [] }, { "type": "error", "name": "SameValue", "inputs": [] }, { "type": "error", "name": "ZeroAddress", "inputs": [] }, { "type": "error", "name": "ZeroRegistryAccess", "inputs": [] }, - { "type": "error", "name": "ZeroSmartM", "inputs": [] } + { "type": "error", "name": "ZeroWrappedM", "inputs": [] } ] diff --git a/src/dex/usual/usual-m-usd0.ts b/src/dex/usual/usual-m-usd0.ts index a1649d344..653450cee 100644 --- a/src/dex/usual/usual-m-usd0.ts +++ b/src/dex/usual/usual-m-usd0.ts @@ -18,7 +18,7 @@ const Config: DexConfigMap = [Network.MAINNET]: { usualDaoCollateralAddress: '0xde6e1F680C4816446C8D515989E2358636A38b04', fromToken: { - address: '0xfe274c305b365dc38e188e8f01c4fae2171ce927', + address: '0x4cbc25559dbbd1272ec5b64c7b5f48a2405e6470', decimals: 6, }, toToken: { diff --git a/src/dex/usual/usual-m-wrapped-m.ts b/src/dex/usual/usual-m-wrapped-m.ts index b4a423f30..8eb5d822f 100644 --- a/src/dex/usual/usual-m-wrapped-m.ts +++ b/src/dex/usual/usual-m-wrapped-m.ts @@ -20,7 +20,7 @@ const Config: DexConfigMap = { decimals: 6, }, toToken: { - address: '0xfe274c305b365dc38e188e8f01c4fae2171ce927', + address: '0x4cbc25559dbbd1272ec5b64c7b5f48a2405e6470', decimals: 6, }, }, diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index ac161e99b..934bbdf7c 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -552,7 +552,7 @@ export const Tokens: { symbol: 'wM', }, UsualM: { - address: '0xFe274C305b365dC38e188E8f01c4FAe2171ce927', + address: '0x4cbc25559dbbd1272ec5b64c7b5f48a2405e6470', decimals: 6, symbol: 'USUALM', }, From 18031e970554a153f692841221e281c06328e2b8 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 16 Dec 2024 12:13:43 +0300 Subject: [PATCH 12/27] fix Recently added FluidDex pools are not discovered --- src/dex/fluid-dex/constants.ts | 1 + src/dex/fluid-dex/fluid-dex-e2e.test.ts | 17 + src/dex/fluid-dex/fluid-dex-events.test.ts | 12 + .../fluid-dex/fluid-dex-liquidity-proxy.ts | 42 ++- src/dex/fluid-dex/fluid-dex-pool.ts | 138 ++++++++ src/dex/fluid-dex/fluid-dex.ts | 294 ++++++++++++++---- src/dex/fluid-dex/types.ts | 4 +- tests/constants-e2e.ts | 5 + 8 files changed, 441 insertions(+), 72 deletions(-) create mode 100644 src/dex/fluid-dex/constants.ts create mode 100644 src/dex/fluid-dex/fluid-dex-pool.ts diff --git a/src/dex/fluid-dex/constants.ts b/src/dex/fluid-dex/constants.ts new file mode 100644 index 000000000..da6d99534 --- /dev/null +++ b/src/dex/fluid-dex/constants.ts @@ -0,0 +1 @@ +export const MIN_SWAP_LIQUIDITY = 10n ** 4n; diff --git a/src/dex/fluid-dex/fluid-dex-e2e.test.ts b/src/dex/fluid-dex/fluid-dex-e2e.test.ts index a6873d07d..333aed44d 100644 --- a/src/dex/fluid-dex/fluid-dex-e2e.test.ts +++ b/src/dex/fluid-dex/fluid-dex-e2e.test.ts @@ -118,6 +118,23 @@ describe('FluidDex E2E', () => { describe('Mainnet', () => { const network = Network.MAINNET; + describe('ETH -> INST', () => { + const tokenASymbol: string = 'ETH'; + const tokenBSymbol: string = 'INST'; + + const tokenAAmount: string = '100000000000000'; + const tokenBAmount: string = '100000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + ); + }); + describe('ETH -> wstETH', () => { const tokenASymbol: string = 'wstETH'; const tokenBSymbol: string = 'ETH'; diff --git a/src/dex/fluid-dex/fluid-dex-events.test.ts b/src/dex/fluid-dex/fluid-dex-events.test.ts index 19d6a89d7..974e8a506 100644 --- a/src/dex/fluid-dex/fluid-dex-events.test.ts +++ b/src/dex/fluid-dex/fluid-dex-events.test.ts @@ -10,6 +10,7 @@ import { FluidDexLiquidityProxyState } from './types'; import { FluidDexConfig } from './config'; import { FluidDexLiquidityProxy } from './fluid-dex-liquidity-proxy'; import { FluidDexFactory } from './fluid-dex-factory'; +import { FluidDexEventPool } from './fluid-dex-pool'; jest.setTimeout(50 * 1000); @@ -126,4 +127,15 @@ describe('FluidDex EventPool Mainnet', function () { }, ); }); + + describe('Pool events', () => { + let dexPool: FluidDexEventPool; + + const eventsToTest: Record = { + '0x8710039D5de6840EdE452A85672B32270a709aE2': { + LogPauseSwapAndArbitrage: [21337128], + LogUnpauseSwapAndArbitrage: [], + }, + }; + }); }); diff --git a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts index ae61daa84..036eb43d1 100644 --- a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts +++ b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts @@ -5,6 +5,7 @@ import { bigIntify, catchParseLogError } from '../../utils'; import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; import { IDexHelper } from '../../dex-helper/idex-helper'; import ResolverABI from '../../abi/fluid-dex/resolver.abi.json'; +import FluidDexPoolABI from '../../abi/fluid-dex/fluid-dex.abi.json'; import LiquidityABI from '../../abi/fluid-dex/liquidityUserModule.abi.json'; import { CommonAddresses, @@ -13,7 +14,13 @@ import { PoolReserveResponse, } from './types'; import { Address } from '../../types'; -import { Contract } from 'ethers'; +import { Contract, ethers } from 'ethers'; +import { uint256ToBigInt } from '../../lib/decoders'; +import { DecodedStateMultiCallResultWithRelativeBitmaps } from '../uniswap-v3/types'; + +const { + utils: { hexlify, hexZeroPad }, +} = ethers; export class FluidDexLiquidityProxy extends StatefulEventSubscriber { handlers: { @@ -28,7 +35,9 @@ export class FluidDexLiquidityProxy extends StatefulEventSubscriber ({ + // target: pool.pool, + // callData: this.poolIface.encodeFunctionData('readFromStorage', [ + // hexZeroPad(hexlify(1), 32), + // ]), + // decodeFunction: uint256ToBigInt, + // })); + // + // const storageResults = await this.dexHelper.multiWrapper.tryAggregate< + // bigint | DecodedStateMultiCallResultWithRelativeBitmaps + // >( + // false, + // multicallData, + // blockNumber, + // this.dexHelper.multiWrapper.defaultBatchSize, + // false, + // ); + // + // const poolsReserves = convertedResult.poolsReserves.map( + // (poolReserve, index) => { + // const isSwapAndArbitragePaused = + // BigInt(storageResults[index].returnData.toString()) >> BigInt(255) === + // BigInt(1); + // return { ...poolReserve, isSwapAndArbitragePaused }; + // }, + // ); + // this.logger.info(`${this.parentName}: ${this.name}: generating state...`); return convertedResult; diff --git a/src/dex/fluid-dex/fluid-dex-pool.ts b/src/dex/fluid-dex/fluid-dex-pool.ts new file mode 100644 index 000000000..2fbfa64c7 --- /dev/null +++ b/src/dex/fluid-dex/fluid-dex-pool.ts @@ -0,0 +1,138 @@ +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; +import { DeepReadonly } from 'ts-essentials'; +import { Address, Log, Logger } from '../../types'; +import { IDexHelper } from '../../dex-helper'; +import { Interface } from '@ethersproject/abi'; +import FluidDexPoolABI from '../../abi/fluid-dex/fluid-dex.abi.json'; +import { catchParseLogError } from '../../utils'; +import { ethers } from 'ethers'; +import { uint256ToBigInt } from '../../lib/decoders'; +import { DecodedStateMultiCallResultWithRelativeBitmaps } from '../uniswap-v3/types'; + +const { + utils: { hexlify, hexZeroPad }, +} = ethers; + +type PoolState = { + isSwapAndArbitragePaused: boolean; +}; + +export class FluidDexEventPool extends StatefulEventSubscriber { + handlers: { + [event: string]: ( + event: any, + state: DeepReadonly, + log: Readonly, + ) => DeepReadonly | null; + } = {}; + + logDecoder: (log: Log) => any; + addressesSubscribed: Address[]; + protected poolIface = new Interface(FluidDexPoolABI); + + constructor( + readonly parentName: string, + readonly poolAddress: string, + protected network: number, + protected dexHelper: IDexHelper, + logger: Logger, + ) { + super(parentName, 'pool', dexHelper, logger); + + this.logDecoder = (log: Log) => this.poolIface.parseLog(log); + this.addressesSubscribed = [poolAddress]; + + // Add handlers + this.handlers['LogPauseSwapAndArbitrage'] = + this.handleLogPauseSwapAndArbitrage.bind(this); + this.handlers['LogUnpauseSwapAndArbitrage'] = + this.handleLogUnpauseSwapAndArbitrage.bind(this); + } + + /** + * The function is called every time any of the subscribed + * addresses release log. The function accepts the current + * state, updates the state according to the log, and returns + * the updated state. + * @param state - Current state of event subscriber + * @param log - Log released by one of the subscribed addresses + * @returns Updates state of the event subscriber after the log + */ + async processLog( + state: DeepReadonly, + log: Readonly, + ): Promise | null> { + try { + let event; + try { + event = this.logDecoder(log); + } catch (e) { + return null; + } + if (event.name in this.handlers) { + return this.handlers[event.name](event, state, log); + } + } catch (e) { + catchParseLogError(e, this.logger); + } + + return null; + } + + handleLogPauseSwapAndArbitrage() { + return { isSwapAndArbitragePaused: true }; + } + + handleLogUnpauseSwapAndArbitrage() { + return { isSwapAndArbitragePaused: false }; + } + + async getStateOrGenerate( + blockNumber: number, + readonly: boolean = false, + ): Promise> { + let state = this.getState(blockNumber); + if (!state) { + state = await this.generateState(blockNumber); + if (!readonly) this.setState(state, blockNumber); + } + return state; + } + + /** + * The function generates state using on-chain calls. This + * function is called to regenerate state if the event based + * system fails to fetch events and the local state is no + * more correct. + * @param blockNumber - Blocknumber for which the state should + * should be generated + * @returns state of the event subscriber at blocknumber + */ + async generateState(blockNumber: number): Promise> { + const multicallData = [ + { + target: this.addressesSubscribed[0], + callData: this.poolIface.encodeFunctionData('readFromStorage', [ + hexZeroPad(hexlify(1), 32), + ]), + decodeFunction: uint256ToBigInt, + }, + ]; + + const storageResults = await this.dexHelper.multiWrapper.tryAggregate< + bigint | DecodedStateMultiCallResultWithRelativeBitmaps + >( + false, + multicallData, + blockNumber, + this.dexHelper.multiWrapper.defaultBatchSize, + false, + ); + + const isSwapAndArbitragePaused = + BigInt(storageResults[0].returnData.toString()) >> BigInt(255) === + BigInt(1); + + return { isSwapAndArbitragePaused }; + } +} diff --git a/src/dex/fluid-dex/fluid-dex.ts b/src/dex/fluid-dex/fluid-dex.ts index e8ea87ad6..a15894031 100644 --- a/src/dex/fluid-dex/fluid-dex.ts +++ b/src/dex/fluid-dex/fluid-dex.ts @@ -34,6 +34,8 @@ import { generalDecoder } from '../../lib/decoders'; import { BigNumber } from 'ethers'; import { sqrt } from './utils'; import { FluidDexLiquidityProxy } from './fluid-dex-liquidity-proxy'; +import { FluidDexEventPool } from './fluid-dex-pool'; +import { MIN_SWAP_LIQUIDITY } from './constants'; export class FluidDex extends SimpleExchange implements IDex { readonly hasConstantPriceLargeAmounts = false; @@ -46,14 +48,14 @@ export class FluidDex extends SimpleExchange implements IDex { pools: FluidDexPool[] = []; + eventPools: FluidDexEventPool[] = []; + readonly factory: FluidDexFactory; readonly liquidityProxy: FluidDexLiquidityProxy; readonly fluidDexPoolIface: Interface; - FEE_100_PERCENT = BigInt(1000000); - constructor( readonly network: Network, readonly dexKey: string, @@ -110,6 +112,20 @@ export class FluidDex extends SimpleExchange implements IDex { await this.factory.initialize(blockNumber); this.pools = await this.fetchFluidDexPools(blockNumber); + this.eventPools = await Promise.all( + this.pools.map(async pool => { + const eventPool = new FluidDexEventPool( + this.dexKey, + pool.address, + this.network, + this.dexHelper, + this.logger, + ); + await eventPool.initialize(blockNumber); + return eventPool; + }), + ); + await this.liquidityProxy.initialize(blockNumber); } @@ -147,29 +163,24 @@ export class FluidDex extends SimpleExchange implements IDex { side: SwapSide, blockNumber: number, ): Promise { - const pool = this.getPoolByTokenPair(srcToken.address, destToken.address); - return pool ? [pool.id] : []; + const pools = this.getPoolsByTokenPair(srcToken.address, destToken.address); + return pools.map(pool => pool.id); } - getPoolByTokenPair( - srcToken: Address, - destToken: Address, - ): FluidDexPool | null { + getPoolsByTokenPair(srcToken: Address, destToken: Address): FluidDexPool[] { const srcAddress = srcToken.toLowerCase(); const destAddress = destToken.toLowerCase(); // A pair must have 2 different tokens. - if (srcAddress === destAddress) return null; + if (srcAddress === destAddress) return []; - for (const pool of this.pools) { - if ( + const pools = this.pools.filter( + pool => (srcAddress === pool.token0 && destAddress === pool.token1) || - (srcAddress === pool.token1 && destAddress === pool.token0) - ) { - return pool; - } - } - return null; + (srcAddress === pool.token1 && destAddress === pool.token0), + ); + + return pools; } // Returns pool prices for amounts. @@ -187,61 +198,99 @@ export class FluidDex extends SimpleExchange implements IDex { try { if (srcToken.address.toLowerCase() === destToken.address.toLowerCase()) return null; - // Get the pool to use. - const pool = this.getPoolByTokenPair(srcToken.address, destToken.address); - if (!pool) return null; + + // Get the pools to use. + const pools = this.getPoolsByTokenPair( + srcToken.address, + destToken.address, + ); + + if (!pools.length) return null; + const poolIds = pools.map(pool => pool.id); // Make sure the pool meets the optional limitPools filter. - if (limitPools && !limitPools.includes(pool.id)) return null; + if ( + limitPools && + !limitPools.every(limitPool => poolIds.includes(limitPool)) + ) + return null; const liquidityProxyState = await this.liquidityProxy.getStateOrGenerate( blockNumber, ); - const currentPoolReserves = liquidityProxyState.poolsReserves.find( - poolReserve => - poolReserve.pool.toLowerCase() === pool.address.toLowerCase(), - ); - if (!currentPoolReserves) { - return null; - } - const prices = amounts.map(amount => { - if (side === SwapSide.SELL) { - return this.swapIn( - srcToken.address.toLowerCase() === pool.token0.toLowerCase(), - amount, - currentPoolReserves.collateralReserves, - currentPoolReserves.debtReserves, - srcToken.decimals, - destToken.decimals, - BigInt(currentPoolReserves.fee), - currentPoolReserves.dexLimits, - Math.floor(Date.now() / 1000), + const poolsPrices = await Promise.all( + pools.map(async pool => { + const currentPoolReserves = liquidityProxyState.poolsReserves.find( + poolReserve => + poolReserve.pool.toLowerCase() === pool.address.toLowerCase(), ); - } else { - return this.swapOut( - srcToken.address.toLowerCase() === pool.token0.toLowerCase(), - amount, - currentPoolReserves.collateralReserves, - currentPoolReserves.debtReserves, - srcToken.decimals, - destToken.decimals, - BigInt(currentPoolReserves.fee), - currentPoolReserves.dexLimits, - Math.floor(Date.now() / 1000), + + const eventPool = this.eventPools.find( + eventPool => + eventPool.poolAddress.toLowerCase() === + pool.address.toLowerCase(), ); - } - }); - return [ - { - prices: prices, - unit: getBigIntPow(destToken.decimals), - data: {}, - exchange: this.dexKey, - poolIdentifier: pool.id, - gasCost: FLUID_DEX_GAS_COST, - poolAddresses: [pool.address], - }, - ]; + + if (!eventPool) { + this.logger.warn( + `${this.dexKey}-${this.network}: Event pool ${pool.address} was not found...`, + ); + return null; + } + + const state = await eventPool.getStateOrGenerate(blockNumber); + + if (!currentPoolReserves || state.isSwapAndArbitragePaused === true) { + return null; + } + + const prices = amounts.map(amount => { + if (side === SwapSide.SELL) { + return this.swapIn( + srcToken.address.toLowerCase() === pool.token0.toLowerCase(), + amount, + currentPoolReserves.collateralReserves, + currentPoolReserves.debtReserves, + srcToken.decimals, + destToken.decimals, + BigInt(currentPoolReserves.fee), + currentPoolReserves.dexLimits, + Math.floor(Date.now() / 1000), + ); + } else { + return this.swapOut( + srcToken.address.toLowerCase() === pool.token0.toLowerCase(), + amount, + currentPoolReserves.collateralReserves, + currentPoolReserves.debtReserves, + srcToken.decimals, + destToken.decimals, + BigInt(currentPoolReserves.fee), + currentPoolReserves.dexLimits, + Math.floor(Date.now() / 1000), + ); + } + }); + + return { + prices: prices, + unit: getBigIntPow(destToken.decimals), + data: { + poolId: pool.id, + }, + exchange: this.dexKey, + poolIdentifier: pool.id, + gasCost: FLUID_DEX_GAS_COST, + poolAddresses: [pool.address], + }; + }), + ); + + const notNullResults = poolsPrices.filter( + res => res !== null, + ) as ExchangePrices; + + return notNullResults; } catch (e) { this.logger.error( `Error_getPricesVolume ${srcToken.address || srcToken.symbol}, ${ @@ -272,10 +321,9 @@ export class FluidDex extends SimpleExchange implements IDex { ): AdapterExchangeParam { // Encode here the payload for adapter const payload = ''; - const pool = this.getPoolByTokenPair(srcToken, destToken); return { - targetExchange: pool!.address, + targetExchange: '0x', payload, networkFee: '0', }; @@ -313,7 +361,11 @@ export class FluidDex extends SimpleExchange implements IDex { side === SwapSide.SELL ? 'amountOut_' : 'amountIn_', ); - const pool = this.getPoolByTokenPair(srcToken, destToken); + const pool = this.pools.find(pool => pool.id === data.poolId); + if (!pool) + throw new Error( + `${this.dexKey}-${this.network}: Pool with id: ${data.poolId} was not found`, + ); if (side === SwapSide.SELL) { if (pool!.token0.toLowerCase() !== srcToken.toLowerCase()) { @@ -584,11 +636,84 @@ export class FluidDex extends SimpleExchange implements IDex { if (priceDiff > maxAllowedDiff) { return 0n; } + + if (amountInCollateral > 0) { + let reservesRatioValid = swap0To1 + ? this.verifyToken1Reserves( + colReserveIn + amountInCollateral, + colReserveOut - amountOutCollateral, + oldPrice, + ) + : this.verifyToken0Reserves( + colReserveOut - amountOutCollateral, + colReserveIn + amountInCollateral, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + + if (amountInDebt > 0) { + let reservesRatioValid = swap0To1 + ? this.verifyToken1Reserves( + debtReserveIn + amountInDebt, + debtReserveOut - amountOutDebt, + oldPrice, + ) + : this.verifyToken0Reserves( + debtReserveOut - amountOutDebt, + debtReserveIn + amountInDebt, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + const totalAmountOut = amountOutCollateral + amountOutDebt; return totalAmountOut; } + /** + * Checks if token0 reserves are sufficient compared to token1 reserves. + * This helps prevent edge cases and ensures high precision in calculations. + * @param {number} token0Reserves - The reserves of token0. + * @param {number} token1Reserves - The reserves of token1. + * @param {number} price - The current price used for calculation. + * @returns {boolean} - Returns false if token0 reserves are too low, true otherwise. + */ + protected verifyToken0Reserves( + token0Reserves: bigint, + token1Reserves: bigint, + price: bigint, + ): boolean { + return ( + token0Reserves >= + (token1Reserves * 10n ** 27n) / (price * MIN_SWAP_LIQUIDITY) + ); + } + + /** + * Checks if token1 reserves are sufficient compared to token0 reserves. + * This helps prevent edge cases and ensures high precision in calculations. + * @param {number} token0Reserves - The reserves of token0. + * @param {number} token1Reserves - The reserves of token1. + * @param {number} price - The current price used for calculation. + * @returns {boolean} - Returns false if token1 reserves are too low, true otherwise. + */ + protected verifyToken1Reserves( + token0Reserves: bigint, + token1Reserves: bigint, + price: bigint, + ): boolean { + return ( + token1Reserves >= + (token0Reserves * price) / (10n ** 27n * MIN_SWAP_LIQUIDITY) + ); + } + /** * Calculates the currently available swappable amount for a token limit considering expansion since last syncTime. * @param syncTime - timestamp in seconds when the limits were synced @@ -765,7 +890,7 @@ export class FluidDex extends SimpleExchange implements IDex { syncTime, ); - if (amountIn == 2n ** 256n - 1n) { + if (amountIn === 2n ** 256n - 1n) { return amountIn; } const ans = (amountIn * BigInt(10 ** inDecimals)) / BigInt(10 ** 12); @@ -985,6 +1110,39 @@ export class FluidDex extends SimpleExchange implements IDex { return 2n ** 256n - 1n; } + if (amountInCollateral > 0) { + let reservesRatioValid = swap0to1 + ? this.verifyToken1Reserves( + colReserveIn + amountInCollateral, + colReserveOut - amountOutCollateral, + oldPrice, + ) + : this.verifyToken0Reserves( + colReserveOut - amountOutCollateral, + colReserveIn + amountInCollateral, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + if (amountInDebt > 0) { + let reservesRatioValid = swap0to1 + ? this.verifyToken1Reserves( + debtReserveIn + amountInDebt, + debtReserveOut - amountOutDebt, + oldPrice, + ) + : this.verifyToken0Reserves( + debtReserveOut - amountOutDebt, + debtReserveIn + amountInDebt, + oldPrice, + ); + if (!reservesRatioValid) { + return 0n; + } + } + const totalAmountIn = amountInCollateral + amountInDebt; return totalAmountIn; diff --git a/src/dex/fluid-dex/types.ts b/src/dex/fluid-dex/types.ts index cffe433ce..0fb982e07 100644 --- a/src/dex/fluid-dex/types.ts +++ b/src/dex/fluid-dex/types.ts @@ -72,7 +72,9 @@ export interface PoolWithReserves { debtReserves: DebtReserves; } -export type FluidDexData = {}; +export type FluidDexData = { + poolId: string; +}; // Each pool has a contract address and token pairs. export type FluidDexPool = { diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index a924a7192..bd2c3934e 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -131,6 +131,10 @@ export const Tokens: { addBalance: balancesFn, addAllowance: allowedFn, }, + INST: { + address: '0x6f40d4A6237C257fff2dB00FA0510DeEECd303eb', + decimals: 18, + }, aEthUSDC: { address: '0x98c23e9d8f34fefb1b7bd6a91b7ff122f4e16f5c', decimals: 6, @@ -1681,6 +1685,7 @@ export const Holders: { SKY: '0x0ddda327A6614130CCb20bc0097313A282176A01', MKR: '0xe9aAA7A9DDc0877626C1779AbC29993aD89A6c1f', ETHx: '0xFCC1A2c71F01B7f58Ed538a6B4AAa5A0724eB5A6', + INST: '0xe2Dd506477D4792A7E811D2E93D44CeBa82c668B', // Idle tokens AA_wstETH: '0xd7C1b48877A7dFA7D51cf1144c89C0A3F134F935', 'AA_idle_cpPOR-USDC': '0x085c8eaccA6911fE60aE3f8FbAe5F3012E3A05Ec', From 1349dfefa2f5637a1687d85af1691fafc4dc38a6 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 16 Dec 2024 13:36:29 +0300 Subject: [PATCH 13/27] add fixes --- src/abi/fluid-dex/fluid-dex.abi.json | 606 +++++++++++++++++- src/dex/fluid-dex/fluid-dex-events.test.ts | 50 +- .../fluid-dex/fluid-dex-liquidity-proxy.ts | 34 - src/dex/fluid-dex/fluid-dex-pool.ts | 5 +- 4 files changed, 628 insertions(+), 67 deletions(-) diff --git a/src/abi/fluid-dex/fluid-dex.abi.json b/src/abi/fluid-dex/fluid-dex.abi.json index 14ada197a..cdd5e0f1a 100644 --- a/src/abi/fluid-dex/fluid-dex.abi.json +++ b/src/abi/fluid-dex/fluid-dex.abi.json @@ -1,9 +1,119 @@ [ + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "dexId", + "type": "uint256" + }, + { + "internalType": "address", + "name": "liquidity", + "type": "address" + }, + { + "internalType": "address", + "name": "factory", + "type": "address" + }, + { + "components": [ + { + "internalType": "address", + "name": "shift", + "type": "address" + }, + { + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "internalType": "address", + "name": "colOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "debtOperations", + "type": "address" + }, + { + "internalType": "address", + "name": "perfectOperationsAndSwapOut", + "type": "address" + } + ], + "internalType": "struct Structs.Implementations", + "name": "implementations", + "type": "tuple" + }, + { + "internalType": "address", + "name": "deployerContract", + "type": "address" + }, + { + "internalType": "address", + "name": "token0", + "type": "address" + }, + { + "internalType": "address", + "name": "token1", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "supplyToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "supplyToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "borrowToken1Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken0Slot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "exchangePriceToken1Slot", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "oracleMapping", + "type": "uint256" + } + ], + "internalType": "struct Structs.ConstantViews", + "name": "constantViews_", + "type": "tuple" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, { "inputs": [ { "internalType": "uint256", - "name": "errorId", + "name": "errorId_", "type": "uint256" } ], @@ -14,7 +124,18 @@ "inputs": [ { "internalType": "uint256", - "name": "shares", + "name": "errorId", + "type": "uint256" + } + ], + "name": "FluidDexFactoryError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "shares_", "type": "uint256" } ], @@ -87,7 +208,7 @@ "type": "uint256" } ], - "internalType": "struct IFluidDexT1.PricesAndExchangePrice", + "internalType": "struct Structs.PricesAndExchangePrice", "name": "pex_", "type": "tuple" } @@ -117,6 +238,346 @@ "name": "FluidDexSwapResult", "type": "error" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidLiquidityCalcsError", + "type": "error" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "errorId_", + "type": "uint256" + } + ], + "name": "FluidSafeTransferError", + "type": "error" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "int256", + "name": "routing", + "type": "int256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amtOut", + "type": "uint256" + } + ], + "name": "LogArbitrage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogBorrowDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogBorrowPerfectDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogDepositColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogDepositPerfectColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + ], + "name": "LogPauseSwapAndArbitrage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + ], + "name": "LogUnpauseSwapAndArbitrage", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogPaybackDebtInOneToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogPaybackDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogPaybackPerfectDebtLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogWithdrawColInOneToken", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + } + ], + "name": "LogWithdrawColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "shares", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token0Amt", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "token1Amt", + "type": "uint256" + } + ], + "name": "LogWithdrawPerfectColLiquidity", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "bool", + "name": "swap0to1", + "type": "bool" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountIn", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amountOut", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "stateMutability": "payable", + "type": "fallback" + }, { "inputs": [], "name": "DEX_ID", @@ -148,9 +609,9 @@ "type": "uint256" }, { - "internalType": "bool", - "name": "estimate_", - "type": "bool" + "internalType": "address", + "name": "to_", + "type": "address" } ], "name": "borrow", @@ -182,9 +643,9 @@ "type": "uint256" }, { - "internalType": "bool", - "name": "estimate_", - "type": "bool" + "internalType": "address", + "name": "to_", + "type": "address" } ], "name": "borrowPerfect", @@ -248,11 +709,11 @@ }, { "internalType": "address", - "name": "perfectOperationsAndOracle", + "name": "perfectOperationsAndSwapOut", "type": "address" } ], - "internalType": "struct IFluidDexT1.Implementations", + "internalType": "struct Structs.Implementations", "name": "implementations", "type": "tuple" }, @@ -307,7 +768,7 @@ "type": "uint256" } ], - "internalType": "struct IFluidDexT1.ConstantViews", + "internalType": "struct Structs.ConstantViews", "name": "constantsView_", "type": "tuple" } @@ -342,7 +803,7 @@ "type": "uint256" } ], - "internalType": "struct IFluidDexT1.ConstantViews2", + "internalType": "struct Structs.ConstantViews2", "name": "constantsView2_", "type": "tuple" } @@ -476,7 +937,7 @@ "type": "uint256" } ], - "internalType": "struct IFluidDexT1.CollateralReserves", + "internalType": "struct Structs.CollateralReserves", "name": "c_", "type": "tuple" } @@ -547,7 +1008,7 @@ "type": "uint256" } ], - "internalType": "struct IFluidDexT1.DebtReserves", + "internalType": "struct Structs.DebtReserves", "name": "d_", "type": "tuple" } @@ -562,6 +1023,29 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "liquidityCallback", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -605,7 +1089,7 @@ "type": "uint256" } ], - "internalType": "struct IFluidDexT1.Oracle[]", + "internalType": "struct Structs.Oracle[]", "name": "twaps_", "type": "tuple[]" }, @@ -778,6 +1262,40 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountOutMin_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "swapInWithCallback", + "outputs": [ + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -812,6 +1330,40 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "bool", + "name": "swap0to1_", + "type": "bool" + }, + { + "internalType": "uint256", + "name": "amountOut_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amountInMax_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to_", + "type": "address" + } + ], + "name": "swapOutWithCallback", + "outputs": [ + { + "internalType": "uint256", + "name": "amountIn_", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, { "inputs": [ { @@ -830,9 +1382,9 @@ "type": "uint256" }, { - "internalType": "bool", - "name": "estimate_", - "type": "bool" + "internalType": "address", + "name": "to_", + "type": "address" } ], "name": "withdraw", @@ -864,9 +1416,9 @@ "type": "uint256" }, { - "internalType": "bool", - "name": "estimate_", - "type": "bool" + "internalType": "address", + "name": "to_", + "type": "address" } ], "name": "withdrawPerfect", @@ -903,9 +1455,9 @@ "type": "uint256" }, { - "internalType": "bool", - "name": "estimate_", - "type": "bool" + "internalType": "address", + "name": "to_", + "type": "address" } ], "name": "withdrawPerfectInOneToken", @@ -918,5 +1470,9 @@ ], "stateMutability": "nonpayable", "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" } ] diff --git a/src/dex/fluid-dex/fluid-dex-events.test.ts b/src/dex/fluid-dex/fluid-dex-events.test.ts index 974e8a506..f5bad501d 100644 --- a/src/dex/fluid-dex/fluid-dex-events.test.ts +++ b/src/dex/fluid-dex/fluid-dex-events.test.ts @@ -11,14 +11,15 @@ import { FluidDexConfig } from './config'; import { FluidDexLiquidityProxy } from './fluid-dex-liquidity-proxy'; import { FluidDexFactory } from './fluid-dex-factory'; import { FluidDexEventPool } from './fluid-dex-pool'; +import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; jest.setTimeout(50 * 1000); -async function fetchLiquidityProxyState( - liquidityProxy: FluidDexLiquidityProxy, +async function fetchState( + statefulEventSubscriber: StatefulEventSubscriber, blockNumber: number, -): Promise { - return liquidityProxy.generateState(blockNumber); +): Promise { + return statefulEventSubscriber.generateState(blockNumber); } // eventName -> blockNumbers @@ -65,7 +66,7 @@ describe('FluidDex EventPool Mainnet', function () { liquidityProxy, liquidityProxy.addressesSubscribed, (_blockNumber: number) => - fetchLiquidityProxyState(liquidityProxy, _blockNumber), + fetchState(liquidityProxy, _blockNumber), blockNumber, `${dexKey}_${poolAddress}`, dexHelper.provider, @@ -134,8 +135,45 @@ describe('FluidDex EventPool Mainnet', function () { const eventsToTest: Record = { '0x8710039D5de6840EdE452A85672B32270a709aE2': { LogPauseSwapAndArbitrage: [21337128], - LogUnpauseSwapAndArbitrage: [], + }, + '0x2886a01a0645390872a9eb99dae1283664b0c524': { + LogPauseSwapAndArbitrage: [21374547], }, }; + + Object.entries(eventsToTest).forEach( + ([poolAddress, events]: [string, EventMappings]) => { + describe(`Events for ${poolAddress}`, () => { + beforeEach(() => { + dexPool = new FluidDexEventPool( + dexKey, + poolAddress, + network, + dexHelper, + logger, + ); + }); + Object.entries(events).forEach( + ([eventName, blockNumbers]: [string, number[]]) => { + describe(`${eventName}`, () => { + blockNumbers.forEach((blockNumber: number) => { + it(`State after ${blockNumber}`, async function () { + await testEventSubscriber( + dexPool, + dexPool.addressesSubscribed, + (_blockNumber: number) => + fetchState(dexPool, _blockNumber), + blockNumber, + `${dexKey}_${poolAddress}`, + dexHelper.provider, + ); + }); + }); + }); + }, + ); + }); + }, + ); }); }); diff --git a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts index 036eb43d1..63276bfb1 100644 --- a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts +++ b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts @@ -15,12 +15,6 @@ import { } from './types'; import { Address } from '../../types'; import { Contract, ethers } from 'ethers'; -import { uint256ToBigInt } from '../../lib/decoders'; -import { DecodedStateMultiCallResultWithRelativeBitmaps } from '../uniswap-v3/types'; - -const { - utils: { hexlify, hexZeroPad }, -} = ethers; export class FluidDexLiquidityProxy extends StatefulEventSubscriber { handlers: { @@ -132,34 +126,6 @@ export class FluidDexLiquidityProxy extends StatefulEventSubscriber ({ - // target: pool.pool, - // callData: this.poolIface.encodeFunctionData('readFromStorage', [ - // hexZeroPad(hexlify(1), 32), - // ]), - // decodeFunction: uint256ToBigInt, - // })); - // - // const storageResults = await this.dexHelper.multiWrapper.tryAggregate< - // bigint | DecodedStateMultiCallResultWithRelativeBitmaps - // >( - // false, - // multicallData, - // blockNumber, - // this.dexHelper.multiWrapper.defaultBatchSize, - // false, - // ); - // - // const poolsReserves = convertedResult.poolsReserves.map( - // (poolReserve, index) => { - // const isSwapAndArbitragePaused = - // BigInt(storageResults[index].returnData.toString()) >> BigInt(255) === - // BigInt(1); - // return { ...poolReserve, isSwapAndArbitragePaused }; - // }, - // ); - // this.logger.info(`${this.parentName}: ${this.name}: generating state...`); return convertedResult; diff --git a/src/dex/fluid-dex/fluid-dex-pool.ts b/src/dex/fluid-dex/fluid-dex-pool.ts index 2fbfa64c7..02fbf3177 100644 --- a/src/dex/fluid-dex/fluid-dex-pool.ts +++ b/src/dex/fluid-dex/fluid-dex-pool.ts @@ -69,6 +69,7 @@ export class FluidDexEventPool extends StatefulEventSubscriber { } catch (e) { return null; } + if (event.name in this.handlers) { return this.handlers[event.name](event, state, log); } @@ -79,11 +80,11 @@ export class FluidDexEventPool extends StatefulEventSubscriber { return null; } - handleLogPauseSwapAndArbitrage() { + handleLogPauseSwapAndArbitrage(): PoolState { return { isSwapAndArbitragePaused: true }; } - handleLogUnpauseSwapAndArbitrage() { + handleLogUnpauseSwapAndArbitrage(): PoolState { return { isSwapAndArbitragePaused: false }; } From 782f3c15a7d2147e15257133fce3bce9626e840f Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 16 Dec 2024 13:37:11 +0300 Subject: [PATCH 14/27] 4.0.5-fluid-dex-pool-discovery-issue.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 21bb6a260..30bf16a42 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.4", + "version": "4.0.5-fluid-dex-pool-discovery-issue.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From a2119ed11db6f3984a6c0cec93875608cc979167 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Mon, 16 Dec 2024 16:06:17 +0200 Subject: [PATCH 15/27] fix: handle at least one available pool from limit pools --- src/dex/fluid-dex/fluid-dex.ts | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/dex/fluid-dex/fluid-dex.ts b/src/dex/fluid-dex/fluid-dex.ts index a15894031..fa3284ea4 100644 --- a/src/dex/fluid-dex/fluid-dex.ts +++ b/src/dex/fluid-dex/fluid-dex.ts @@ -200,19 +200,13 @@ export class FluidDex extends SimpleExchange implements IDex { return null; // Get the pools to use. - const pools = this.getPoolsByTokenPair( - srcToken.address, - destToken.address, - ); + let pools = this.getPoolsByTokenPair(srcToken.address, destToken.address); + + if (limitPools) { + pools = pools.filter(pool => limitPools.includes(pool.id)); + } if (!pools.length) return null; - const poolIds = pools.map(pool => pool.id); - // Make sure the pool meets the optional limitPools filter. - if ( - limitPools && - !limitPools.every(limitPool => poolIds.includes(limitPool)) - ) - return null; const liquidityProxyState = await this.liquidityProxy.getStateOrGenerate( blockNumber, From e571369742c38ef638c40a0c5de7734c256fda28 Mon Sep 17 00:00:00 2001 From: Danylo Kanievskyi Date: Mon, 16 Dec 2024 16:08:34 +0200 Subject: [PATCH 16/27] fix: removed unused imports & poolIface on FluidDexLiquidityProxy --- src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts index 63276bfb1..f6f7b890b 100644 --- a/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts +++ b/src/dex/fluid-dex/fluid-dex-liquidity-proxy.ts @@ -5,7 +5,6 @@ import { bigIntify, catchParseLogError } from '../../utils'; import { StatefulEventSubscriber } from '../../stateful-event-subscriber'; import { IDexHelper } from '../../dex-helper/idex-helper'; import ResolverABI from '../../abi/fluid-dex/resolver.abi.json'; -import FluidDexPoolABI from '../../abi/fluid-dex/fluid-dex.abi.json'; import LiquidityABI from '../../abi/fluid-dex/liquidityUserModule.abi.json'; import { CommonAddresses, @@ -14,7 +13,7 @@ import { PoolReserveResponse, } from './types'; import { Address } from '../../types'; -import { Contract, ethers } from 'ethers'; +import { Contract } from 'ethers'; export class FluidDexLiquidityProxy extends StatefulEventSubscriber { handlers: { @@ -31,8 +30,6 @@ export class FluidDexLiquidityProxy extends StatefulEventSubscriber Date: Mon, 16 Dec 2024 17:28:56 +0300 Subject: [PATCH 17/27] 4.0.5-fluid-dex-pool-discovery-issue.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 30bf16a42..cda56f083 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.5-fluid-dex-pool-discovery-issue.0", + "version": "4.0.5-fluid-dex-pool-discovery-issue.1", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From a70655f5b707152d52f8297c2d6c98acddcbb8dd Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 16 Dec 2024 17:31:54 +0300 Subject: [PATCH 18/27] small fix --- src/dex/fluid-dex/fluid-dex-pool.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/dex/fluid-dex/fluid-dex-pool.ts b/src/dex/fluid-dex/fluid-dex-pool.ts index 02fbf3177..7550933de 100644 --- a/src/dex/fluid-dex/fluid-dex-pool.ts +++ b/src/dex/fluid-dex/fluid-dex-pool.ts @@ -131,8 +131,7 @@ export class FluidDexEventPool extends StatefulEventSubscriber { ); const isSwapAndArbitragePaused = - BigInt(storageResults[0].returnData.toString()) >> BigInt(255) === - BigInt(1); + BigInt(storageResults[0].returnData.toString()) >> 255n === 1n; return { isSwapAndArbitragePaused }; } From 867e0764d585364526944e16294772fddec9279d Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 16 Dec 2024 17:34:24 +0300 Subject: [PATCH 19/27] 4.0.5-fluid-dex-pool-discovery-issue.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index cda56f083..06e6dbdc9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.5-fluid-dex-pool-discovery-issue.1", + "version": "4.0.5-fluid-dex-pool-discovery-issue.2", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From dc1db6bd3739ff0183cda76a3724353f5c7ddb85 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Mon, 16 Dec 2024 18:46:13 +0300 Subject: [PATCH 20/27] 4.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d8297852..48411ccfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.5", + "version": "4.0.6", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From e00e27615aa3538f4ca68c8574e8ac07d0f4d5c1 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 17 Dec 2024 11:34:37 +0300 Subject: [PATCH 21/27] add holder for UsualM --- src/dex/index.ts | 1 - src/dex/usual/usual-e2e.test.ts | 4 ++-- tests/constants-e2e.ts | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dex/index.ts b/src/dex/index.ts index 84d709975..89e9008f8 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -91,7 +91,6 @@ import { ConcentratorArusd } from './concentrator-arusd/concentrator-arusd'; import { FxProtocolRusd } from './fx-protocol-rusd/fx-protocol-rusd'; import { AaveGsm } from './aave-gsm/aave-gsm'; import { LitePsm } from './lite-psm/lite-psm'; -import { UsualBond } from './usual-bond/usual-bond'; import { StkGHO } from './stkgho/stkgho'; import { BalancerV3 } from './balancer-v3/balancer-v3'; import { balancerV3Merge } from './balancer-v3/optimizer'; diff --git a/src/dex/usual/usual-e2e.test.ts b/src/dex/usual/usual-e2e.test.ts index d75673ee8..1c3008a15 100644 --- a/src/dex/usual/usual-e2e.test.ts +++ b/src/dex/usual/usual-e2e.test.ts @@ -107,8 +107,8 @@ describe('UsualM<>Usd0 E2E', () => { const tokenASymbol: string = 'UsualM'; const tokenBSymbol: string = 'USD0'; - const tokenAAmount: string = '100000'; - const tokenBAmount: string = '100000'; + const tokenAAmount: string = '1000000'; + const tokenBAmount: string = '1000000'; testForNetwork( network, diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 175796991..744baed67 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -1811,6 +1811,7 @@ export const Holders: { 'USD0++': '0x2227b6806339906707b43F36a1f07B52FF7Fa776', USDM: '0x57F5E098CaD7A3D1Eed53991D4d66C45C9AF7812', wUSDM: '0x3B95bC951EE0f553ba487327278cAc44f29715E5', + UsualM: '0xE3f7A0c4a44b740328157A5152A85c3bCB54DA09', }, [Network.POLYGON]: { jGBP: '0x02aa0B826c7BA6386DdBE04C0a8715A1c0A16B24', From f3ef370c595e14e8dd7dd86114360ff5717c741b Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 17 Dec 2024 11:36:34 +0300 Subject: [PATCH 22/27] 4.0.7-smart-m-usual-m-usd0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48411ccfe..7bed88c51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.6", + "version": "4.0.7-smart-m-usual-m-usd0.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 09316bd71fb7e335d0db01b5ceb40437be9b2bf7 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 17 Dec 2024 12:35:11 +0300 Subject: [PATCH 23/27] add fix for pricing --- src/dex/usual/usual.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/dex/usual/usual.ts b/src/dex/usual/usual.ts index 872b75904..b17019e5d 100644 --- a/src/dex/usual/usual.ts +++ b/src/dex/usual/usual.ts @@ -96,8 +96,12 @@ export class Usual extends SimpleExchange implements IDex { return null; } - const unitOut = BI_POWS[18]; // 1:1 swap - const amountsOut = amounts; // 1:1 swap, so output amounts are the same as input + const unitOut = BI_POWS[this.config.toToken.decimals]; // 1:1 swap + const amountsOut = amounts.map( + amount => + (amount * BI_POWS[this.config.toToken.decimals]) / + BI_POWS[this.config.fromToken.decimals], + ); // 1:1 swap, so output amounts are the same as input return [ { From 79806b8229918c7b0a3cde50f21abbb00cfb6505 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 17 Dec 2024 12:35:38 +0300 Subject: [PATCH 24/27] 4.0.7-smart-m-usual-m-usd0.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7bed88c51..87c94cd75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.7-smart-m-usual-m-usd0.0", + "version": "4.0.7-smart-m-usual-m-usd0.1", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 08ce1a7366a772111e6033600a7f850e95c54ed8 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 17 Dec 2024 13:18:56 +0300 Subject: [PATCH 25/27] 4.0.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 87c94cd75..64171d680 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.7-smart-m-usual-m-usd0.1", + "version": "4.0.7", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From 5eb0bb0c54108fb8d11419482c396e127998d289 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Tue, 17 Dec 2024 16:34:43 +0300 Subject: [PATCH 26/27] 4.0.8-maverick-v2-fix.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 64171d680..33ccd7b3e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.7", + "version": "4.0.8-maverick-v2-fix.0", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", From b63debaac5cf80e0e765ce84e0533a9ec58be771 Mon Sep 17 00:00:00 2001 From: Alexander Burkut Date: Wed, 18 Dec 2024 13:09:32 +0300 Subject: [PATCH 27/27] 4.0.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 33ccd7b3e..5f0a33edf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "4.0.8-maverick-v2-fix.0", + "version": "4.0.8", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib",