From b3f2a3cabae914aa8743dbd9476141df87dc85d0 Mon Sep 17 00:00:00 2001 From: primata Date: Fri, 3 Oct 2025 17:49:08 -0300 Subject: [PATCH 1/2] modifies tests so it handles uniswap swaps, univ4 does not have enough liquidity right now --- .../contracts/test/token/MOVETokenV2.t.sol | 122 ++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol b/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol index 3606e23b3..8f3dfb702 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol @@ -47,6 +47,48 @@ interface IRetrieveDelegates { function delegates(address account) external view returns (address); } +interface IUniswapV3Quoter { + function quoteExactInputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountIn, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountOut); + } + + interface ISwapRouter { + struct ExactInputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountIn; + uint256 amountOutMinimum; + uint160 sqrtPriceLimitX96; + } + + function exactInputSingle(ExactInputSingleParams calldata params) + external + payable + returns (uint256 amountOut); + + struct ExactOutputSingleParams { + address tokenIn; + address tokenOut; + uint24 fee; + address recipient; + uint256 amountOut; + uint256 amountInMaximum; + uint160 sqrtPriceLimitX96; + } + + function exactOutputSingle(ExactOutputSingleParams calldata params) + external + payable + returns (uint256 amountIn); +} + contract MOVETokenV2Test is Test { // ============================================================================= // STATE VARIABLES - CONTRACT INSTANCES @@ -562,6 +604,86 @@ contract MOVETokenV2Test is Test { assertEq(move2.totalSupply(), totalSupplyBefore - (amount * 2)); } + function testUniswap() public { + testConfigOFT(); + address v3movePool = 0x663901c2590893fdCe43c128D20603A9b69A053D; + address uniswapv3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; + address uniswapv4Router = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af; + address uniswapv3Quoter = 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6; + address uniswapv4Quoter = 0x61fFE014bA17989E743c5F6cB21bF9697530B21e; + + uint256 amount = 100; + + // quote from uniswap v3 + (bool successV3, bytes memory dataV3) = uniswapv3Quoter.call( + abi.encodeWithSignature( + "quoteExactInputSingle(address,address,uint24,uint256,uint160)", + address(move2), + 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + 10000, + amount, + 0 + ) + ); + require(successV3, "V3 quote failed"); + uint256 amountOutV3 = abi.decode(dataV3, (uint256)); + + // quote from uniswap v4 + // (bool successV4, bytes memory dataV4) = uniswapv4Quoter.call( + // abi.encodeWithSignature( + // "quoteExactInputSingle(address,address,uint24,uint256,uint160)", + // address(move2), + // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + // 3000, + // amount, + // 0 + // ) + // ); + // require(successV4, "V4 quote failed"); + // uint256 amountOutV4 = abi.decode(dataV4, (uint256)); + + uint256 balanceBefore = move2.balanceOf(anchorage); + uint256 snapshotId = vm.snapshot(); + + vm.prank(anchorage); + move2.approve(uniswapv3Router, amount); + + vm.prank(anchorage); + ISwapRouter(uniswapv3Router).exactInputSingle( + ISwapRouter.ExactInputSingleParams({ + tokenIn: address(move2), + tokenOut: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + fee: 10000, + recipient: anchorage, + amountIn: amount, + amountOutMinimum: (amountOutV3 * 95) / 100, // slippage 5% + sqrtPriceLimitX96: 0 + }) + ); + + assertEq(move2.balanceOf(anchorage), balanceBefore - amount); + + vm.prank(anchorage); + move2.approve(uniswapv4Router, amount); + + // vm.revertTo(snapshotId); + // vm.prank(anchorage); + // ISwapRouter(uniswapv4Router).exactInputSingle( + // ISwapRouter.ExactInputSingleParams({ + // tokenIn: address(move2), + // tokenOut: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH + // fee: 300, + // recipient: anchorage, + // deadline: block.timestamp + 100, + // amountIn: amount, + // amountOutMinimum: (amountOutV4 * 95) / 100, // slippage 5% + // sqrtPriceLimitX96: 0 + // }) + // ); + + // assertEq(move2.balanceOf(anchorage), balanceBefore - amount); + } + // ============================================================================= // HELPER FUNCTIONS // ============================================================================= From b5b6b8e51f4a615b072d7acec25e4bf63a3ff0c1 Mon Sep 17 00:00:00 2001 From: primata Date: Mon, 6 Oct 2025 16:42:44 -0300 Subject: [PATCH 2/2] prepare v4 swap --- .../mcr/contracts/lib/openzeppelin-contracts | 2 +- .../contracts/test/token/MOVETokenV2.t.sol | 190 ++++++++++++------ 2 files changed, 130 insertions(+), 62 deletions(-) diff --git a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts index dbb6104ce..c64a1edb6 160000 --- a/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts +++ b/protocol-units/settlement/mcr/contracts/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit dbb6104ce834628e473d2173bbc9d47f81a9eec3 +Subproject commit c64a1edb67b6e3f4a15cca8909c9482ad33a02b0 diff --git a/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol b/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol index 8f3dfb702..e7c515a72 100644 --- a/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol +++ b/protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol @@ -39,7 +39,6 @@ import {IOAppCore} from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOApp import {MessagingFee} from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; import {OFTAdapter} from "lib/LayerZero-v2/packages/layerzero-v2/evm/oapp/contracts/oft/OFTAdapter.sol"; import {OptionsBuilder} from "lib/LayerZero-v2/packages/layerzero-v2/evm/oapp/contracts/oapp/libs/OptionsBuilder.sol"; - // Safe contracts import {CompatibilityFallbackHandler} from "@safe-smart-account/contracts/handler/CompatibilityFallbackHandler.sol"; @@ -47,17 +46,39 @@ interface IRetrieveDelegates { function delegates(address account) external view returns (address); } +struct PoolKey { + /// @notice The lower currency of the pool, sorted numerically. + /// For native ETH, Currency currency0 = Currency.wrap(address(0)); + address currency0; + /// @notice The higher currency of the pool, sorted numerically + address currency1; + /// @notice The pool LP fee, capped at 1_000_000. If the highest bit is 1, the pool has a dynamic fee and must be exactly equal to 0x800000 + uint24 fee; + /// @notice Ticks that involve positions must be a multiple of tick spacing + int24 tickSpacing; + /// @notice The hooks of the pool + address hooks; +} + +struct QuoteExactInputSingleParams { + address tokenIn; + address tokenOut; + uint256 amountIn; + uint24 fee; + uint160 sqrtPriceLimitX96; +} + interface IUniswapV3Quoter { - function quoteExactInputSingle( - address tokenIn, - address tokenOut, - uint24 fee, - uint256 amountIn, - uint160 sqrtPriceLimitX96 - ) external returns (uint256 amountOut); - } + function quoteExactInputSingle( + address tokenIn, + address tokenOut, + uint24 fee, + uint256 amountIn, + uint160 sqrtPriceLimitX96 + ) external returns (uint256 amountOut); +} - interface ISwapRouter { +interface ISwapRouter { struct ExactInputSingleParams { address tokenIn; address tokenOut; @@ -68,25 +89,21 @@ interface IUniswapV3Quoter { uint160 sqrtPriceLimitX96; } - function exactInputSingle(ExactInputSingleParams calldata params) - external - payable - returns (uint256 amountOut); + function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut); +} - struct ExactOutputSingleParams { - address tokenIn; - address tokenOut; - uint24 fee; - address recipient; - uint256 amountOut; - uint256 amountInMaximum; - uint160 sqrtPriceLimitX96; +interface IV4Router { + struct ExactInputSingleParams { + PoolKey poolKey; + bool zeroForOne; + uint128 amountIn; + uint128 amountOutMinimum; + bytes hookData; } - function exactOutputSingle(ExactOutputSingleParams calldata params) + function execute(bytes memory commands, bytes[] memory inputs, uint256 deadline) external - payable - returns (uint256 amountIn); + payable; } contract MOVETokenV2Test is Test { @@ -167,7 +184,7 @@ contract MOVETokenV2Test is Test { uint32 public constant ULN_CONFIG_TYPE = 2; /// @dev LayerZero config type for receive library configuration uint32 public constant RECEIVE_CONFIG_TYPE = 2; - + /// @dev Total supply of MOVE tokens (10 billion with 8 decimals) uint256 public constant TOTAL_SUPPLY = 10000000000 * 10 ** 8; /// @dev MOVE token decimals @@ -213,12 +230,16 @@ contract MOVETokenV2Test is Test { assertEq(move.hasRole(DEFAULT_ADMIN_ROLE, other), false); vm.expectRevert( - abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), DEFAULT_ADMIN_ROLE) + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, address(this), DEFAULT_ADMIN_ROLE + ) ); move.grantRole(DEFAULT_ADMIN_ROLE, other); vm.prank(labs); - vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, labs, DEFAULT_ADMIN_ROLE)); + vm.expectRevert( + abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, labs, DEFAULT_ADMIN_ROLE) + ); move.grantRole(DEFAULT_ADMIN_ROLE, other); vm.prank(oldFoundation); @@ -288,7 +309,7 @@ contract MOVETokenV2Test is Test { address(moveTokenImplementation2), initializeData ); - + // Once transaction is scheduled comment out testScheduleAndSetPeer and RERUN TEST to verify that all arguments are correct testScheduleAndSetPeer(); @@ -396,18 +417,24 @@ contract MOVETokenV2Test is Test { assertEq(move2.hasRole(DEFAULT_ADMIN_ROLE, other), false); vm.prank(other); - vm.expectRevert(abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, DEFAULT_ADMIN_ROLE)); + vm.expectRevert( + abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, other, DEFAULT_ADMIN_ROLE) + ); move2.grantRole(DEFAULT_ADMIN_ROLE, other); vm.prank(foundation); vm.expectRevert( - abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, foundation, DEFAULT_ADMIN_ROLE) + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, foundation, DEFAULT_ADMIN_ROLE + ) ); move2.grantRole(DEFAULT_ADMIN_ROLE, other); vm.prank(oldFoundation); vm.expectRevert( - abi.encodeWithSelector(IAccessControl.AccessControlUnauthorizedAccount.selector, oldFoundation, DEFAULT_ADMIN_ROLE) + abi.encodeWithSelector( + IAccessControl.AccessControlUnauthorizedAccount.selector, oldFoundation, DEFAULT_ADMIN_ROLE + ) ); move2.grantRole(DEFAULT_ADMIN_ROLE, other); @@ -451,7 +478,7 @@ contract MOVETokenV2Test is Test { composeMsg: bytes(""), oftCmd: bytes("") }); - + IOFT deprecatedBridge = IOFT(bridge); vm.expectRevert(); // Should fail - bridge is deprecated deprecatedBridge.quoteSend(sendParam, false); @@ -606,7 +633,6 @@ contract MOVETokenV2Test is Test { function testUniswap() public { testConfigOFT(); - address v3movePool = 0x663901c2590893fdCe43c128D20603A9b69A053D; address uniswapv3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45; address uniswapv4Router = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af; address uniswapv3Quoter = 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6; @@ -628,19 +654,25 @@ contract MOVETokenV2Test is Test { require(successV3, "V3 quote failed"); uint256 amountOutV3 = abi.decode(dataV3, (uint256)); + // Adds eth and move balance to uniswapv4 pool + vm.deal(0x1B42bb0771690a3A82beDb1BC74933788145CbfD, 100 ether); + deal(address(move2), 0x1B42bb0771690a3A82beDb1BC74933788145CbfD, 100 ether); + uint24 v4fee = 10000; // quote from uniswap v4 - // (bool successV4, bytes memory dataV4) = uniswapv4Quoter.call( - // abi.encodeWithSignature( - // "quoteExactInputSingle(address,address,uint24,uint256,uint160)", - // address(move2), - // 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH - // 3000, - // amount, - // 0 - // ) - // ); - // require(successV4, "V4 quote failed"); - // uint256 amountOutV4 = abi.decode(dataV4, (uint256)); + (bool successV4, bytes memory dataV4) = uniswapv4Quoter.call( + abi.encodeWithSignature( + "quoteExactInputSingle((address,address,uint256,uint24,uint160))", + QuoteExactInputSingleParams({ + tokenIn: address(move2), + tokenOut: address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2), // ETH + amountIn: amount, + fee: v4fee, + sqrtPriceLimitX96: 0 + }) + ) + ); + require(successV4, "V4 quote failed"); + uint256 amountOutV4 = abi.decode(dataV4, (uint256)); uint256 balanceBefore = move2.balanceOf(anchorage); uint256 snapshotId = vm.snapshot(); @@ -665,21 +697,23 @@ contract MOVETokenV2Test is Test { vm.prank(anchorage); move2.approve(uniswapv4Router, amount); - - // vm.revertTo(snapshotId); - // vm.prank(anchorage); - // ISwapRouter(uniswapv4Router).exactInputSingle( - // ISwapRouter.ExactInputSingleParams({ - // tokenIn: address(move2), - // tokenOut: 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, // WETH - // fee: 300, - // recipient: anchorage, - // deadline: block.timestamp + 100, - // amountIn: amount, - // amountOutMinimum: (amountOutV4 * 95) / 100, // slippage 5% - // sqrtPriceLimitX96: 0 - // }) - // ); + + vm.revertTo(snapshotId); + vm.prank(anchorage); + move2.approve(uniswapv4Router, amount); + + PoolKey memory key = PoolKey({ + currency0: address(move2), // currency0 (lower) + currency1: address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2), // currency1 (higher) + fee: v4fee, // fee + tickSpacing: 1, // tickSpacing + hooks: address(0) // no hooks + }); + + + // vm.startPrank(anchorage); + // swapExactInputSingle(uniswapv4Router, key, uint128(amount), uint128((amount * 50) / 100)); + // vm.stopPrank(); // assertEq(move2.balanceOf(anchorage), balanceBefore - amount); } @@ -688,6 +722,40 @@ contract MOVETokenV2Test is Test { // HELPER FUNCTIONS // ============================================================================= + function swapExactInputSingle(address router, PoolKey memory key, uint128 amountIn, uint128 minAmountOut) + internal + returns (uint256 amountOut) + { + // Encode the Universal Router command + bytes memory commands = abi.encodePacked(uint8(0x10)); + bytes[] memory inputs = new bytes[](1); + + // Encode V4Router actions + bytes memory actions = + abi.encodePacked(uint8(0x06), uint8(0x0c), uint8(0x0f)); + + // Prepare parameters for each action + bytes[] memory params = new bytes[](3); + params[0] = abi.encode( + IV4Router.ExactInputSingleParams({ + poolKey: key, + zeroForOne: true, + amountIn: amountIn, + amountOutMinimum: minAmountOut, + hookData: bytes("") + }) + ); + params[1] = abi.encode(key.currency0, amountIn); + params[2] = abi.encode(key.currency1, minAmountOut); + + // Combine actions and params into inputs + inputs[0] = abi.encode(actions, params); + + // Execute the swap + uint256 deadline = block.timestamp + 20; + IV4Router(router).execute(commands, inputs, deadline); + } + /** * @dev Utility function to convert bytes to address using keccak256 hash * Used for generating deterministic addresses from string inputs