diff --git a/contracts/GetWethValueInPoolBatchRequest.sol b/contracts/GetWethValueInPoolBatchRequest.sol index 7dbb997..ed5cdda 100644 --- a/contracts/GetWethValueInPoolBatchRequest.sol +++ b/contracts/GetWethValueInPoolBatchRequest.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; -import "./test/Console.sol"; - contract GetWethValueInPoolBatchRequest { uint256 internal constant Q96 = 0x1000000000000000000000000; address internal constant ADDRESS_ZERO = address(0); @@ -12,7 +10,7 @@ contract GetWethValueInPoolBatchRequest { constructor( address[] memory pools, address[] memory dexes, - bool[] memory dexIsUniV3, + uint8[] memory dexVariantBitmap, //0=v2, 1=v3, 2=izumi address weth, uint256 wethInPoolThreshold ) { @@ -28,8 +26,7 @@ contract GetWethValueInPoolBatchRequest { //Get the token0 and token1 from the pool if (!codeSizeIsZero(pools[i])) { - address token0 = IUniswapV2Pair(pools[i]).token0(); - address token1 = IUniswapV2Pair(pools[i]).token1(); + (address token0, address token1) = getTokens(pools[i]); if (!codeSizeIsZero(token0) && !codeSizeIsZero(token1)) { //Get the reserves from the pool @@ -46,7 +43,7 @@ contract GetWethValueInPoolBatchRequest { weth, r0, dexes, - dexIsUniV3, + dexVariantBitmap, wethInPoolThreshold ); // console.log("t0wvip", token0WethValueInPool); @@ -56,7 +53,7 @@ contract GetWethValueInPoolBatchRequest { weth, r1, dexes, - dexIsUniV3, + dexVariantBitmap, wethInPoolThreshold ); @@ -80,7 +77,7 @@ contract GetWethValueInPoolBatchRequest { } } - console.log("wvip", wethValueInPools[0]); + // console.log("wvip", wethValueInPools[0]); // insure abi encoding, not needed here but increase reusability for different return types // note: abi.encode add a first 32 bytes word with the address of the original data @@ -94,23 +91,38 @@ contract GetWethValueInPoolBatchRequest { } } - function badPool(address lp) internal returns (bool) { - //If the pool is v3 - if (!lpIsNotUniV3(lp)) { - if (IUniswapV3PoolState(lp).liquidity() == 0) { - return true; - } - } + function getTokens( + address lp + ) internal returns (address token0, address token1) { + uint8 poolVariant = getLpVariant(lp); - return false; + if (poolVariant == 0 || poolVariant == 1) { + token0 = IUniswapV2Pair(lp).token0(); //TODO: Add izumi support here + token1 = IUniswapV2Pair(lp).token1(); //TODO: Add izumi support here + } else { + //TODO: Get tokenX, tokenY from izumi pool interface + token0 = IiZiSwapPool(lp).tokenX(); + token1 = IiZiSwapPool(lp).tokenY(); + } } + // function badPool(address lp) internal returns (bool) { + // //If the pool is v3 + // if (!lpIsNotUniV3(lp)) { + // if (IUniswapV3PoolState(lp).liquidity() == 0) { + // return true; + // } + // } + + // return false; + // } + function getWethValueOfToken( address token, address weth, uint256 amount, address[] memory dexes, - bool[] memory dexIsUniV3, + uint8[] memory dexVariantBitmap, //TODO: Change this to dex bitmap uint256 wethInPoolThreshold ) internal returns (uint256) { //If the token is weth, the amount is the amount of weth in the pool for that token @@ -136,7 +148,7 @@ contract GetWethValueInPoolBatchRequest { token, weth, dexes[i], - dexIsUniV3[i], + dexVariantBitmap[i], wethInPoolThreshold ); @@ -157,49 +169,15 @@ contract GetWethValueInPoolBatchRequest { } } - ///Does not normalize to 18 decimals - function calculateV3VirtualReserves(address pool) - internal - view - returns (uint256 r_0, uint256 r_1) - { - (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3PoolState(pool).slot0(); - uint128 liquidity = IUniswapV3PoolState(pool).liquidity(); - - if (liquidity == 0 || sqrtPriceX96 == 0) { - return (0, 0); - } - - unchecked { - uint256 sqrtPriceInv = (2**192 / sqrtPriceX96); - - uint256 lo_r0 = (uint256(sqrtPriceInv) * - (uint256(liquidity) & (2**64))) >> 96; - uint256 hi_r0 = (uint256(sqrtPriceInv) * - (uint256(liquidity) >> 96)); - uint256 lo_r1 = (uint256(sqrtPriceX96) * - (uint256(liquidity) & (2**64))) >> 96; - uint256 hi_r1 = (uint256(sqrtPriceX96) * - (uint256(liquidity) >> 96)); - - hi_r0 <<= 96; - hi_r1 <<= 96; - - require(hi_r0 <= type(uint256).max, "hi_r0"); - require(hi_r1 <= type(uint256).max, "hi_r1"); - - (r_0, r_1) = (hi_r0 + lo_r0, hi_r1 + lo_r1); - } - } - function getTokenToWethPrice( address token, address weth, address dexFactory, - bool isUniV3, + uint8 dexVariant, //TODO: Change this to uint representing the dex variant of the bitmap. uint256 wethInPoolThreshold ) internal returns (uint128) { - if (isUniV3) { + //TODO: Differentiate between izumi, v2, v3 based on uint dex variant + if (dexVariant == 1) { uint16[3] memory feeTiers = [500, 3000, 10000]; for (uint256 i = 0; i < feeTiers.length; ++i) { address pairAddress = IUniswapV3Factory(dexFactory).getPool( @@ -211,7 +189,7 @@ contract GetWethValueInPoolBatchRequest { if (pairAddress != ADDRESS_ZERO) { ///Check here if the weth in pool threshold is met uint128 price = getTokenToWethPriceFromPool( - isUniV3, + dexVariant, token, weth, pairAddress, @@ -223,7 +201,7 @@ contract GetWethValueInPoolBatchRequest { } } } - } else { + } else if (dexVariant == 0) { bool tokenIsToken0 = token < weth; address pairAddress = IUniswapV2Factory(dexFactory).getPair( @@ -233,7 +211,7 @@ contract GetWethValueInPoolBatchRequest { if (pairAddress != ADDRESS_ZERO) { uint128 price = getTokenToWethPriceFromPool( - isUniV3, + dexVariant, token, weth, pairAddress, @@ -244,6 +222,31 @@ contract GetWethValueInPoolBatchRequest { return price; } } + } else { + //TODO: Do iziswap logic + uint16[4] memory feeTiers = [100, 400, 2000, 10000]; + for (uint256 i = 0; i < feeTiers.length; ++i) { + address pairAddress = IiZiSwapFactory(dexFactory).pool( + token < weth ? token : weth, + token < weth ? weth : token, + feeTiers[i] + ); + + if (pairAddress != ADDRESS_ZERO) { + ///Check here if the weth in pool threshold is met + uint128 price = getTokenToWethPriceFromPool( + dexVariant, + token, + weth, + pairAddress, + wethInPoolThreshold + ); + + if (price != 0) { + return price; + } + } + } } //We set the price to 1 so that we know that the token to weth pairing does not exist or is not valid @@ -252,14 +255,14 @@ contract GetWethValueInPoolBatchRequest { } function getTokenToWethPriceFromPool( - bool isUniV3, + uint8 dexVariant, //TODO: Change this to uint representing the dex variant from the bitmap. address token, address weth, address pool, uint256 wethLiquidityThreshold ) internal returns (uint128 price) { bool tokenIsToken0 = token < weth; - if (!isUniV3) { + if (dexVariant == 0) { (uint256 r_0, uint256 r_1) = getNormalizedReserves( pool, token, @@ -282,13 +285,19 @@ contract GetWethValueInPoolBatchRequest { } } - price = divuu( - tokenIsToken0 ? r_1 : r_0, - tokenIsToken0 ? r_0 : r_1 + price = divuu(tokenIsToken0 ? r_1 : r_0, tokenIsToken0 ? r_0 : r_1); + } else if (dexVariant == 1) { + (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3PoolState(pool) + .slot0(); + price = uint128( + fromSqrtX96(sqrtPriceX96, tokenIsToken0, token, weth) >> 64 + ); + } else { + //TODO: Add izumi case + (uint160 sqrtPriceX96, , , , , , , ) = IiZiSwapPool(pool).state(); + price = uint128( + fromSqrtX96(sqrtPriceX96, tokenIsToken0, token, weth) >> 64 ); - }else { - (uint160 sqrtPriceX96, , , , , , ) = IUniswapV3PoolState(pool).slot0(); - price = uint128(fromSqrtX96(sqrtPriceX96, tokenIsToken0, token, weth)>>64); } // console.log("price"); @@ -296,8 +305,6 @@ contract GetWethValueInPoolBatchRequest { //Add the price to the tokenToWeth price mapping tokenToWethPrices[token] = price; - - } function getReserves( @@ -311,13 +318,34 @@ contract GetWethValueInPoolBatchRequest { uint256 r_x; uint256 r_y; - - if (lpIsNotUniV3(lp)) { + uint8 poolVariant = getLpVariant(lp); + //TODO: Differentiate between izumi, v2, v3 through an external call to the interface. + if (poolVariant == 0) { (uint112 r_x_112, uint112 r_y_112, ) = IUniswapV2Pair(lp) .getReserves(); r_x = r_x_112; r_y = r_y_112; + } else if (poolVariant == 1) { + (uint256 lpBalanceOfToken0, bool success0) = getBalanceOfUnsafe( + token0, + lp + ); + (uint256 lpBalanceOfToken1, bool success1) = getBalanceOfUnsafe( + token1, + lp + ); + + if (success0 && success1) { + if (token0 < token1) { + r_x = lpBalanceOfToken0; + r_y = lpBalanceOfToken1; + } else { + r_y = lpBalanceOfToken0; + r_x = lpBalanceOfToken1; + } + } } else { + //TODO: Add izumi logic to get token balances. (uint256 lpBalanceOfToken0, bool success0) = getBalanceOfUnsafe( token0, lp @@ -370,11 +398,11 @@ contract GetWethValueInPoolBatchRequest { if (t0s && t1s) { r_x = token0Decimals <= 18 - ? x * (10**(18 - token0Decimals)) - : x / (10**(token0Decimals - 18)); + ? x * (10 ** (18 - token0Decimals)) + : x / (10 ** (token0Decimals - 18)); r_y = token1Decimals <= 18 - ? y * (10**(18 - token1Decimals)) - : y / (10**(token1Decimals - 18)); + ? y * (10 ** (18 - token1Decimals)) + : y / (10 ** (token1Decimals - 18)); } } @@ -390,8 +418,9 @@ contract GetWethValueInPoolBatchRequest { int8(IERC20(token1).decimals()); ///@notice Square the sqrtPrice ratio and normalize the value based on decimalShift. uint256 priceSquaredX96 = decimalShift < 0 - ? uint256(sqrtPriceX96)**2 / uint256(10)**(uint8(-decimalShift)) - : uint256(sqrtPriceX96)**2 * 10**uint8(decimalShift); + ? uint256(sqrtPriceX96) ** 2 / + uint256(10) ** (uint8(-decimalShift)) + : uint256(sqrtPriceX96) ** 2 * 10 ** uint8(decimalShift); ///@notice The first value is a Q96 representation of p_token0, the second is 128X fixed point representation of p_token1. uint256 priceSquaredShiftQ96 = token0IsReserve0 @@ -412,7 +441,7 @@ contract GetWethValueInPoolBatchRequest { /// @param x uint256 unsigned integer /// @param y uint256 unsigned integer /// @return unsigned 64.64 fixed point number - function divuu(uint256 x, uint256 y) internal view returns (uint128) { + function divuu(uint256 x, uint256 y) internal pure returns (uint128) { unchecked { if (y == 0) return 0; @@ -492,10 +521,9 @@ contract GetWethValueInPoolBatchRequest { } /// @notice returns true as the second return value if the token decimals can be successfully retrieved - function getTokenDecimalsUnsafe(address token) - internal - returns (uint8, bool) - { + function getTokenDecimalsUnsafe( + address token + ) internal returns (uint8, bool) { (bool tokenDecimalsSuccess, bytes memory tokenDecimalsData) = token .call(abi.encodeWithSignature("decimals()")); @@ -519,10 +547,10 @@ contract GetWethValueInPoolBatchRequest { } /// @notice returns true as the second return value if the token decimals can be successfully retrieved - function getBalanceOfUnsafe(address token, address targetAddress) - internal - returns (uint256, bool) - { + function getBalanceOfUnsafe( + address token, + address targetAddress + ) internal returns (uint256, bool) { (bool balanceOfSuccess, bytes memory balanceOfData) = token.call( abi.encodeWithSignature("balanceOf(address)", targetAddress) ); @@ -575,13 +603,34 @@ contract GetWethValueInPoolBatchRequest { ///@notice Helper function to determine if a pool address is Uni V2 compatible. ///@param lp - Pair address. ///@return bool Indicator whether the pool is not Uni V3 compatible. - function lpIsNotUniV3(address lp) internal returns (bool) { + function getLpVariant(address lp) internal returns (uint8) { bool success; assembly { - //store the function sig for "fee()" + //store the function sig for "getReserves()" + mstore( + 0x00, + 0x0902f1ac00000000000000000000000000000000000000000000000000000000 + ) + + success := call( + gas(), // gas remaining + lp, // destination address + 0, // no ether + 0x00, // input buffer (starts after the first 32 bytes in the `data` array) + 0x04, // input length (loaded from the first 32 bytes in the `data` array) + 0x00, // output buffer + 0x00 // output length + ) + } + if (success) { + return 0; + } + + assembly { + //store the function sig for "tokenX()" mstore( 0x00, - 0xddca3f4300000000000000000000000000000000000000000000000000000000 + 0x16dc165b00000000000000000000000000000000000000000000000000000000 ) success := call( @@ -594,9 +643,12 @@ contract GetWethValueInPoolBatchRequest { 0x00 // output length ) } - ///@notice return the opposite of success, meaning if the call succeeded, the address is univ3, and we should - ///@notice indicate that lpIsNotUniV3 is false - return !success; + + if (success) { + return 2; + } else { + return 1; //NOTE: This will for sure break the contract if we pass in another variant other than izumi into the contract with this method. + } } function codeSizeIsZero(address target) internal view returns (bool) { @@ -628,10 +680,18 @@ interface IERC20 { } interface IUniswapV2Factory { - function getPair(address tokenA, address tokenB) - external - view - returns (address pair); + function getPair( + address tokenA, + address tokenB + ) external view returns (address pair); +} + +interface IiZiSwapFactory { + function pool( + address tokenX, + address tokenY, + uint24 fee + ) external view returns (address); } interface IUniswapV2Pair { @@ -642,12 +702,36 @@ interface IUniswapV2Pair { function token1() external view returns (address); function getReserves() + external + view + returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast); +} + +interface IiZiSwapPool { + function tokenX() external view returns (address); + + function tokenY() external view returns (address); + + function state() external view returns ( - uint112 reserve0, - uint112 reserve1, - uint32 blockTimestampLast + // a 96 fixpoing number describe the sqrt value of current price(tokenX/tokenY) + uint160 sqrtPrice_96, + // The current point of the pool, 1.0001 ^ currentPoint = price + int24 currentPoint, + // The index of the last oracle observation that was written, + uint16 observationCurrentIndex, + // The current maximum number of observations stored in the pool, + uint16 observationQueueLen, + // The next maximum number of observations, to be updated when the observation. + uint16 observationNextQueueLen, + // whether the pool is locked (only used for checking reentrance) + bool locked, + // total liquidity on the currentPoint (currX * sqrtPrice + currY / sqrtPrice) + uint128 liquidity, + // liquidity of tokenX, liquidity of tokenY is liquidity - liquidityX + uint128 liquidityX ); } diff --git a/contracts/test/GetWethValueInPoolBatchRequest.t.sol b/contracts/test/GetWethValueInPoolBatchRequest.t.sol index 5df5a63..e8235f5 100644 --- a/contracts/test/GetWethValueInPoolBatchRequest.t.sol +++ b/contracts/test/GetWethValueInPoolBatchRequest.t.sol @@ -6,32 +6,33 @@ import "../GetWethValueInPoolBatchRequest.sol"; contract GasTest is DSTest { function setUp() public {} - + + /// @dev This requires an arbitrum rpc endpoint to test. function testBatchContract() public { address[] memory pools = new address[](1); // pools[0] = 0xf9E9526E55a0e1Fac1813B2fE88bc9B30Eea04F9; // pools[0] = 0xA374094527e1673A86dE625aa59517c5dE346d32; - pools[0] = 0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640; + pools[0] = 0x67425EE6EaC0E3DE4f560f221E9F9986dcD16037; address[] memory dexes = new address[](1); // dexes[0] = 0x5757371414417b8C6CAad45bAeF941aBc7d3Ab32; - dexes[0] = 0x1F98431c8aD98523631AE4a59f267346ea31F984; + dexes[0] = 0x45e5F26451CDB01B0fA1f8582E0aAD9A6F27C218; // dexes[0] = 0x1F98431c8aD98523631AE4a59f267346ea31F984; - bool[] memory dexIsUniV3 = new bool[](1); - dexIsUniV3[0] = true; + uint8[] memory dexVariant = new uint8[](1); + dexVariant[0] = 2; // dexIsUniV3[1] = false; // dexIsUniV3[0] = true; - address weth = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; + address weth = 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1; uint256 wethInPoolThreshold = 100000000000000000000; GetWethValueInPoolBatchRequest batchContract = new GetWethValueInPoolBatchRequest( pools, dexes, - dexIsUniV3, + dexVariant, weth, wethInPoolThreshold ); - console.logBytes(address(batchContract).code); + // console.logBytes(address(batchContract).code); } } diff --git a/src/batch_requests/filter_by_value/mod.rs b/src/batch_requests/filter_by_value/mod.rs index 508c5fa..be8cb2a 100644 --- a/src/batch_requests/filter_by_value/mod.rs +++ b/src/batch_requests/filter_by_value/mod.rs @@ -29,12 +29,12 @@ pub async fn get_weth_value_in_amm_batch_request( .map(|a| Token::Address(a.address())) .collect::>(); - let factory_is_uni_v3 = factories + let dexVariants = factories .iter() .map(|d| match d { - Factory::UniswapV2Factory(_) => Token::Bool(false), - Factory::UniswapV3Factory(_) => Token::Bool(true), - Factory::IziSwapFactory(_) => Token::Bool(true) //TODO: This needs to be changed + Factory::UniswapV2Factory(_) => Token::Uint(U256::zero()), + Factory::UniswapV3Factory(_) => Token::Uint(U256::from(1)), + Factory::IziSwapFactory(_) => Token::Uint(U256::from(2)) //TODO: This needs to be changed }) .collect::>(); @@ -46,7 +46,7 @@ pub async fn get_weth_value_in_amm_batch_request( let constructor_args = Token::Tuple(vec![ Token::Array(amms), Token::Array(factories), - Token::Array(factory_is_uni_v3), + Token::Array(dexVariants), Token::Address(weth), Token::Uint(weth_value_in_token_to_weth_pool_threshold), ]);