Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 199 additions & 9 deletions protocol-units/settlement/mcr/contracts/test/token/MOVETokenV2.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,73 @@ 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";

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);
}

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);
}

interface IV4Router {
struct ExactInputSingleParams {
PoolKey poolKey;
bool zeroForOne;
uint128 amountIn;
uint128 amountOutMinimum;
bytes hookData;
}

function execute(bytes memory commands, bytes[] memory inputs, uint256 deadline)
external
payable;
}

contract MOVETokenV2Test is Test {
// =============================================================================
// STATE VARIABLES - CONTRACT INSTANCES
Expand Down Expand Up @@ -125,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
Expand Down Expand Up @@ -171,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);
Expand Down Expand Up @@ -246,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();

Expand Down Expand Up @@ -354,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);

Expand Down Expand Up @@ -409,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);
Expand Down Expand Up @@ -562,10 +631,131 @@ contract MOVETokenV2Test is Test {
assertEq(move2.totalSupply(), totalSupplyBefore - (amount * 2));
}

function testUniswap() public {
testConfigOFT();
address uniswapv3Router = 0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45;
address uniswapv4Router = 0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af;
address uniswapv3Quoter = 0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6;
address uniswapv4Quoter = 0x61fFE014bA17989E743c5F6cB21bF9697530B21e;

uint256 amount = 100;

Copilot AI Oct 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded value '100' is a magic number. Consider defining it as a named constant or making it configurable to improve test maintainability.

Copilot uses AI. Check for mistakes.

// 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));

// 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,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();

Comment on lines +678 to +679

Copilot AI Oct 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The snapshot is created but never used effectively. Either remove this unused variable or implement the snapshot/revert pattern properly.

Suggested change
uint256 snapshotId = vm.snapshot();

Copilot uses AI. Check for mistakes.
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);
Comment on lines +698 to +699

Copilot AI Oct 4, 2025

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The approval for uniswapv4Router serves no purpose since the V4 swap code is commented out. Remove this unused approval or implement the V4 swap functionality.

Suggested change
vm.prank(anchorage);
move2.approve(uniswapv4Router, amount);
// vm.prank(anchorage);
// move2.approve(uniswapv4Router, amount);

Copilot uses AI. Check for mistakes.

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);
}

// =============================================================================
// 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
Expand Down
Loading