diff --git a/package.json b/package.json index caf2fa423..ddb337219 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@paraswap/dex-lib", - "version": "2.30.2", + "version": "2.33.3", "main": "build/index.js", "types": "build/index.d.ts", "repository": "https://github.com/paraswap/paraswap-dex-lib", diff --git a/src/abi/nomiswap-v2/nomiswap-v2-pool.json b/src/abi/nomiswap-v2/nomiswap-v2-pool.json new file mode 100644 index 000000000..98740664d --- /dev/null +++ b/src/abi/nomiswap-v2/nomiswap-v2-pool.json @@ -0,0 +1,773 @@ +[ + { + "inputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Approval", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Burn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "name": "Mint", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1In", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "Swap", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint112", + "name": "reserve0", + "type": "uint112" + }, + { + "indexed": false, + "internalType": "uint112", + "name": "reserve1", + "type": "uint112" + } + ], + "name": "Sync", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "Transfer", + "type": "event" + }, + { + "constant": true, + "inputs": [], + "name": "DOMAIN_SEPARATOR", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "MINIMUM_LIQUIDITY", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "PERMIT_TYPEHASH", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "allowance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "approve", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "balanceOf", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "decimals", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "devFee", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "kLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "name", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "address", + "name": "spender", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "deadline", + "type": "uint256" + }, + { + "internalType": "uint8", + "name": "v", + "type": "uint8" + }, + { + "internalType": "bytes32", + "name": "r", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "s", + "type": "bytes32" + } + ], + "name": "permit", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price0CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "price1CumulativeLast", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "swapFee", + "outputs": [ + { + "internalType": "uint32", + "name": "", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "symbol", + "outputs": [ + { + "internalType": "string", + "name": "", + "type": "string" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token0", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "token1", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "totalSupply", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transfer", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "transferFrom", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "getReserves", + "outputs": [ + { + "internalType": "uint112", + "name": "_reserve0", + "type": "uint112" + }, + { + "internalType": "uint112", + "name": "_reserve1", + "type": "uint112" + }, + { + "internalType": "uint32", + "name": "_blockTimestampLast", + "type": "uint32" + } + ], + "payable": false, + "stateMutability": "view", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "_token0", + "type": "address" + }, + { + "internalType": "address", + "name": "_token1", + "type": "address" + } + ], + "name": "initialize", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint32", + "name": "_swapFee", + "type": "uint32" + } + ], + "name": "setSwapFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "_devFee", + "type": "uint256" + } + ], + "name": "setDevFee", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "mint", + "outputs": [ + { + "internalType": "uint256", + "name": "liquidity", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "burn", + "outputs": [ + { + "internalType": "uint256", + "name": "amount0", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1", + "type": "uint256" + } + ], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "uint256", + "name": "amount0Out", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount1Out", + "type": "uint256" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "swap", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + } + ], + "name": "skim", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + }, + { + "constant": false, + "inputs": [], + "name": "sync", + "outputs": [], + "payable": false, + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/dex/balancer-v2/balancer-v2-e2e.test.ts b/src/dex/balancer-v2/balancer-v2-e2e.test.ts index 1aadf7669..1f7de204b 100644 --- a/src/dex/balancer-v2/balancer-v2-e2e.test.ts +++ b/src/dex/balancer-v2/balancer-v2-e2e.test.ts @@ -96,6 +96,18 @@ describe('BalancerV2 E2E', () => { buyAmount: '10000000000', }, ], + [ + { + name: 'USDC', + sellAmount: '111000000', + buyAmount: '111000000', + }, + { + name: 'wstETH', + sellAmount: '100000000000000000', + buyAmount: '100000000000000000', + }, + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1054,7 +1066,7 @@ describe('BalancerV2 E2E', () => { sellAmount: '200000000', buyAmount: '2000000000', }, - ] + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1137,7 +1149,7 @@ describe('BalancerV2 E2E', () => { sellAmount: '200000000', buyAmount: '2000000000000000', }, - ] + ], ]; sideToContractMethods.forEach((contractMethods, side) => @@ -1181,7 +1193,6 @@ describe('BalancerV2 E2E', () => { }), ); }); - }); describe('BeetsFi OPTIMISM', () => { @@ -1436,5 +1447,4 @@ describe('BalancerV2 E2E', () => { }); }); }); - }); diff --git a/src/dex/balancer-v2/balancer-v2.ts b/src/dex/balancer-v2/balancer-v2.ts index ac1c3f443..d6c34b3bf 100644 --- a/src/dex/balancer-v2/balancer-v2.ts +++ b/src/dex/balancer-v2/balancer-v2.ts @@ -72,6 +72,91 @@ import { } from './constants'; import { NumberAsString, OptimalSwapExchange } from '@paraswap/core'; +// If you disable some pool, don't forget to clear the cache, otherwise changes won't be applied immediately +const enabledPoolTypes = [ + // BalancerPoolTypes.MetaStable, // BOOSTED POOLS Disabled since vulnerability https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md + BalancerPoolTypes.Stable, + BalancerPoolTypes.Weighted, + BalancerPoolTypes.LiquidityBootstrapping, + BalancerPoolTypes.Investment, + BalancerPoolTypes.StablePhantom, + BalancerPoolTypes.AaveLinear, + BalancerPoolTypes.ERC4626Linear, + BalancerPoolTypes.Linear, + BalancerPoolTypes.ComposableStable, + BalancerPoolTypes.BeefyLinear, + BalancerPoolTypes.GearboxLinear, + BalancerPoolTypes.MidasLinear, + BalancerPoolTypes.ReaperLinear, + BalancerPoolTypes.SiloLinear, + BalancerPoolTypes.TetuLinear, + BalancerPoolTypes.YearnLinear, +]; + +const disabledPoolIds = [ + // broken ? + '0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6', + + /* DISABLED POOLS SINCE VULNERABILITY https://github.com/BalancerMaxis/multisig-ops/blob/main/BIPs/00notGov/2023-08-mitigation.md*/ + /* START:2023-08-mitigation */ + //mainnet + '0xbf2ef8bdc2fc0f3203b3a01778e3ec5009aeef3300000000000000000000058d', + '0x99c88ad7dc566616548adde8ed3effa730eb6c3400000000000000000000049a', + '0x60683b05e9a39e3509d8fdb9c959f23170f8a0fa000000000000000000000489', + '0xa13a9247ea42d743238089903570127dda72fe4400000000000000000000035d', + '0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe', + '0x25accb7943fd73dda5e23ba6329085a3c24bfb6a000200000000000000000387', + '0x50cf90b954958480b8df7958a9e965752f62712400000000000000000000046f', + '0x133d241f225750d2c92948e464a5a80111920331000000000000000000000476', + '0x8a6b25e33b12d1bb6929a8793961076bd1f9d3eb0002000000000000000003e8', + '0x959216bb492b2efa72b15b7aacea5b5c984c3cca000200000000000000000472', + '0x9b692f571b256140a39a34676bffa30634c586e100000000000000000000059d', + + // polygon + '0xb3d658d5b95bf04e2932370dd1ff976fe18dd66a000000000000000000000ace', + '0x48e6b98ef6329f8f0a30ebb8c7c960330d64808500000000000000000000075b', + '0xb54b2125b711cd183edd3dd09433439d5396165200000000000000000000075e', + + // arbitrum + '0xa8af146d79ac0bb981e4e0d8b788ec5711b1d5d000000000000000000000047b', + '0x077794c30afeccdf5ad2abc0588e8cee7197b71a000000000000000000000352', + '0x519cce718fcd11ac09194cff4517f12d263be067000000000000000000000382', + + // optimism + '0x23ca0306b21ea71552b148cf3c4db4fc85ae19290000000000000000000000ac', + '0x62cf35db540152e94936de63efc90d880d4e241b0000000000000000000000ef', + '0x098f32d98d0d64dba199fc1923d3bf4192e787190001000000000000000000d2', + '0x43da214fab3315aa6c02e0b8f2bfb7ef2e3c60a50000000000000000000000ae', + '0xb1c9ac57594e9b1ec0f3787d9f6744ef4cb0a02400000000000000000000006e', + '0xde45f101250f2ca1c0f8adfc172576d10c12072d00000000000000000000003f', + '0x05e7732bf9ae5592e6aa05afe8cd80f7ab0a7bea00020000000000000000005a', + '0x981fb05b738e981ac532a99e77170ecb4bc27aef00010000000000000000004b', + '0x6222ae1d2a9f6894da50aa25cb7b303497f9bebd000000000000000000000046', + '0x3c74c4ed512050eb843d89fb9dcd5ebb4668eb6d0002000000000000000000cc', + + // fantom + '0xc0064b291bd3d4ba0e44ccfc81bf8e7f7a579cd200000000000000000000042c', + '0x6e6dc948ce85c62125ff7a1e543d761a88f0a4cb000000000000000000000743', + '0x78ab08bf98f90f29a09c9b1d85b3b549369b03a3000100000000000000000354', + '0x302b8b64795b064cadc32f74993a6372498608070001000000000000000003e0', + '0x5ddb92a5340fd0ead3987d3661afcd6104c3b757000000000000000000000187', + '0xdfc65c1f15ad3507754ef0fd4ba67060c108db7e000000000000000000000406', + '0x6da14f5acd58dd5c8e486cfa1dc1c550f5c61c1c0000000000000000000003cf', + '0x592fa9f9d58065096f2b7838709c116957d7b5cf00020000000000000000043c', + '0xf47f4d59c863c02cbfa3eefe6771b9c9fbe7b97800000000000000000000072b', + '0xff2753aaba51c9f84689b9bd0a21b3cf380a1cff00000000000000000000072e', + '0x10441785a928040b456a179691141c48356eb3a50001000000000000000002fa', + '0x64b301e21d640f9bef90458b0987d81fb4cf1b9e00020000000000000000022e', + '0xba0e9aea8a7fa1daab4edf244191f2387a4e472b000100000000000000000737', + '0x1e2576344d49779bdbb71b1b76193d27e6f996b700020000000000000000032d', + '0xa10285f445bcb521f1d623300dc4998b02f11c8f00000000000000000000043b', + + // zkevm + '0x6f34a44fce1506352a171232163e7716dd073ade000200000000000000000015', + '0xe274c9deb6ed34cfe4130f8d0a8a948dea5bb28600000000000000000000000d', + /* END:2023-08-mitigation */ +]; + const fetchAllPools = `query ($count: Int) { pools: pools( first: $count @@ -81,7 +166,7 @@ const fetchAllPools = `query ($count: Int) { totalLiquidity_gt: ${MIN_USD_LIQUIDITY_TO_FETCH.toString()}, totalShares_not_in: ["0", "0.000000000001"], id_not_in: [ - "0xbd482ffb3e6e50dc1c437557c3bea2b68f3683ee0000000000000000000003c6" + ${disabledPoolIds.map(p => `"${p}"`).join(', ')} ], address_not_in: [ "0x0afbd58beca09545e4fb67772faf3858e610bcd0", @@ -93,9 +178,7 @@ const fetchAllPools = `query ($count: Int) { ], swapEnabled: true, poolType_in: [ - "MetaStable", "Stable", "Weighted", "LiquidityBootstrapping", "Investment", "StablePhantom", "AaveLinear", - "ERC4626Linear", "Linear", "ComposableStable", "BeefyLinear", "GearboxLinear", "MidasLinear", - "ReaperLinear", "SiloLinear", "TetuLinear", "YearnLinear" + ${enabledPoolTypes.map(p => `"${p}"`).join(', ')} ] } ) { diff --git a/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts b/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts index 5dc723f42..23f81a439 100644 --- a/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts +++ b/src/dex/curve-v1-factory/curve-v1-factory-integration.test.ts @@ -275,6 +275,63 @@ describe('CurveV1Factory', function () { } }); }); + + describe(`renBTC-wibBTC`, () => { + const srcTokenSymbol = 'renBTC'; + const destTokenSymbol = 'wibBTC'; + const amountsForSell = [ + 0n, + 1n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 2n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 3n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 4n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 5n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 6n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 7n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 8n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 9n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + 10n * BI_POWS[tokens[srcTokenSymbol].decimals - 1], + ]; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + await testPricingOnNetwork( + curveV1Factory, + network, + dexKey, + blockNumber, + srcTokenSymbol, + destTokenSymbol, + SwapSide.SELL, + amountsForSell, + ); + }); + + it('getTopPoolsForToken', async function () { + // We have to check without calling initializePricing, because + // pool-tracker is not calling that function + const newCurveV1Factory = new CurveV1Factory( + network, + dexKey, + dexHelper, + ); + if (newCurveV1Factory.updatePoolState) { + await newCurveV1Factory.updatePoolState(); + } + const poolLiquidity = await newCurveV1Factory.getTopPoolsForToken( + tokens[srcTokenSymbol].address, + 10, + ); + console.log(`${srcTokenSymbol} Top Pools:`, poolLiquidity); + + if (!newCurveV1Factory.hasConstantPriceLargeAmounts) { + checkPoolsLiquidity( + poolLiquidity, + Tokens[network][srcTokenSymbol].address, + dexKey, + ); + } + }); + }); }); describe('Polygon', () => { const network = Network.POLYGON; diff --git a/src/dex/curve-v1-factory/curve-v1-factory.ts b/src/dex/curve-v1-factory/curve-v1-factory.ts index 0e88729f8..8606bdce6 100644 --- a/src/dex/curve-v1-factory/curve-v1-factory.ts +++ b/src/dex/curve-v1-factory/curve-v1-factory.ts @@ -181,6 +181,8 @@ export class CurveV1Factory // This is only to start timer, each pool is initialized with updated state this.poolManager.initializePollingPools(); await this.fetchFactoryPools(blockNumber); + await this.poolManager.fetchLiquiditiesFromApi(); + await this.poolManager.updatePollingPoolsInBatch(); this.logger.info(`${this.dexKey}: successfully initialized`); } @@ -340,7 +342,7 @@ export class CurveV1Factory // Variable initializeInitialState is only for poolTracker. We don't want to keep state updated with scheduler // We just want to initialize factory pools and send request to CurveAPI // Other values are not used - initializeInitialState: boolean = true, + initializeInitialState: boolean = false, ) { if (this.areFactoryPoolsFetched) { return; @@ -770,57 +772,69 @@ export class CurveV1Factory ) : amountsWithUnit; - const results = pools.map( - (pool): PoolPrices | null => { - const state = pool.getState(); + const results = await Promise.all( + pools.map( + async (pool): Promise | null> => { + let state = pool.getState(); + if (!state) { + await this.poolManager.updateManuallyPollingPools( + pool.baseStatePoolPolling + ? [pool.baseStatePoolPolling, pool] + : [pool], + ); + state = pool.getState(); + if (!state) { + return null; + } + } - if (!state) { - return null; - } + if (state.balances.every(b => b === 0n)) { + this.logger.trace( + `${this.dexKey} on ${this.dexHelper.config.data.network}: State balances equal to 0 in pool ${pool.address}`, + ); + return null; + } - if (state.balances.every(b => b === 0n)) { - this.logger.trace( - `${this.dexKey} on ${this.dexHelper.config.data.network}: State balances equal to 0 in pool ${pool.address}`, + const poolData = pool.getPoolData( + srcTokenAddress, + destTokenAddress, ); - return null; - } - const poolData = pool.getPoolData(srcTokenAddress, destTokenAddress); + if (poolData === null) { + this.logger.error( + `${pool.fullName}: one or both tokens can not be exchanged in pool ${pool.address}: ${srcTokenAddress} -> ${destTokenAddress}`, + ); + return null; + } - if (poolData === null) { - this.logger.error( - `${pool.fullName}: one or both tokens can not be exchanged in pool ${pool.address}: ${srcTokenAddress} -> ${destTokenAddress}`, - ); - return null; - } + let outputs: bigint[] = this.poolManager + .getPriceHandler(pool.implementationAddress) + .getOutputs( + state, + amountsWithUnitAndFee, + poolData.i, + poolData.j, + poolData.underlyingSwap, + ); - let outputs: bigint[] = this.poolManager - .getPriceHandler(pool.implementationAddress) - .getOutputs( - state, - amountsWithUnitAndFee, - poolData.i, - poolData.j, - poolData.underlyingSwap, + outputs = applyTransferFee( + outputs, + side, + transferFees.destDexFee, + this.DEST_TOKEN_DEX_TRANSFERS, ); - outputs = applyTransferFee( - outputs, - side, - transferFees.destDexFee, - this.DEST_TOKEN_DEX_TRANSFERS, - ); - - return { - prices: [0n, ...outputs.slice(1)], - unit: outputs[0], - data: poolData, - exchange: this.dexKey, - poolIdentifier: pool.poolIdentifier, - gasCost: POOL_EXCHANGE_GAS_COST, - poolAddresses: [pool.address], - }; - }, + return { + prices: [0n, ...outputs.slice(1)], + unit: outputs[0], + data: poolData, + exchange: this.dexKey, + poolIdentifier: pool.poolIdentifier, + gasCost: POOL_EXCHANGE_GAS_COST, + poolAddresses: [pool.address], + }; + }, + ), ); return results.filter( diff --git a/src/dex/curve-v1-factory/curve-v1-pool-manager.ts b/src/dex/curve-v1-factory/curve-v1-pool-manager.ts index 42e2c8c20..45141def5 100644 --- a/src/dex/curve-v1-factory/curve-v1-pool-manager.ts +++ b/src/dex/curve-v1-factory/curve-v1-pool-manager.ts @@ -94,7 +94,7 @@ export class CurveV1FactoryPoolManager { filteredPoolsByLiquidity.sort((a, b) => +a.isMetaPool - +b.isMetaPool), ); - this.statePollingManager.updatePoolsInBatch( + return this.statePollingManager.updatePoolsInBatch( this.logger, this.dexHelper, pools, @@ -105,6 +105,16 @@ export class CurveV1FactoryPoolManager { ); } + async updateManuallyPollingPools(pools: PoolPollingBase[]) { + return this.statePollingManager.updatePoolsInBatch( + this.logger, + this.dexHelper, + pools, + undefined, + undefined, + ); + } + async initializeIndividualPollingPoolState( identifier: string, isSrcFeeOnTransferTokenToBeExchanged: boolean, diff --git a/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts b/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts index b742d20da..f16d6b114 100644 --- a/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts +++ b/src/dex/curve-v1-factory/price-handlers/functions/_rates.ts @@ -36,7 +36,7 @@ const customPlain3CoinSbtc: _rates = ( let rate = LENDING_PRECISION; // Used with no lending if (use_lending[i]) { const currentRate = state.exchangeRateCurrent[i]; - if (currentRate === undefined) { + if (!currentRate) { throw new Error( `${self.IMPLEMENTATION_NAME}: exchangeRateCurrent contains undefined value that supposed to be used: ${state.exchangeRateCurrent}`, ); diff --git a/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts b/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts index 7a83192de..f2c3ea087 100644 --- a/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts +++ b/src/dex/curve-v1-factory/state-polling-pools/custom-pool-polling.ts @@ -13,6 +13,7 @@ import { _require } from '../../../utils'; import { Address } from '@paraswap/core'; import { AbiItem } from 'web3-utils'; import { NULL_ADDRESS } from '../../../constants'; +import { assert } from 'ts-essentials'; type FunctionToCall = | 'A' @@ -227,16 +228,17 @@ export class CustomBasePoolForFactory extends PoolPollingBase { let exchangeRateCurrent: (bigint | undefined)[] | undefined; - let lastEndIndex = lastIndex + 1; + let lastEndIndex = lastIndex + this.poolConstants.COINS.length; if (this.useLending) { exchangeRateCurrent = new Array(this.useLending.length).fill(undefined); + let trueUseLendingCount = this.useLending.filter(el => el).length; const exchangeRateResults = multiOutputs.slice( lastEndIndex, // Filter false elements before checking length - lastEndIndex + this.useLending.filter(el => el).length, + lastEndIndex + trueUseLendingCount, ) as bigint[]; - lastEndIndex += this.useLending.length; + lastEndIndex += trueUseLendingCount; // We had array with booleans and I filtered of `false` and sent request. // So, now I must map that results to original indices. That is the reason of this complication const indicesToFill = this.useLending.reduce((acc, curr, i) => { @@ -255,9 +257,15 @@ export class CustomBasePoolForFactory extends PoolPollingBase { }, 'indicesToFill.length === exchangeRateResults.length', ); - indicesToFill.forEach((indexToFill, currentIndex) => { - exchangeRateResults[indexToFill] = exchangeRateResults[currentIndex]; + if (exchangeRateCurrent === undefined) { + throw new Error( + `${this.poolIdentifier}: exchangeRateCurrent is undefined`, + ); + } + const resultRate = exchangeRateResults[currentIndex]; + assert(resultRate, "resultRate can't be undefined"); + exchangeRateCurrent[indexToFill] = resultRate; }); } diff --git a/src/dex/generic-rfq/example-api.test.ts b/src/dex/generic-rfq/example-api.test.ts index 9fff282b0..81809dba4 100644 --- a/src/dex/generic-rfq/example-api.test.ts +++ b/src/dex/generic-rfq/example-api.test.ts @@ -157,7 +157,7 @@ export const startTestServer = (account: ethers.Wallet) => { new BigNumber(_prices.asks![0][0]), ); } else { - const reversedPrices = _prices.bids!.map(price => + const reversedPrices = _prices.asks!.map(price => reversePrice([new BigNumber(price[0]), new BigNumber(price[1])]), ); value = new BigNumber(payload.makerAmount).times( diff --git a/src/dex/generic-rfq/generic-rfq.ts b/src/dex/generic-rfq/generic-rfq.ts index 389a9a4a3..2c682d8b1 100644 --- a/src/dex/generic-rfq/generic-rfq.ts +++ b/src/dex/generic-rfq/generic-rfq.ts @@ -140,6 +140,10 @@ export class GenericRFQ extends ParaSwapLimitOrders { _destToken.address, ); + if (!limitPools?.includes(expectedIdentifier)) { + return null; + } + const rates = await this.rateFetcher.getOrderPrice( _srcToken, _destToken, @@ -203,6 +207,7 @@ export class GenericRFQ extends ParaSwapLimitOrders { : overOrder(optimalSwapExchange.destAmount, 1), side, options.txOrigin, + options.partner, ); const expiryAsBigInt = BigInt(order.order.expiry); diff --git a/src/dex/generic-rfq/rate-fetcher.ts b/src/dex/generic-rfq/rate-fetcher.ts index 64442d157..f5760c532 100644 --- a/src/dex/generic-rfq/rate-fetcher.ts +++ b/src/dex/generic-rfq/rate-fetcher.ts @@ -226,7 +226,7 @@ export class RateFetcher { private handleRatesResponse(resp: RatesResponse) { const pairs = this.pairs; - if(isEmpty(pairs)) return; + if (isEmpty(pairs)) return; Object.keys(resp.prices).forEach(pairName => { const pair = pairs[pairName]; @@ -239,7 +239,7 @@ export class RateFetcher { return; } - if(isEmpty(this.tokens)) return; + if (isEmpty(this.tokens)) return; const baseToken = this.tokens[pair.base]; const quoteToken = this.tokens[pair.quote]; @@ -376,6 +376,7 @@ export class RateFetcher { srcAmount: string, side: SwapSide, userAddress: Address, + partner?: string, ): Promise { const srcToken = this.dexHelper.config.wrapETH(_srcToken); const destToken = this.dexHelper.config.wrapETH(_destToken); @@ -390,6 +391,7 @@ export class RateFetcher { makerAmount: side === SwapSide.BUY ? srcAmount : undefined, takerAmount: side === SwapSide.SELL ? srcAmount : undefined, userAddress, + partner, }; try { diff --git a/src/dex/generic-rfq/types.ts b/src/dex/generic-rfq/types.ts index 1ded4dc7a..dc2d3fcf2 100644 --- a/src/dex/generic-rfq/types.ts +++ b/src/dex/generic-rfq/types.ts @@ -84,6 +84,7 @@ export type RFQPayload = { makerAmount?: string; takerAmount?: string; userAddress: Address; + partner?: string; }; export type AugustusOrderWithStringAndSignature = AugustusOrderWithString & { diff --git a/src/dex/index.ts b/src/dex/index.ts index 54f1a7f82..eee1d80f0 100644 --- a/src/dex/index.ts +++ b/src/dex/index.ts @@ -46,10 +46,12 @@ import { Ramses } from './solidly/forks-override/ramses'; import { Thena } from './solidly/forks-override/thena'; import { Chronos } from './solidly/forks-override/chronos'; import { Velodrome } from './solidly/forks-override/velodrome'; +import { VelodromeV2 } from './solidly/forks-override/velodromeV2'; import { SpiritSwapV2 } from './solidly/forks-override/spiritSwapV2'; import { Synthetix } from './synthetix/synthetix'; import { Cone } from './solidly/forks-override/cone'; import { SoliSnek } from './solidly/forks-override/solisnek'; +import { Equalizer } from './solidly/forks-override/equalizer'; import { BalancerV1 } from './balancer-v1/balancer-v1'; import { balancerV1Merge } from './balancer-v1/optimizer'; import { CurveV1 } from './curve-v1/curve-v1'; @@ -71,6 +73,7 @@ import { SpiritSwapV3 } from './quickswap/spiritswap-v3'; import { TraderJoeV21 } from './trader-joe-v2.1'; import { PancakeswapV3 } from './pancakeswap-v3/pancakeswap-v3'; import { Algebra } from './algebra/algebra'; +import { NomiswapV2 } from './uniswap-v2/nomiswap-v2'; import { Carbon } from './carbon/carbon'; const LegacyDexes = [ @@ -130,8 +133,10 @@ const Dexes = [ Thena, Chronos, Velodrome, + VelodromeV2, Cone, SoliSnek, + Equalizer, Synthetix, CurveV1Factory, SwaapV1, @@ -140,6 +145,7 @@ const Dexes = [ MaverickV1, Camelot, SwaapV2, + NomiswapV2, Carbon, ]; diff --git a/src/dex/solidly/config.ts b/src/dex/solidly/config.ts index fb940e66e..b18dba6d7 100644 --- a/src/dex/solidly/config.ts +++ b/src/dex/solidly/config.ts @@ -72,6 +72,17 @@ export const SolidlyConfig: DexConfigMap = { feeCode: 2, }, }, + VelodromeV2: { + [Network.OPTIMISM]: { + // There is no subgraph for VelodromeV2 + factoryAddress: '0xF1046053aa5682b4F9a81b5481394DA16BE5FF5a', + router: '0xa2f581b012E0f2dcCDe86fCbfb529f4aC5dD4983', + initCode: + '0x1a8f01f7eab324003d9388f229ea17991eee9c9d14586f429799f3656790eba0', + poolGasCost: 180 * 1000, + feeCode: 0, + }, + }, Cone: { [Network.BSC]: { subgraphURL: 'https://api.thegraph.com/subgraphs/name/cone-exchange/cone', @@ -137,6 +148,16 @@ export const SolidlyConfig: DexConfigMap = { feeCode: 0, }, }, + Equalizer: { + [Network.FANTOM]: { + factoryAddress: '0xc6366EFD0AF1d09171fe0EBF32c7943BB310832a', + router: '0x93d2611EB8b85bE4FDEa9D94Ce9913D90072eC0f', + initCode: + '0x02ada2a0163cd4f7e0f0c9805f5230716a95b174140e4c84c14883de216cc6a3', + feeCode: 0, + poolGasCost: 180 * 1000, + }, + }, }; export const Adapters: Record = { @@ -144,7 +165,7 @@ export const Adapters: Record = { [SwapSide.SELL]: [{ name: 'PolygonAdapter02', index: 3 }], // dystopia }, [Network.FANTOM]: { - [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly + spiritSwapV2 + [SwapSide.SELL]: [{ name: 'FantomAdapter01', index: 10 }], // solidly + spiritSwapV2 + Equalizer }, [Network.OPTIMISM]: { [SwapSide.SELL]: [{ name: 'OptimismAdapter01', index: 8 }], // velodrome diff --git a/src/dex/solidly/forks-override/equalizer.ts b/src/dex/solidly/forks-override/equalizer.ts new file mode 100644 index 000000000..c72a61068 --- /dev/null +++ b/src/dex/solidly/forks-override/equalizer.ts @@ -0,0 +1,58 @@ +import { Solidly } from '../solidly'; +import { SolidlyPair } from '../types'; +import { Network } from '../../../constants'; +import { IDexHelper } from '../../../dex-helper'; +import { Interface } from '@ethersproject/abi'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; + +const EqualizerFactoryABI = [ + { + inputs: [{ internalType: 'address', name: '_pair', type: 'address' }], + name: 'getRealFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const equalizerFactoryIface = new Interface(EqualizerFactoryABI); + +export class Equalizer extends Solidly { + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['Equalizer'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, // dynamic fees + ); + } + + protected getFeesMultiCallData(pair: SolidlyPair) { + const callEntry = { + target: this.factoryAddress, + callData: equalizerFactoryIface.encodeFunctionData('getRealFee', [ + pair.exchange, + ]), + }; + const callDecoder = (values: any[]) => + parseInt( + equalizerFactoryIface + .decodeFunctionResult('getRealFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/solidly/forks-override/velodromeV2.ts b/src/dex/solidly/forks-override/velodromeV2.ts new file mode 100644 index 000000000..c15663cc2 --- /dev/null +++ b/src/dex/solidly/forks-override/velodromeV2.ts @@ -0,0 +1,61 @@ +import { Solidly } from '../solidly'; +import { Network } from '../../../constants'; +import { getDexKeysWithNetwork } from '../../../utils'; +import { SolidlyConfig } from '../config'; +import _ from 'lodash'; +import { Address, PoolLiquidity } from '../../../types'; +import { SolidlyPair } from '../types'; +import { Interface } from '@ethersproject/abi'; +import { IDexHelper } from '../../../dex-helper'; + +const VelodromeV2FactoryABI = [ + { + inputs: [ + { internalType: 'address', name: 'pool', type: 'address' }, + { internalType: 'bool', name: '_stable', type: 'bool' }, + ], + name: 'getFee', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, +]; + +const velodromeV2FactoryIface = new Interface(VelodromeV2FactoryABI); + +export class VelodromeV2 extends Solidly { + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(_.pick(SolidlyConfig, ['VelodromeV2'])); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, // dynamic fees + ); + } + + protected getFeesMultiCallData(pair: SolidlyPair) { + const callEntry = { + target: this.factoryAddress, + callData: velodromeV2FactoryIface.encodeFunctionData('getFee', [pair.exchange, pair.stable]), + }; + const callDecoder = (values: any[]) => + parseInt( + velodromeV2FactoryIface + .decodeFunctionResult('getFee', values)[0] + .toString(), + ); + + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/solidly/solidly-e2e.test.ts b/src/dex/solidly/solidly-e2e.test.ts index 86c59fbaa..5395ad36b 100644 --- a/src/dex/solidly/solidly-e2e.test.ts +++ b/src/dex/solidly/solidly-e2e.test.ts @@ -2,11 +2,95 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { Tokens, Holders } from '../../../tests/constants-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, + nativeTokenAmount: string, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + // TODO: Add any direct swap contractMethod name if it exists + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }), + ); + }); +} + describe('Solidly E2E', () => { describe('Fantom', () => { const network = Network.FANTOM; @@ -309,6 +393,28 @@ describe('Solidly E2E', () => { }); }); }); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const network = Network.FANTOM; + + const tokenASymbol: string = 'FUSDT'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '111110'; + const tokenBAmount: string = '100000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('Mainnet', () => { @@ -689,6 +795,29 @@ describe('Solidly E2E', () => { }); }); }); + + describe('VelodromeV2', () => { + const dexKey = 'VelodromeV2'; + + const network = Network.OPTIMISM; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '1100000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); }); describe('BSC', () => { diff --git a/src/dex/solidly/solidly-integration.test.ts b/src/dex/solidly/solidly-integration.test.ts index c4d3163e1..73f50a127 100644 --- a/src/dex/solidly/solidly-integration.test.ts +++ b/src/dex/solidly/solidly-integration.test.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-console */ import dotenv from 'dotenv'; dotenv.config(); @@ -14,6 +15,8 @@ import { Cone } from './forks-override/cone'; import { Chronos } from './forks-override/chronos'; import { Ramses } from './forks-override/ramses'; import * as util from 'util'; +import { VelodromeV2 } from './forks-override/velodromeV2'; +import { Equalizer } from './forks-override/equalizer'; const amounts18 = [0n, BI_POWS[18], 2000000000000000000n]; const amounts6 = [0n, BI_POWS[6], 2000000n]; @@ -45,7 +48,7 @@ function decodeReaderResult( const constructCheckOnChainPricing = (dexHelper: DummyDexHelper) => async ( - soldily: Solidly, + solidly: Solidly, funcName: string, blockNumber: number, prices: bigint[], @@ -87,7 +90,7 @@ describe('Solidly integration tests', () => { describe('Solidly', function () { const dexKey = 'Solidly'; - const soldily = new Solidly(network, dexKey, dexHelper); + const solidly = new Solidly(network, dexKey, dexHelper); describe('UniswapV2 like pool', function () { const TokenASymbol = 'WFTM'; @@ -99,7 +102,7 @@ describe('Solidly integration tests', () => { it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await soldily.getPoolIdentifiers( + const pools = await solidly.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -112,7 +115,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await soldily.getPricesVolume( + const poolPrices = await solidly.getPricesVolume( tokenA, tokenB, amounts, @@ -130,13 +133,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( - soldily, + solidly, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -144,7 +147,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await soldily.getTopPoolsForToken( + const poolLiquidity = await solidly.getTopPoolsForToken( tokenA.address, 10, ); @@ -164,7 +167,7 @@ describe('Solidly integration tests', () => { it('getPoolIdentifiers and getPricesVolume', async function () { const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); - const pools = await soldily.getPoolIdentifiers( + const pools = await solidly.getPoolIdentifiers( tokenA, tokenB, SwapSide.SELL, @@ -177,7 +180,7 @@ describe('Solidly integration tests', () => { expect(pools.length).toBeGreaterThan(0); - const poolPrices = await soldily.getPricesVolume( + const poolPrices = await solidly.getPricesVolume( tokenA, tokenB, amounts, @@ -194,13 +197,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( - soldily, + solidly, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -208,7 +211,7 @@ describe('Solidly integration tests', () => { }); it('getTopPoolsForToken', async function () { - const poolLiquidity = await soldily.getTopPoolsForToken( + const poolLiquidity = await solidly.getTopPoolsForToken( tokenA.address, 10, ); @@ -264,13 +267,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( spiritSwapV2, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -328,13 +331,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( spiritSwapV2, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -352,6 +355,176 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Equalizer', () => { + const dexKey = 'Equalizer'; + const equalizer = new Equalizer(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'FUSDT'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'FUSDT'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; // amounts6; + + it('getPoolIdentifiers and getPricesVolume', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('FTM -> EQUAL', () => { + const TokenASymbol = 'WFTM'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'EQUAL'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = [0n, 10000000n]; + + console.log('AMOUNTS: ', amounts); + it('getPoolIdentifiers and getPricesVolume', async function () { + // const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const blocknumber = 67666611; + const pools = await equalizer.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await equalizer.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + equalizer, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); }); describe('Polygon', () => { @@ -404,13 +577,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( dystopia, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -468,13 +641,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( dystopia, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -544,13 +717,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( cone, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -608,13 +781,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( cone, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -684,13 +857,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( chronos, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -748,13 +921,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( chronos, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -820,13 +993,13 @@ describe('Solidly integration tests', () => { // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( ramses, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -884,13 +1057,13 @@ describe('Solidly integration tests', () => { checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); // Check if onchain pricing equals to calculated ones - for (const i in poolPrices || []) { + for (const poolPrice of poolPrices || []) { await checkOnChainPricing( ramses, 'getAmountOut', blocknumber, - poolPrices![i].prices, - poolPrices![i].poolAddresses![0], + poolPrice.prices, + poolPrice.poolAddresses![0], tokenA.address, amounts, ); @@ -909,4 +1082,124 @@ describe('Solidly integration tests', () => { }); }); }); + + describe('Optimism', () => { + const network = Network.OPTIMISM; + const dexHelper = new DummyDexHelper(network); + const checkOnChainPricing = constructCheckOnChainPricing(dexHelper); + + describe('VelodromeV2', () => { + const dexKey = 'VelodromeV2'; + const velodromeV2 = new VelodromeV2(network, dexKey, dexHelper); + + describe('UniswapV2 like pool', function () { + const TokenASymbol = 'USDC'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'WETH'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts18; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velodromeV2.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velodromeV2.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velodromeV2, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + + describe('Curve like stable pool', function () { + const TokenASymbol = 'USDT'; + const tokenA = Tokens[network][TokenASymbol]; + const TokenBSymbol = 'USDC'; + const tokenB = Tokens[network][TokenBSymbol]; + + const amounts = amounts6; + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber(); + const pools = await velodromeV2.getPoolIdentifiers( + tokenA, + tokenB, + SwapSide.SELL, + blocknumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await velodromeV2.getPricesVolume( + tokenA, + tokenB, + amounts, + SwapSide.SELL, + blocknumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + // Check if onchain pricing equals to calculated ones + for (const poolPrice of poolPrices || []) { + await checkOnChainPricing( + velodromeV2, + 'getAmountOut', + blocknumber, + poolPrice.prices, + poolPrice.poolAddresses![0], + tokenA.address, + amounts, + ); + } + }); + }); + }); + }); }); diff --git a/src/dex/solidly/solidly.ts b/src/dex/solidly/solidly.ts index 7614ce0f9..899acd50f 100644 --- a/src/dex/solidly/solidly.ts +++ b/src/dex/solidly/solidly.ts @@ -66,6 +66,7 @@ export class Solidly extends UniswapV2 { getDexKeysWithNetwork( _.omit(SolidlyConfig, [ 'Velodrome', + 'VelodromeV2', 'SpiritSwapV2', 'Cone', 'SolidlyV2', @@ -73,6 +74,7 @@ export class Solidly extends UniswapV2 { 'SoliSnek', 'Chronos', 'Ramses', + 'Equalizer', ]), ); diff --git a/src/dex/uniswap-v2/nomiswap-v2.ts b/src/dex/uniswap-v2/nomiswap-v2.ts new file mode 100644 index 000000000..116f08c96 --- /dev/null +++ b/src/dex/uniswap-v2/nomiswap-v2.ts @@ -0,0 +1,62 @@ +import { Network } from '../../constants'; +import { getDexKeysWithNetwork } from '../../utils'; +import { IDexHelper } from '../../dex-helper/idex-helper'; +import { UniswapV2, UniswapV2Pair } from '../uniswap-v2/uniswap-v2'; +import { Interface } from '@ethersproject/abi'; +import NomiswapPoolABI from '../../abi/nomiswap-v2/nomiswap-v2-pool.json'; +import { DexParams } from './types'; +import { DexConfigMap } from '../../types'; + +export const NomiswapV2Config: DexConfigMap = { + NomiswapV2: { + [Network.BSC]: { + factoryAddress: '0xd6715A8be3944ec72738F0BFDC739d48C3c29349', + initCode: + '0x83eb759f5ea0525124f03d4ac741bb4af0bb1c703d5f694bd42a8bd72e495a01', + poolGasCost: 120 * 1000, + feeCode: 0, // this is ignored as Nomiswap uses dynamic fees, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/nominex/nomiswap-exchange-subgraph', + }, + }, +}; + +export class NomiswapV2 extends UniswapV2 { + nomiswapPool: Interface; + + public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = + getDexKeysWithNetwork(NomiswapV2Config); + + constructor( + protected network: Network, + dexKey: string, + protected dexHelper: IDexHelper, + ) { + super( + network, + dexKey, + dexHelper, + true, + NomiswapV2Config[dexKey][network].factoryAddress, + NomiswapV2Config[dexKey][network].subgraphURL, + NomiswapV2Config[dexKey][network].initCode, + NomiswapV2Config[dexKey][network].feeCode, + NomiswapV2Config[dexKey][network].poolGasCost, + ); + this.nomiswapPool = new Interface(NomiswapPoolABI); + } + + protected getFeesMultiCallData(pair: UniswapV2Pair) { + const callEntry = { + target: pair.exchange!, + callData: this.nomiswapPool.encodeFunctionData('swapFee', []), + }; + const callDecoder = (values: any[]) => + parseInt( + this.nomiswapPool.decodeFunctionResult('swapFee', values)[0].toString(), + ) * 10; + return { + callEntry, + callDecoder, + }; + } +} diff --git a/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts b/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts index ff7a66013..6d2872ce3 100644 --- a/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts +++ b/src/dex/uniswap-v2/uniswap-v2-e2e-bsc.test.ts @@ -2,11 +2,110 @@ import dotenv from 'dotenv'; dotenv.config(); import { testE2E } from '../../../tests/utils-e2e'; -import { Tokens, Holders } from '../../../tests/constants-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, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('UniswapV2 E2E BSC', () => { const network = Network.BSC; const tokens = Tokens[network]; @@ -1407,6 +1506,28 @@ describe('UniswapV2 E2E BSC', () => { }); }); + describe('NomiswapV2', () => { + const dexKey = 'NomiswapV2'; + const network = Network.BSC; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '1111100000'; + const tokenBAmount: string = '1000000000'; + const nativeTokenAmount = '11000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + describe(`Swapsicle`, () => { const dexKey = 'Swapsicle'; diff --git a/src/dex/uniswap-v3/config.ts b/src/dex/uniswap-v3/config.ts index 18a66c612..86d0e6163 100644 --- a/src/dex/uniswap-v3/config.ts +++ b/src/dex/uniswap-v3/config.ts @@ -107,6 +107,113 @@ export const UniswapV3Config: DexConfigMap = { 'https://api.thegraph.com/subgraphs/name/lynnshaoyu/uniswap-v3-avax', }, }, + SushiSwapV3: { + [Network.MAINNET]: { + factory: '0xbACEB8eC6b9355Dfc0269C18bac9d6E2Bdc29C4F', + quoter: '0x64e8802FE490fa7cc61d3463958199161Bb608A7', + router: '0x00F23572b16c5e9e58e7b965DEF51Ff8Ff546E34', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x9c764D2e92dA68E4CDfD784B902283A095ff8b63', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-ethereum', + }, + [Network.POLYGON]: { + factory: '0x917933899c6a5f8e37f31e19f92cdbff7e8ff0e2', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0x34D41cE301257a4615D4F5AD260FA91D03925243', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x6Dc993Fe1e945A640576B4Dca81281d8e998DF71', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-polygon', + }, + [Network.BSC]: { + factory: '0x126555dd55a39328F69400d6aE4F782Bd4C34ABb', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x593F39A4Ba26A9c8ed2128ac95D109E8e403C485', + uniswapMulticall: '0x963Df249eD09c358A4819E39d9Cd5736c3087184', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-bsc', + }, + [Network.AVALANCHE]: { + factory: '0x3e603C14aF37EBdaD31709C4f848Fc6aD5BEc715', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0x24c90C7d8fb463722e304A71255341610Fa7589b', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', + uniswapMulticall: '0x8C0F842791F03C095b6c633759224FcC9ACe68ea', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-avalanche', + }, + [Network.FANTOM]: { + factory: '0x7770978eED668a3ba661d51a773d3a992Fc9DDCB', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xDCf4EE5B700e2a5Fec458e06B763A4a3E3004494', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x30F6B9b6485ff0B67E881f5ac80D3F1c70A4B23d', + uniswapMulticall: '0xB1395e098c0a847CC719Bcf1Fc8114421a9F8232', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-fantom', + }, + [Network.ARBITRUM]: { + factory: '0x1af415a1eba07a4986a52b6f2e7de7003d82231e', + quoter: '0x0524E833cCD057e4d7A296e3aaAb9f7675964Ce1', + router: '0xbDa4176fD98b47018aF673805d069b9dbd49373D', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0xaBB58098A7B5172A9b0B38a1925A522dbf0b4FC3', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-arbitrum', + }, + [Network.OPTIMISM]: { + factory: '0x9c6522117e2ed1fE5bdb72bb0eD5E3f2bdE7DBe0', + quoter: '0xb1E835Dc2785b52265711e17fCCb0fd018226a6e', + router: '0xa05d8C3F278fC7b20b39Ea7A3035E3aD8D808c78', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x4FF0dEC5f9a763Aa1E5C2a962aa6f4eDFeE4f9eA', + uniswapMulticall: '0x1F98415757620B543A52E61c46B32eB19261F984', + chunksCount: 10, + initRetryFrequency: 10, + initHash: `0xe34f199b19b2b4f47f68442619d555527d244f78a3297ea89325f843f87b8b54`, + subgraphURL: + 'https://api.thegraph.com/subgraphs/name/sushi-v3/v3-optimism', + }, + }, + ChronosV3: { + [Network.ARBITRUM]: { + factory: '0x4Db9D624F67E00dbF8ef7AE0e0e8eE54aF1dee49', + quoter: '0x6E7f0Ca45171a4440c0CDdF3A46A8dC5D4c2d4A0', + router: '0xE0aBdFD837D451640CF43cB1Ec4eE87976eFbb41', + supportedFees: SUPPORTED_FEES, + stateMulticall: '0x46b44eb4Cc3bEbB9f04C419f691aB85Ff885A4D6', + uniswapMulticall: '0xaBB58098A7B5172A9b0B38a1925A522dbf0b4FC3', + chunksCount: 10, + initRetryFrequency: 10, + initHash: + '0x09c178be473df44d1de6970978a4fdedce1ce52a23b2b979754547f6b43a19a5', + subgraphURL: + 'https://subgraph.chronos.exchange/subgraphs/name/chronos-v3', + }, + }, RamsesV2: { [Network.ARBITRUM]: { factory: '0xAA2cd7477c451E703f3B9Ba5663334914763edF8', diff --git a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts index ac7b91002..ed2e642c6 100644 --- a/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-e2e.test.ts @@ -11,7 +11,109 @@ 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, + nativeTokenAmount: string, + slippage?: number | undefined, +) { + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + const tokens = Tokens[network]; + const holders = Holders[network]; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + describe(`${network}`, () => { + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + it(`${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + undefined, + undefined, + undefined, + slippage, + ); + }); + }); + }); + }), + ); + }); +} + describe('UniswapV3 E2E', () => { + describe('UniswapV3', () => { const dexKey = 'UniswapV3'; @@ -505,4 +607,392 @@ describe('UniswapV3 E2E', () => { ); }); }); + + describe('ChronosV3', () => { + const dexKey = 'ChronosV3'; + describe('Arbitrum', () => { + const network = Network.ARBITRUM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const tokenASymbol: string = 'USDCe'; + const tokenBSymbol: string = 'USDT'; + const nativeTokenSymbol = NativeTokenSymbols[network]; + + const tokenAAmount: string = '2000000'; + const tokenBAmount: string = '2000000'; + const nativeTokenAmount = '100000000000000000'; + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + sideToContractMethods.forEach((contractMethods, side) => + contractMethods.forEach((contractMethod: ContractMethod) => { + describe(`${contractMethod}`, () => { + it(`${network} ${side} ${contractMethod} ${nativeTokenSymbol} -> ${tokenASymbol}`, async () => { + await testE2E( + tokens[nativeTokenSymbol], + tokens[tokenASymbol], + holders[nativeTokenSymbol], + side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${nativeTokenSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[nativeTokenSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${network} ${side} ${contractMethod} ${tokenASymbol} -> ${tokenBSymbol}`, async () => { + await testE2E( + tokens[tokenASymbol], + tokens[tokenBSymbol], + holders[tokenASymbol], + side === SwapSide.SELL ? tokenAAmount : tokenBAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }), + ); + }); + }); + + describe('SushiSwapV3 E2E', () => { + const dexKey = 'SushiSwapV3'; + + describe('MAINNET', () => { + const network = Network.MAINNET; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '1100000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('ARBITRUM', () => { + const network = Network.ARBITRUM; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDCe'; + + const tokenAAmount: string = '10000000'; + const tokenBAmount: string = '10000000'; + const nativeTokenAmount = '900000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('POLYGON', () => { + const network = Network.POLYGON; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '21111000'; + const tokenBAmount: string = '200000000'; + const nativeTokenAmount = '110000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('BSC', () => { + const network = Network.BSC; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'USDC', + sellAmount: '100000000000000000000', + buyAmount: '100000000000000000000', + }, + { + name: 'USDT', + sellAmount: '100000000000000000000', + buyAmount: '100000000000000000000', + }, + ], + [ + { + name: 'BNB', + sellAmount: '1000000000000000000', + buyAmount: '10000000000000000000', + }, + { + name: 'USDT', + sellAmount: '1000000000000000000000', + buyAmount: '20000000000000000', + }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }); + }), + ); + }); + + describe('AVALANCHE', () => { + const network = Network.AVALANCHE; + + const tokenASymbol: string = 'USDT'; + const tokenBSymbol: string = 'USDC'; + + const tokenAAmount: string = '111110'; + const tokenBAmount: string = '100000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + + describe('FANTOM', () => { + const network = Network.FANTOM; + const tokens = Tokens[network]; + const holders = Holders[network]; + const provider = new StaticJsonRpcProvider( + generateConfig(network).privateHttpProvider, + network, + ); + + const sideToContractMethods = new Map([ + [ + SwapSide.SELL, + [ + ContractMethod.simpleSwap, + ContractMethod.multiSwap, + ContractMethod.megaSwap, + ContractMethod.directUniV3Swap, + ], + ], + [ + SwapSide.BUY, + [ + ContractMethod.simpleBuy, + ContractMethod.buy, + ContractMethod.directUniV3Buy, + ], + ], + ]); + + const pairs: { name: string; sellAmount: string; buyAmount: string }[][] = [ + [ + { + name: 'FTM', + sellAmount: '100000000000000000', + buyAmount: '100000000', + }, + { + name: 'USDC', + sellAmount: '100000000', + buyAmount: '100000000000000000', + }, + ], + [ + { + name: 'WFTM', + sellAmount: '100000000000000', + buyAmount: '1000000000000000', + }, + { name: 'WETH', sellAmount: '1000000000000000', buyAmount: '100000000000000' }, + ], + ]; + + sideToContractMethods.forEach((contractMethods, side) => + describe(`${side}`, () => { + contractMethods.forEach((contractMethod: ContractMethod) => { + pairs.forEach(pair => { + describe(`${contractMethod}`, () => { + it(`${pair[0].name} -> ${pair[1].name}`, async () => { + await testE2E( + tokens[pair[0].name], + tokens[pair[1].name], + holders[pair[0].name], + side === SwapSide.SELL + ? pair[0].sellAmount + : pair[0].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + it(`${pair[1].name} -> ${pair[0].name}`, async () => { + await testE2E( + tokens[pair[1].name], + tokens[pair[0].name], + holders[pair[1].name], + side === SwapSide.SELL + ? pair[1].sellAmount + : pair[1].buyAmount, + side, + dexKey, + contractMethod, + network, + provider, + ); + }); + }); + }); + }); + }), + ); + }); + + describe('OPTIMISM', () => { + const network = Network.OPTIMISM; + + const tokenASymbol: string = 'USDC'; + const tokenBSymbol: string = 'USDT'; + + const tokenAAmount: string = '111110000'; + const tokenBAmount: string = '10000000'; + const nativeTokenAmount = '11000000000000000'; + + testForNetwork( + network, + dexKey, + tokenASymbol, + tokenBSymbol, + tokenAAmount, + tokenBAmount, + nativeTokenAmount, + ); + }); + }); }); diff --git a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts index b6f27af9c..1896e15c7 100644 --- a/src/dex/uniswap-v3/uniswap-v3-integration.test.ts +++ b/src/dex/uniswap-v3/uniswap-v3-integration.test.ts @@ -611,3 +611,360 @@ describe('RamsesV2', () => { expect(falseChecksCounter).toBeLessThan(poolPrices!.length); }); }); + +describe('ChronosV3', () => { + const dexKey = 'ChronosV3'; + let blockNumber: number; + let uniswapV3: UniswapV3; + let uniswapV3Mainnet: UniswapV3; + + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDCe'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDT'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + uniswapV3 = new UniswapV3(network, dexKey, dexHelper); + uniswapV3Mainnet = new UniswapV3( + Network.ARBITRUM, + dexKey, + dexHelper, + ); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await uniswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await uniswapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + uniswapV3, + 'quoteExactInputSingle', + blockNumber, + '0x6E7f0Ca45171a4440c0CDdF3A46A8dC5D4c2d4A0', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await uniswapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await uniswapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = uniswapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + uniswapV3, + 'quoteExactOutputSingle', + blockNumber, + '0x6E7f0Ca45171a4440c0CDdF3A46A8dC5D4c2d4A0', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it.skip('getTopPoolsForToken', async function () { + const poolLiquidity = await uniswapV3.getTopPoolsForToken( + TokenB.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); +}); + +describe('SushiSwapV3', () => { + const dexKey = 'SushiSwapV3'; + + describe('Mainnet', () => { + let blockNumber: number; + let sushiSwapV3: UniswapV3; + let sushiSwapV3Mainnet: UniswapV3; + + const network = Network.MAINNET; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDC'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDT'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + sushiSwapV3 = new UniswapV3(network, dexKey, dexHelper); + sushiSwapV3Mainnet = new UniswapV3(Network.MAINNET, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume SELL', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.SELL, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.SELL, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactInputSingle', + blockNumber, + '0x64e8802FE490fa7cc61d3463958199161Bb608A7', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, BI_POWS[6], 2000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactOutputSingle', + blockNumber, + '0x64e8802FE490fa7cc61d3463958199161Bb608A7', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + it('getTopPoolsForToken', async function () { + const poolLiquidity = await sushiSwapV3.getTopPoolsForToken( + TokenB.address, + 10, + ); + console.log(`${TokenASymbol} Top Pools:`, poolLiquidity); + + checkPoolsLiquidity(poolLiquidity, TokenB.address, dexKey); + }); + }); + + describe('Arbitrum', () => { + let blockNumber: number; + let sushiSwapV3: UniswapV3; + let sushiSwapV3Mainnet: UniswapV3; + + const network = Network.ARBITRUM; + const dexHelper = new DummyDexHelper(network); + const TokenASymbol = 'USDCe'; + const TokenA = Tokens[network][TokenASymbol]; + + const TokenBSymbol = 'USDT'; + const TokenB = Tokens[network][TokenBSymbol]; + + beforeEach(async () => { + // blockNumber = await dexHelper.web3Provider.eth.getBlockNumber(); + blockNumber = 125789437; + sushiSwapV3 = new UniswapV3(network, dexKey, dexHelper); + sushiSwapV3Mainnet = new UniswapV3(Network.ARBITRUM, dexKey, dexHelper); + }); + + it('getPoolIdentifiers and getPricesVolume BUY', async function () { + const amounts = [0n, 100000000n, 200000000n]; + + const pools = await sushiSwapV3.getPoolIdentifiers( + TokenA, + TokenB, + SwapSide.BUY, + blockNumber, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, + pools, + ); + + expect(pools.length).toBeGreaterThan(0); + + const poolPrices = await sushiSwapV3.getPricesVolume( + TokenA, + TokenB, + amounts, + SwapSide.BUY, + blockNumber, + pools, + ); + console.log( + `${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, + poolPrices, + ); + + expect(poolPrices).not.toBeNull(); + checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey); + + let falseChecksCounter = 0; + await Promise.all( + poolPrices!.map(async price => { + const fee = sushiSwapV3.eventPools[price.poolIdentifier!]!.feeCode; + const res = await checkOnChainPricing( + dexHelper, + sushiSwapV3, + 'quoteExactOutputSingle', + blockNumber, + '0x0524E833cCD057e4d7A296e3aaAb9f7675964Ce1', + price.prices, + TokenA.address, + TokenB.address, + fee, + amounts, + ); + if (res === false) falseChecksCounter++; + }), + ); + + expect(falseChecksCounter).toBeLessThan(poolPrices!.length); + }); + + }); +}); + diff --git a/src/dex/uniswap-v3/uniswap-v3.ts b/src/dex/uniswap-v3/uniswap-v3.ts index b31280a11..7ef42c117 100644 --- a/src/dex/uniswap-v3/uniswap-v3.ts +++ b/src/dex/uniswap-v3/uniswap-v3.ts @@ -91,7 +91,7 @@ export class UniswapV3 public static dexKeysWithNetwork: { key: string; networks: Network[] }[] = getDexKeysWithNetwork( - _.pick(UniswapV3Config, ['UniswapV3', 'QuickSwapV3.1', 'RamsesV2']), + _.pick(UniswapV3Config, ['UniswapV3', 'SushiSwapV3', 'QuickSwapV3.1', 'RamsesV2', 'ChronosV3']), ); logger: Logger; diff --git a/src/types.ts b/src/types.ts index 9d914b038..922247bd6 100644 --- a/src/types.ts +++ b/src/types.ts @@ -291,6 +291,7 @@ export type PreprocessTransactionOptions = { hmac?: string; mockRfqAndLO?: boolean; isDirectMethod?: boolean; + partner?: string; }; export type TransferFeeParams = { diff --git a/tests/constants-e2e.ts b/tests/constants-e2e.ts index 7e91808ca..7b88aa214 100644 --- a/tests/constants-e2e.ts +++ b/tests/constants-e2e.ts @@ -307,6 +307,10 @@ export const Tokens: { address: '0xf939E0A03FB07F59A73314E73794Be0E57ac1b4E', decimals: 18, }, + wibBTC: { + address: '0x8751d4196027d4e6da63716fa7786b5174f04c15', + decimals: 18, + }, }, [Network.ROPSTEN]: { DAI: { @@ -456,6 +460,10 @@ export const Tokens: { address: '0x049d68029688eabf473097a2fc38ef61633a3c7a', decimals: 6, }, + EQUAL: { + address: '0x3fd3a0c85b70754efc07ac9ac0cbbdce664865a6', + decimals: 18, + }, POPS: { address: '0x9dE4b40bDcE50Ec6a1A668bF85997BbBD324069a', decimals: 18, @@ -918,6 +926,7 @@ export const Holders: { PSP: '0xE5E5440a1CE69C5cf67BFFA74d185e57c31b43E5', crvUSD: '0xA920De414eA4Ab66b97dA1bFE9e6EcA7d4219635', GHO: '0x844Dc85EdD8492A56228D293cfEbb823EF3E10EC', + wibBTC: '0xFbdCA68601f835b27790D98bbb8eC7f05FDEaA9B', }, [Network.ROPSTEN]: { ETH: '0x43262A12d8610AA70C15DbaeAC321d51613c9071', @@ -958,7 +967,7 @@ export const Holders: { DAI: '0x370f4b2dcf75c94d8d4450b493661a9c6170d0b5', FTM: '0x431e81E5dfB5A24541b5Ff8762bDEF3f32F96354', WFTM: '0x3e923747ca2675e096d812c3b24846ac39aed645', - USDC: '0xe48793b1533b351ae184e1c3119d0955dde7b330', + USDC: '0xf53feaeb035361c046e5669745695e450ebb4028', FUSDT: '0x9ade1c17d25246c405604344f89E8F23F8c1c632', POPS: '0x4b78b52e7de4d8b7d367297cb8a87c1875a9d591', aFanUSDT: '0x8EBc96fF91A30059E447bFC7C0a7394f8A5793E6', @@ -974,6 +983,7 @@ export const Holders: { ETH: '0xf48883940b4056801de30f12b934dcea90133ee6', GUSDC: '0x894d774a293f8aa3d23d67815d4cadb5319c1094', GDAI: '0x0e2ed73f9c1409e2b36fe6c46e60d4557b7c2ac0', + EQUAL: '0x8b187ea19c93091a4d6b426b71871648182b5fac', }, [Network.BSC]: { DAI: '0xf68a4b64162906eff0ff6ae34e2bb1cd42fef62d',