From 9120dc5451bc1e67846cef5e24989a2b7e03e80c Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Thu, 3 Jul 2025 19:21:18 +0200 Subject: [PATCH 01/23] feat: Implement IexecLayerZeroBridgeHarness and refactor deployment setup in TestUtils --- src/mocks/IexecLayerZeroBridgeHarness.sol | 23 ++++ test/units/RLCLiquidityUnifierUpgrade.t.sol | 18 ++- .../layerZero/IexecLayerZeroBridge.t.sol | 20 ++- .../IexecLayerZeroBridgeUpgrade.t.sol | 18 ++- test/units/utils/TestUtils.sol | 120 +++++++++++------- 5 files changed, 143 insertions(+), 56 deletions(-) create mode 100644 src/mocks/IexecLayerZeroBridgeHarness.sol diff --git a/src/mocks/IexecLayerZeroBridgeHarness.sol b/src/mocks/IexecLayerZeroBridgeHarness.sol new file mode 100644 index 00000000..c7d5e042 --- /dev/null +++ b/src/mocks/IexecLayerZeroBridgeHarness.sol @@ -0,0 +1,23 @@ +// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.22; + +import {IexecLayerZeroBridge} from "../bridges/layerZero/IexecLayerZeroBridge.sol"; + +contract IexecLayerZeroBridgeHarness is IexecLayerZeroBridge { + constructor(bool approvalRequired_, address bridgeableToken, address lzEndpoint) + IexecLayerZeroBridge(approvalRequired_, bridgeableToken, lzEndpoint) + {} + + function exposed_debit(address from, uint256 amountLD, uint256 minAmountLD, uint32 dstEid) + external + returns (uint256 amountSentLD, uint256 amountReceivedLD) + { + return _debit(from, amountLD, minAmountLD, dstEid); + } + + function exposed_credit(address to, uint256 amountLD, uint32 srcEid) external returns (uint256 amountReceivedLD) { + return _credit(to, amountLD, srcEid); + } +} diff --git a/test/units/RLCLiquidityUnifierUpgrade.t.sol b/test/units/RLCLiquidityUnifierUpgrade.t.sol index b6eb2f83..e2b0d7d2 100644 --- a/test/units/RLCLiquidityUnifierUpgrade.t.sol +++ b/test/units/RLCLiquidityUnifierUpgrade.t.sol @@ -22,8 +22,6 @@ contract RLCLiquidityUnifierUpgradeTest is TestHelperOz5 { address private upgrader = makeAddr("upgrader"); address public proxyAddress; - string private name = "iEx.ec Network Token"; - string public symbol = "RLC"; uint256 public constant NEW_STATE_VARIABLE = 2; function setUp() public virtual override { @@ -31,8 +29,20 @@ contract RLCLiquidityUnifierUpgradeTest is TestHelperOz5 { setUpEndpoints(2, LibraryType.UltraLightNode); mockEndpoint = address(endpoints[1]); - (,, rlcToken,, rlcLiquidityUnifierV1) = - TestUtils.setupDeployment(name, symbol, mockEndpoint, mockEndpoint, admin, upgrader, pauser); + TestUtils.DeploymentResult memory deploymentResult = TestUtils.setupDeployment( + TestUtils.DeploymentParams({ + iexecLayerZeroBridgeContractName: "IexecLayerZeroBridge", + lzEndpointSource: mockEndpoint, + lzEndpointDestination: mockEndpoint, + initialAdmin: admin, + initialUpgrader: upgrader, + initialPauser: pauser + }) + ); + + rlcToken = deploymentResult.rlcToken; + rlcLiquidityUnifierV1 = deploymentResult.rlcLiquidityUnifier; + proxyAddress = address(rlcLiquidityUnifierV1); } diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 331ebbf9..dd07b825 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -40,8 +40,6 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { uint256 private constant INITIAL_BALANCE = 100 * 10 ** 9; // 100 RLC tokens with 9 decimals uint256 private constant TRANSFER_AMOUNT = 1 * 10 ** 9; // 1 RLC token with 9 decimals - string private name = "iEx.ec Network Token"; - string private symbol = "RLC"; function setUp() public virtual override { super.setUp(); @@ -51,8 +49,22 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { address lzEndpointSource = address(endpoints[SOURCE_EID]); // Source endpoint for Sepolia - Destination endpoint for Arbitrum Sepolia address lzEndpointDestination = address(endpoints[DEST_EID]); // Source endpoint for Arbitrum Sepolia - Destination endpoint for Sepolia - (iexecLayerZeroBridgeEthereum, iexecLayerZeroBridgeChainX, rlcToken, rlcCrosschainToken, rlcLiquidityUnifier) = - TestUtils.setupDeployment(name, symbol, lzEndpointSource, lzEndpointDestination, admin, upgrader, pauser); + TestUtils.DeploymentResult memory deploymentResult2 = TestUtils.setupDeployment( + TestUtils.DeploymentParams({ + iexecLayerZeroBridgeContractName: "IexecLayerZeroBridge", + lzEndpointSource: lzEndpointSource, + lzEndpointDestination: lzEndpointDestination, + initialAdmin: admin, + initialUpgrader: upgrader, + initialPauser: pauser + }) + ); + + iexecLayerZeroBridgeEthereum = deploymentResult2.iexecLayerZeroBridgeChainA; + iexecLayerZeroBridgeChainX = deploymentResult2.iexecLayerZeroBridgeChainB; + rlcToken = deploymentResult2.rlcToken; + rlcCrosschainToken = deploymentResult2.rlcCrosschainToken; + rlcLiquidityUnifier = deploymentResult2.rlcLiquidityUnifier; address iexecLayerZeroBridgeEthereumAddress = address(iexecLayerZeroBridgeEthereum); address iexecLayerZeroBridgeChainXAddress = address(iexecLayerZeroBridgeChainX); diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol index ceec7cac..c8bf1afe 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol @@ -22,8 +22,6 @@ contract IexecLayerZeroBridgeUpgradeTest is TestHelperOz5 { address public pauser = makeAddr("pauser"); address public proxyAddress; - string public name = "iEx.ec Network Token"; - string public symbol = "RLC"; uint256 public constant NEW_STATE_VARIABLE = 2; function setUp() public virtual override { @@ -31,8 +29,20 @@ contract IexecLayerZeroBridgeUpgradeTest is TestHelperOz5 { setUpEndpoints(2, LibraryType.UltraLightNode); mockEndpoint = address(endpoints[1]); - (, iexecLayerZeroBridgeV1,, rlcCrosschainToken,) = - TestUtils.setupDeployment(name, symbol, mockEndpoint, mockEndpoint, admin, upgrader, pauser); + TestUtils.DeploymentResult memory deploymentResult1 = TestUtils.setupDeployment( + TestUtils.DeploymentParams({ + iexecLayerZeroBridgeContractName: "IexecLayerZeroBridge", + lzEndpointSource: mockEndpoint, + lzEndpointDestination: mockEndpoint, + initialAdmin: admin, + initialUpgrader: upgrader, + initialPauser: pauser + }) + ); + + iexecLayerZeroBridgeV1 = deploymentResult1.iexecLayerZeroBridgeChainA; + rlcCrosschainToken = deploymentResult1.rlcCrosschainToken; + proxyAddress = address(iexecLayerZeroBridgeV1); } diff --git a/test/units/utils/TestUtils.sol b/test/units/utils/TestUtils.sol index 0450b5e4..eb45e434 100644 --- a/test/units/utils/TestUtils.sol +++ b/test/units/utils/TestUtils.sol @@ -16,35 +16,58 @@ import {Deploy as RLCCrosschainTokenDeployScript} from "../../../script/RLCCross library TestUtils { using OptionsBuilder for bytes; - // TODO declare name and symbol inside the function. - function setupDeployment( - string memory name, - string memory symbol, - address lzEndpointSource, - address lzEndpointDestination, - address initialAdmin, - address initialUpgrader, - address initialPauser - ) - internal - returns ( - IexecLayerZeroBridge iexecLayerZeroBridgeChainA, - IexecLayerZeroBridge iexecLayerZeroBridgeChainB, - RLCMock rlcToken, - RLCCrosschainToken rlcCrosschainToken, - RLCLiquidityUnifier rlcLiquidityUnifier - ) - { - address createXFactory = address(new CreateX()); + // Struct to hold deployment parameters and reduce stack depth + struct DeploymentParams { + string iexecLayerZeroBridgeContractName; + address lzEndpointSource; + address lzEndpointDestination; + address initialAdmin; + address initialUpgrader; + address initialPauser; + } - // Deploy RLC token mock for L1 - rlcToken = new RLCMock(); + // Struct to hold deployment results + struct DeploymentResult { + IexecLayerZeroBridge iexecLayerZeroBridgeChainA; + IexecLayerZeroBridge iexecLayerZeroBridgeChainB; + RLCMock rlcToken; + RLCCrosschainToken rlcCrosschainToken; + RLCLiquidityUnifier rlcLiquidityUnifier; + } - // salt for createX + function setupDeployment(DeploymentParams memory params) public returns (DeploymentResult memory result) { + string memory name = "iEx.ec Network Token"; + string memory symbol = "RLC"; + address createXFactory = address(new CreateX()); bytes32 salt = keccak256("salt"); + // Deploy RLC token mock for L1 + result.rlcToken = new RLCMock(); + // Deploy Liquidity Unifier - rlcLiquidityUnifier = RLCLiquidityUnifier( + result.rlcLiquidityUnifier = + _deployLiquidityUnifier(result.rlcToken, params.initialAdmin, params.initialUpgrader, createXFactory, salt); + + // Deploy IexecLayerZeroBridge for Chain A + result.iexecLayerZeroBridgeChainA = + _deployBridge(params, true, address(result.rlcLiquidityUnifier), createXFactory, salt); + + // Deploy RLC Crosschain token and Bridge for Chain B + result.rlcCrosschainToken = + _deployCrosschainToken(name, symbol, params.initialAdmin, params.initialUpgrader, createXFactory, salt); + + result.iexecLayerZeroBridgeChainB = + _deployBridge(params, false, address(result.rlcCrosschainToken), createXFactory, salt); + } + + function _deployLiquidityUnifier( + RLCMock rlcToken, + address initialAdmin, + address initialUpgrader, + address createXFactory, + bytes32 salt + ) private returns (RLCLiquidityUnifier) { + return RLCLiquidityUnifier( UUPSProxyDeployer.deployUUPSProxyWithCreateX( "RLCLiquidityUnifier", abi.encode(rlcToken), @@ -53,39 +76,48 @@ library TestUtils { salt ) ); + } - // Deploy IexecLayerZeroBridgeAdapter - iexecLayerZeroBridgeChainA = IexecLayerZeroBridge( + function _deployBridge( + DeploymentParams memory params, + bool approvalRequired, + address bridgeableToken, + address createXFactory, + bytes32 salt + ) private returns (IexecLayerZeroBridge) { + return IexecLayerZeroBridge( UUPSProxyDeployer.deployUUPSProxyWithCreateX( - "IexecLayerZeroBridge", - abi.encode(true, rlcLiquidityUnifier, lzEndpointSource), + params.iexecLayerZeroBridgeContractName, + abi.encode( + approvalRequired, + bridgeableToken, + approvalRequired ? params.lzEndpointSource : params.lzEndpointDestination + ), abi.encodeWithSelector( - IexecLayerZeroBridge.initialize.selector, initialAdmin, initialUpgrader, initialPauser + IexecLayerZeroBridge.initialize.selector, + params.initialAdmin, + params.initialUpgrader, + params.initialPauser ), createXFactory, salt ) ); + } - // Deploy RLC Crosschain token (for L2) - rlcCrosschainToken = RLCCrosschainToken( + function _deployCrosschainToken( + string memory name, + string memory symbol, + address initialAdmin, + address initialUpgrader, + address createXFactory, + bytes32 salt + ) private returns (RLCCrosschainToken) { + return RLCCrosschainToken( new RLCCrosschainTokenDeployScript().deploy( name, symbol, initialAdmin, initialUpgrader, createXFactory, salt ) ); - // Deploy IexecLayerZeroBridge - iexecLayerZeroBridgeChainB = IexecLayerZeroBridge( - UUPSProxyDeployer.deployUUPSProxyWithCreateX( - "IexecLayerZeroBridge", - abi.encode(false, rlcCrosschainToken, lzEndpointDestination), - abi.encodeWithSelector( - IexecLayerZeroBridge.initialize.selector, initialAdmin, initialUpgrader, initialPauser - ), - createXFactory, - salt - ) - ); - // TODO: see if it's possible to authorize the bridge here. } /** From bf05f1bb423cc7ee3bfa7623a8e2e81cd00be9e9 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Fri, 4 Jul 2025 10:27:25 +0200 Subject: [PATCH 02/23] feat: Add IexecLayerZeroBridgeHarness test suite for enhanced cross-chain functionality --- .../layerZero/IexecLayerZeroBridge.t.sol | 3 - .../IexecLayerZeroBridgeHarness.t.sol | 225 ++++++++++++++++++ 2 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index dd07b825..e8ec4ba3 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -3,13 +3,11 @@ pragma solidity ^0.8.22; -import {OptionsBuilder} from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol"; import {MessagingFee, SendParam, IOFT} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; -import {CreateX} from "@createx/contracts/CreateX.sol"; import {IexecLayerZeroBridge} from "../../../../src/bridges/layerZero/IexecLayerZeroBridge.sol"; import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol"; import {TestUtils} from "../../utils/TestUtils.sol"; @@ -18,7 +16,6 @@ import {RLCLiquidityUnifier} from "../../../../src/RLCLiquidityUnifier.sol"; import {RLCMock} from "../../mocks/RLCMock.sol"; contract IexecLayerZeroBridgeTest is TestHelperOz5 { - using OptionsBuilder for bytes; using TestUtils for *; // ============ STATE VARIABLES ============ diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol new file mode 100644 index 00000000..2ff1c00e --- /dev/null +++ b/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol @@ -0,0 +1,225 @@ +// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +pragma solidity ^0.8.22; + +import {MessagingFee, SendParam, IOFT} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; +import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; +import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; +import {IexecLayerZeroBridge} from "../../../../src/bridges/layerZero/IexecLayerZeroBridge.sol"; +import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol"; +import {TestUtils} from "../../utils/TestUtils.sol"; +import {RLCCrosschainToken} from "../../../../src/RLCCrosschainToken.sol"; +import {RLCLiquidityUnifier} from "../../../../src/RLCLiquidityUnifier.sol"; +import {RLCMock} from "../../mocks/RLCMock.sol"; + +contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { + using TestUtils for *; + + // ============ STATE VARIABLES ============ + IexecLayerZeroBridge private iexecLayerZeroBridgeEthereum; // A chain with approval required. + IexecLayerZeroBridge private iexecLayerZeroBridgeChainX; + RLCCrosschainToken private rlcCrosschainToken; + RLCLiquidityUnifier private rlcLiquidityUnifier; + RLCMock private rlcToken; + + uint32 private constant SOURCE_EID = 1; + uint32 private constant DEST_EID = 2; + + address private admin = makeAddr("admin"); + address private upgrader = makeAddr("upgrader"); + address private pauser = makeAddr("pauser"); + address private user1 = makeAddr("user1"); + address private user2 = makeAddr("user2"); + address private unauthorizedUser = makeAddr("unauthorizedUser"); + + uint256 private constant INITIAL_BALANCE = 100 * 10 ** 9; // 100 RLC tokens with 9 decimals + uint256 private constant TRANSFER_AMOUNT = 1 * 10 ** 9; // 1 RLC token with 9 decimals + + function setUp() public virtual override { + super.setUp(); + setUpEndpoints(2, LibraryType.UltraLightNode); + + // Set up endpoints for the deployment + address lzEndpointSource = address(endpoints[SOURCE_EID]); // Source endpoint for Sepolia - Destination endpoint for Arbitrum Sepolia + address lzEndpointDestination = address(endpoints[DEST_EID]); // Source endpoint for Arbitrum Sepolia - Destination endpoint for Sepolia + + TestUtils.DeploymentResult memory deploymentResult2 = TestUtils.setupDeployment( + TestUtils.DeploymentParams({ + iexecLayerZeroBridgeContractName: "IexecLayerZeroBridgeHarness", + lzEndpointSource: lzEndpointSource, + lzEndpointDestination: lzEndpointDestination, + initialAdmin: admin, + initialUpgrader: upgrader, + initialPauser: pauser + }) + ); + + iexecLayerZeroBridgeEthereum = deploymentResult2.iexecLayerZeroBridgeChainA; + iexecLayerZeroBridgeChainX = deploymentResult2.iexecLayerZeroBridgeChainB; + rlcToken = deploymentResult2.rlcToken; + rlcCrosschainToken = deploymentResult2.rlcCrosschainToken; + rlcLiquidityUnifier = deploymentResult2.rlcLiquidityUnifier; + + address iexecLayerZeroBridgeEthereumAddress = address(iexecLayerZeroBridgeEthereum); + address iexecLayerZeroBridgeChainXAddress = address(iexecLayerZeroBridgeChainX); + // Wire the contracts + address[] memory contracts = new address[](2); + contracts[0] = iexecLayerZeroBridgeEthereumAddress; // Index 0 → EID 1 + contracts[1] = iexecLayerZeroBridgeChainXAddress; // Index 1 → EID 2 + vm.startPrank(admin); + wireOApps(contracts); + vm.stopPrank(); + + // ### Setup for chainX ### + // Authorize the bridge to mint/burn tokens. + vm.startPrank(admin); + rlcCrosschainToken.grantRole(rlcCrosschainToken.TOKEN_BRIDGE_ROLE(), iexecLayerZeroBridgeChainXAddress); + vm.stopPrank(); + + // Mint RLC tokens to user1 + vm.prank(iexecLayerZeroBridgeChainXAddress); + rlcCrosschainToken.crosschainMint(user1, INITIAL_BALANCE); + + // ### Setup for Ethereum Mainnet ### + // Authorize the bridge to lock/unLock tokens. + vm.startPrank(admin); + rlcLiquidityUnifier.grantRole(rlcLiquidityUnifier.TOKEN_BRIDGE_ROLE(), iexecLayerZeroBridgeEthereumAddress); + vm.stopPrank(); + + // Transfer initial RLC balance to user1 + rlcToken.transfer(user1, INITIAL_BALANCE); + + //Add label to make logs more readable + vm.label(iexecLayerZeroBridgeEthereumAddress, "iexecLayerZeroBridgeEthereum"); + vm.label(iexecLayerZeroBridgeChainXAddress, "iexecLayerZeroBridgeChainX"); + vm.label(address(rlcToken), "rlcToken"); + vm.label(address(rlcCrosschainToken), "rlcCrosschainToken"); + vm.label(address(rlcLiquidityUnifier), "rlcLiquidityUnifier"); + } + + // ============ _credit ============ + function test_credit_SuccessfulMintToUser() public { + // Test successful minting to a regular user address + uint256 mintAmount = TRANSFER_AMOUNT; + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + vm.expectEmit(true, true, true, true); + emit Transfer(address(0), user2, mintAmount); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + + assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(user2), + initialBalance + mintAmount, + "User balance should increase by mint amount" + ); + } + + function test_credit_ZeroAddressRedirection() public { + // Test that minting to zero address redirects to 0xdead address + uint256 mintAmount = TRANSFER_AMOUNT; + address deadAddress = address(0xdead); + uint256 initialBalance = rlcCrosschainToken.balanceOf(deadAddress); + + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), deadAddress, mintAmount); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), mintAmount, SOURCE_EID); + + assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(deadAddress), + initialBalance + mintAmount, + "Dead address balance should increase by mint amount" + ); + assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); + } + + function testFuzz_credit_Address(address to) public { + // Fuzz test with different addresses + vm.assume(to != address(0)); // Exclude zero address as it gets redirected + + uint256 mintAmount = TRANSFER_AMOUNT; + uint256 initialBalance = rlcCrosschainToken.balanceOf(to); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, mintAmount, SOURCE_EID); + + assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(to), initialBalance + mintAmount, "Address balance should increase"); + } + + function testFuzz_credit_Amount(uint256 amount) public { + // Fuzz test with different amounts + vm.assume(amount > 0 && amount < type(uint128).max); // Reasonable bounds to avoid overflow + + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, amount, SOURCE_EID); + + assertEq(amountReceived, amount, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(user2), initialBalance + amount, "User balance should increase by mint amount" + ); + } + + function test_credit_RevertsWhenFullyPaused() public { + // Test that _credit reverts when contract is fully paused + uint256 mintAmount = TRANSFER_AMOUNT; + + // Pause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); + + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + } + + function test_credit_WorksWhenOnlySendPaused() public { + // Test that _credit still works when only sends are paused (Level 2 pause) + uint256 mintAmount = TRANSFER_AMOUNT; + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // Pause only sends + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pauseSend(); + + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, mintAmount); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + + assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); + } + + function test_credit_WorksAfterUnpause() public { + // Test that _credit works after unpausing + uint256 mintAmount = TRANSFER_AMOUNT; + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // Pause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); + + // Verify it's paused + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + + // Unpause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.unpause(); + + // Now it should work + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, mintAmount); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + + assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); + } +} From 9dc52a139ac1284f721b2adc7389669e8ad7f1a2 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Fri, 4 Jul 2025 12:17:48 +0200 Subject: [PATCH 03/23] feat: Refactor deployment results and update test contracts for LayerZeroBridge --- foundry.toml | 4 +- .../layerZero/IexecLayerZeroBridge.t.sol | 12 +- .../IexecLayerZeroBridgeHarness.t.sol | 176 ++++++++---------- .../IexecLayerZeroBridgeUpgrade.t.sol | 2 +- test/units/utils/TestUtils.sol | 12 +- 5 files changed, 91 insertions(+), 115 deletions(-) diff --git a/foundry.toml b/foundry.toml index 2bde81b0..f1fa8def 100644 --- a/foundry.toml +++ b/foundry.toml @@ -17,11 +17,11 @@ extra_output = ["storageLayout"] [profile.test] optimizer = false +fuzz = true +runs = 1000 # TODO configure this profile and use it in CI. # [profile.ci] -# fuzz = true -# fuzz.runs = 1000 # TODO use vm.rpcUrl("arbitrumSepolia") instead of env variable. # https://getfoundry.sh/config/reference/testing#rpc_endpoints diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index e8ec4ba3..5e575b20 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -46,7 +46,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { address lzEndpointSource = address(endpoints[SOURCE_EID]); // Source endpoint for Sepolia - Destination endpoint for Arbitrum Sepolia address lzEndpointDestination = address(endpoints[DEST_EID]); // Source endpoint for Arbitrum Sepolia - Destination endpoint for Sepolia - TestUtils.DeploymentResult memory deploymentResult2 = TestUtils.setupDeployment( + TestUtils.DeploymentResult memory deploymentResult = TestUtils.setupDeployment( TestUtils.DeploymentParams({ iexecLayerZeroBridgeContractName: "IexecLayerZeroBridge", lzEndpointSource: lzEndpointSource, @@ -57,11 +57,11 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { }) ); - iexecLayerZeroBridgeEthereum = deploymentResult2.iexecLayerZeroBridgeChainA; - iexecLayerZeroBridgeChainX = deploymentResult2.iexecLayerZeroBridgeChainB; - rlcToken = deploymentResult2.rlcToken; - rlcCrosschainToken = deploymentResult2.rlcCrosschainToken; - rlcLiquidityUnifier = deploymentResult2.rlcLiquidityUnifier; + iexecLayerZeroBridgeEthereum = deploymentResult.iexecLayerZeroBridgeChainWithApproval; + iexecLayerZeroBridgeChainX = deploymentResult.iexecLayerZeroBridgeChainWithoutApproval; + rlcToken = deploymentResult.rlcToken; + rlcCrosschainToken = deploymentResult.rlcCrosschainToken; + rlcLiquidityUnifier = deploymentResult.rlcLiquidityUnifier; address iexecLayerZeroBridgeEthereumAddress = address(iexecLayerZeroBridgeEthereum); address iexecLayerZeroBridgeChainXAddress = address(iexecLayerZeroBridgeChainX); diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol index 2ff1c00e..055452f2 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol @@ -8,7 +8,7 @@ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/Pau import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; -import {IexecLayerZeroBridge} from "../../../../src/bridges/layerZero/IexecLayerZeroBridge.sol"; +import {IexecLayerZeroBridgeHarness} from "../../../../src/mocks/IexecLayerZeroBridgeHarness.sol"; import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol"; import {TestUtils} from "../../utils/TestUtils.sol"; import {RLCCrosschainToken} from "../../../../src/RLCCrosschainToken.sol"; @@ -19,11 +19,9 @@ contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { using TestUtils for *; // ============ STATE VARIABLES ============ - IexecLayerZeroBridge private iexecLayerZeroBridgeEthereum; // A chain with approval required. - IexecLayerZeroBridge private iexecLayerZeroBridgeChainX; + IexecLayerZeroBridgeHarness private iexecLayerZeroBridgeEthereum; // A chain with approval required. + IexecLayerZeroBridgeHarness private iexecLayerZeroBridgeChainX; RLCCrosschainToken private rlcCrosschainToken; - RLCLiquidityUnifier private rlcLiquidityUnifier; - RLCMock private rlcToken; uint32 private constant SOURCE_EID = 1; uint32 private constant DEST_EID = 2; @@ -46,7 +44,7 @@ contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { address lzEndpointSource = address(endpoints[SOURCE_EID]); // Source endpoint for Sepolia - Destination endpoint for Arbitrum Sepolia address lzEndpointDestination = address(endpoints[DEST_EID]); // Source endpoint for Arbitrum Sepolia - Destination endpoint for Sepolia - TestUtils.DeploymentResult memory deploymentResult2 = TestUtils.setupDeployment( + TestUtils.DeploymentResult memory deploymentResult = TestUtils.setupDeployment( TestUtils.DeploymentParams({ iexecLayerZeroBridgeContractName: "IexecLayerZeroBridgeHarness", lzEndpointSource: lzEndpointSource, @@ -57,14 +55,12 @@ contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { }) ); - iexecLayerZeroBridgeEthereum = deploymentResult2.iexecLayerZeroBridgeChainA; - iexecLayerZeroBridgeChainX = deploymentResult2.iexecLayerZeroBridgeChainB; - rlcToken = deploymentResult2.rlcToken; - rlcCrosschainToken = deploymentResult2.rlcCrosschainToken; - rlcLiquidityUnifier = deploymentResult2.rlcLiquidityUnifier; + address iexecLayerZeroBridgeEthereumAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithApproval); + address iexecLayerZeroBridgeChainXAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithoutApproval); + iexecLayerZeroBridgeChainX = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeEthereumAddress); + iexecLayerZeroBridgeChainX = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeChainXAddress); + rlcCrosschainToken = deploymentResult.rlcCrosschainToken; - address iexecLayerZeroBridgeEthereumAddress = address(iexecLayerZeroBridgeEthereum); - address iexecLayerZeroBridgeChainXAddress = address(iexecLayerZeroBridgeChainX); // Wire the contracts address[] memory contracts = new address[](2); contracts[0] = iexecLayerZeroBridgeEthereumAddress; // Index 0 → EID 1 @@ -83,81 +79,58 @@ contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { vm.prank(iexecLayerZeroBridgeChainXAddress); rlcCrosschainToken.crosschainMint(user1, INITIAL_BALANCE); - // ### Setup for Ethereum Mainnet ### - // Authorize the bridge to lock/unLock tokens. - vm.startPrank(admin); - rlcLiquidityUnifier.grantRole(rlcLiquidityUnifier.TOKEN_BRIDGE_ROLE(), iexecLayerZeroBridgeEthereumAddress); - vm.stopPrank(); - - // Transfer initial RLC balance to user1 - rlcToken.transfer(user1, INITIAL_BALANCE); - //Add label to make logs more readable - vm.label(iexecLayerZeroBridgeEthereumAddress, "iexecLayerZeroBridgeEthereum"); vm.label(iexecLayerZeroBridgeChainXAddress, "iexecLayerZeroBridgeChainX"); - vm.label(address(rlcToken), "rlcToken"); vm.label(address(rlcCrosschainToken), "rlcCrosschainToken"); - vm.label(address(rlcLiquidityUnifier), "rlcLiquidityUnifier"); } // ============ _credit ============ function test_credit_SuccessfulMintToUser() public { // Test successful minting to a regular user address - uint256 mintAmount = TRANSFER_AMOUNT; uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); vm.expectEmit(true, true, true, true); - emit Transfer(address(0), user2, mintAmount); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); assertEq( rlcCrosschainToken.balanceOf(user2), - initialBalance + mintAmount, + initialBalance + TRANSFER_AMOUNT, "User balance should increase by mint amount" ); } - function test_credit_ZeroAddressRedirection() public { - // Test that minting to zero address redirects to 0xdead address - uint256 mintAmount = TRANSFER_AMOUNT; - address deadAddress = address(0xdead); - uint256 initialBalance = rlcCrosschainToken.balanceOf(deadAddress); + function testFuzz_credit_Address(address to) public { + // Fuzz test with different addresses including zero address + // Handle zero address redirection + address actualRecipient = (to == address(0)) ? address(0xdead) : to; + uint256 initialBalance = rlcCrosschainToken.balanceOf(actualRecipient); + // Expect the Transfer event to the actual recipient (0xdead if input was address(0)) vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), deadAddress, mintAmount); + emit IERC20.Transfer(address(0), actualRecipient, TRANSFER_AMOUNT); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), mintAmount, SOURCE_EID); + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, TRANSFER_AMOUNT, SOURCE_EID); - assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); assertEq( - rlcCrosschainToken.balanceOf(deadAddress), - initialBalance + mintAmount, - "Dead address balance should increase by mint amount" + rlcCrosschainToken.balanceOf(actualRecipient), + initialBalance + TRANSFER_AMOUNT, + "Actual recipient balance should increase" ); - assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); - } - - function testFuzz_credit_Address(address to) public { - // Fuzz test with different addresses - vm.assume(to != address(0)); // Exclude zero address as it gets redirected - - uint256 mintAmount = TRANSFER_AMOUNT; - uint256 initialBalance = rlcCrosschainToken.balanceOf(to); - - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, mintAmount, SOURCE_EID); - assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); - assertEq(rlcCrosschainToken.balanceOf(to), initialBalance + mintAmount, "Address balance should increase"); + // Additional check for zero address case + if (to == address(0)) { + assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); + } } function testFuzz_credit_Amount(uint256 amount) public { - // Fuzz test with different amounts - vm.assume(amount > 0 && amount < type(uint128).max); // Reasonable bounds to avoid overflow - + // Fuzz test with different amounts for testing edge case (0 & max RLC supply) + vm.assume(amount <= INITIAL_BALANCE); uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, amount, SOURCE_EID); assertEq(amountReceived, amount, "Amount received should equal mint amount"); @@ -166,60 +139,63 @@ contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { ); } - function test_credit_RevertsWhenFullyPaused() public { - // Test that _credit reverts when contract is fully paused - uint256 mintAmount = TRANSFER_AMOUNT; + // function test_credit_RevertsWhenFullyPaused() public { + // // Test that _credit reverts when contract is fully paused + // uint256 mintAmount = TRANSFER_AMOUNT; - // Pause the contract - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pause(); + // // Pause the contract + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pause(); - vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - } + // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + // iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + // } - function test_credit_WorksWhenOnlySendPaused() public { - // Test that _credit still works when only sends are paused (Level 2 pause) - uint256 mintAmount = TRANSFER_AMOUNT; - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + // function test_credit_WorksWhenOnlySendPaused() public { + // // Test that _credit still works when only sends are paused (Level 2 pause) + // uint256 mintAmount = TRANSFER_AMOUNT; + // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - // Pause only sends - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pauseSend(); + // // Pause only sends + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pauseSend(); - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), user2, mintAmount); + // vm.expectEmit(true, true, true, true); + // emit IERC20.Transfer(address(0), user2, mintAmount); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); - assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); - } + // assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); + // } - function test_credit_WorksAfterUnpause() public { - // Test that _credit works after unpausing - uint256 mintAmount = TRANSFER_AMOUNT; - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + // function test_credit_WorksAfterUnpause() public { + // // Test that _credit works after unpausing + // uint256 mintAmount = TRANSFER_AMOUNT; + // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - // Pause the contract - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pause(); + // // Pause the contract + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pause(); - // Verify it's paused - vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + // // Verify it's paused + // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + // iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - // Unpause the contract - vm.prank(pauser); - iexecLayerZeroBridgeChainX.unpause(); + // // Unpause the contract + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.unpause(); - // Now it should work - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), user2, mintAmount); + // // Now it should work + // vm.expectEmit(true, true, true, true); + // emit IERC20.Transfer(address(0), user2, mintAmount); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); + // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); - assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); - } + // assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); + // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); + // } + + // ============ _debit ============ + // TODO:Add _debit tests } diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol index c8bf1afe..0cb8b905 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol @@ -40,7 +40,7 @@ contract IexecLayerZeroBridgeUpgradeTest is TestHelperOz5 { }) ); - iexecLayerZeroBridgeV1 = deploymentResult1.iexecLayerZeroBridgeChainA; + iexecLayerZeroBridgeV1 = deploymentResult1.iexecLayerZeroBridgeChainWithoutApproval; rlcCrosschainToken = deploymentResult1.rlcCrosschainToken; proxyAddress = address(iexecLayerZeroBridgeV1); diff --git a/test/units/utils/TestUtils.sol b/test/units/utils/TestUtils.sol index eb45e434..2b6ea89e 100644 --- a/test/units/utils/TestUtils.sol +++ b/test/units/utils/TestUtils.sol @@ -28,8 +28,8 @@ library TestUtils { // Struct to hold deployment results struct DeploymentResult { - IexecLayerZeroBridge iexecLayerZeroBridgeChainA; - IexecLayerZeroBridge iexecLayerZeroBridgeChainB; + IexecLayerZeroBridge iexecLayerZeroBridgeChainWithApproval; + IexecLayerZeroBridge iexecLayerZeroBridgeChainWithoutApproval; RLCMock rlcToken; RLCCrosschainToken rlcCrosschainToken; RLCLiquidityUnifier rlcLiquidityUnifier; @@ -48,15 +48,15 @@ library TestUtils { result.rlcLiquidityUnifier = _deployLiquidityUnifier(result.rlcToken, params.initialAdmin, params.initialUpgrader, createXFactory, salt); - // Deploy IexecLayerZeroBridge for Chain A - result.iexecLayerZeroBridgeChainA = + // Deploy IexecLayerZeroBridge for Sepolia + result.iexecLayerZeroBridgeChainWithApproval = _deployBridge(params, true, address(result.rlcLiquidityUnifier), createXFactory, salt); - // Deploy RLC Crosschain token and Bridge for Chain B + // Deploy RLC Crosschain token and Bridge for ChainX result.rlcCrosschainToken = _deployCrosschainToken(name, symbol, params.initialAdmin, params.initialUpgrader, createXFactory, salt); - result.iexecLayerZeroBridgeChainB = + result.iexecLayerZeroBridgeChainWithoutApproval = _deployBridge(params, false, address(result.rlcCrosschainToken), createXFactory, salt); } From c79968a119ca9c12c9b692ebb996b1ed3418bab0 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Fri, 4 Jul 2025 15:20:22 +0200 Subject: [PATCH 04/23] feat: Update IexecLayerZeroBridge tests to use harness and remove obsolete test file --- .../layerZero/IexecLayerZeroBridge.t.sol | 129 ++++++++++- .../IexecLayerZeroBridgeHarness.t.sol | 201 ------------------ 2 files changed, 119 insertions(+), 211 deletions(-) delete mode 100644 test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 5e575b20..9a750f33 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -3,12 +3,13 @@ pragma solidity ^0.8.22; +import {Script, console} from "forge-std/Script.sol"; import {MessagingFee, SendParam, IOFT} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; -import {IexecLayerZeroBridge} from "../../../../src/bridges/layerZero/IexecLayerZeroBridge.sol"; +import {IexecLayerZeroBridgeHarness} from "../../../../src/mocks/IexecLayerZeroBridgeHarness.sol"; import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol"; import {TestUtils} from "../../utils/TestUtils.sol"; import {RLCCrosschainToken} from "../../../../src/RLCCrosschainToken.sol"; @@ -19,8 +20,8 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { using TestUtils for *; // ============ STATE VARIABLES ============ - IexecLayerZeroBridge private iexecLayerZeroBridgeEthereum; // A chain with approval required. - IexecLayerZeroBridge private iexecLayerZeroBridgeChainX; + IexecLayerZeroBridgeHarness private iexecLayerZeroBridgeEthereum; // A chain with approval required. + IexecLayerZeroBridgeHarness private iexecLayerZeroBridgeChainX; RLCCrosschainToken private rlcCrosschainToken; RLCLiquidityUnifier private rlcLiquidityUnifier; RLCMock private rlcToken; @@ -48,7 +49,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { TestUtils.DeploymentResult memory deploymentResult = TestUtils.setupDeployment( TestUtils.DeploymentParams({ - iexecLayerZeroBridgeContractName: "IexecLayerZeroBridge", + iexecLayerZeroBridgeContractName: "IexecLayerZeroBridgeHarness", lzEndpointSource: lzEndpointSource, lzEndpointDestination: lzEndpointDestination, initialAdmin: admin, @@ -57,14 +58,15 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { }) ); - iexecLayerZeroBridgeEthereum = deploymentResult.iexecLayerZeroBridgeChainWithApproval; - iexecLayerZeroBridgeChainX = deploymentResult.iexecLayerZeroBridgeChainWithoutApproval; + address iexecLayerZeroBridgeEthereumAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithApproval); + address iexecLayerZeroBridgeChainXAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithoutApproval); + + iexecLayerZeroBridgeEthereum = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeEthereumAddress); + iexecLayerZeroBridgeChainX = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeChainXAddress); rlcToken = deploymentResult.rlcToken; rlcCrosschainToken = deploymentResult.rlcCrosschainToken; rlcLiquidityUnifier = deploymentResult.rlcLiquidityUnifier; - address iexecLayerZeroBridgeEthereumAddress = address(iexecLayerZeroBridgeEthereum); - address iexecLayerZeroBridgeChainXAddress = address(iexecLayerZeroBridgeChainX); // Wire the contracts address[] memory contracts = new address[](2); contracts[0] = iexecLayerZeroBridgeEthereumAddress; // Index 0 → EID 1 @@ -88,7 +90,6 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { vm.startPrank(admin); rlcLiquidityUnifier.grantRole(rlcLiquidityUnifier.TOKEN_BRIDGE_ROLE(), iexecLayerZeroBridgeEthereumAddress); vm.stopPrank(); - // Transfer initial RLC balance to user1 rlcToken.transfer(user1, INITIAL_BALANCE); @@ -115,7 +116,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { } function _test_SendToken_WhenOperational( - IexecLayerZeroBridge iexecLayerZeroBridge, + IexecLayerZeroBridgeHarness iexecLayerZeroBridge, address tokenAddress, bool approvalRequired ) internal { @@ -288,4 +289,112 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { "token() should return the correct token contract address" ); } + + // ============ _credit ============ + function test_credit_SuccessfulMintToUser() public { + // Test successful minting to a regular user address + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(user2), + initialBalance + TRANSFER_AMOUNT, + "User balance should increase by mint amount" + ); + } + + function testFuzz_credit_Address(address to) public { + // Fuzz test with different addresses including zero address + // Handle zero address redirection + address actualRecipient = (to == address(0)) ? address(0xdead) : to; + uint256 initialBalance = rlcCrosschainToken.balanceOf(actualRecipient); + + // Expect the Transfer event to the actual recipient (0xdead if input was address(0)) + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), actualRecipient, TRANSFER_AMOUNT); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(actualRecipient), + initialBalance + TRANSFER_AMOUNT, + "Actual recipient balance should increase" + ); + + // Additional check for zero address case + if (to == address(0)) { + assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); + } + } + + function testFuzz_credit_Amount(uint256 amount) public { + // Fuzz test with different amounts for testing edge case (0 & max RLC supply) + vm.assume(amount <= INITIAL_BALANCE); + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, amount, SOURCE_EID); + + assertEq(amountReceived, amount, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(user2), initialBalance + amount, "User balance should increase by mint amount" + ); + } + + function test_credit_RevertsWhenFullyPaused() public { + // Test that _credit reverts when contract is fully paused + // Pause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); + + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + } + + function test_credit_WorksWhenOnlySendPaused() public { + // Test that _credit still works when only sends are paused (Level 2 pause) + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // Pause only sends + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pauseSend(); + + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); + } + + function test_credit_WorksAfterUnpause() public { + // Test that _credit works after unpausing + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // Pause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); + + // Verify it's paused + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + // Unpause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.unpause(); + + // Now it should work + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); + } } diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol deleted file mode 100644 index 055452f2..00000000 --- a/test/units/bridges/layerZero/IexecLayerZeroBridgeHarness.t.sol +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 - -pragma solidity ^0.8.22; - -import {MessagingFee, SendParam, IOFT} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; -import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; -import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; -import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; -import {IexecLayerZeroBridgeHarness} from "../../../../src/mocks/IexecLayerZeroBridgeHarness.sol"; -import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol"; -import {TestUtils} from "../../utils/TestUtils.sol"; -import {RLCCrosschainToken} from "../../../../src/RLCCrosschainToken.sol"; -import {RLCLiquidityUnifier} from "../../../../src/RLCLiquidityUnifier.sol"; -import {RLCMock} from "../../mocks/RLCMock.sol"; - -contract IexecLayerZeroBridgeHarnessTest is TestHelperOz5 { - using TestUtils for *; - - // ============ STATE VARIABLES ============ - IexecLayerZeroBridgeHarness private iexecLayerZeroBridgeEthereum; // A chain with approval required. - IexecLayerZeroBridgeHarness private iexecLayerZeroBridgeChainX; - RLCCrosschainToken private rlcCrosschainToken; - - uint32 private constant SOURCE_EID = 1; - uint32 private constant DEST_EID = 2; - - address private admin = makeAddr("admin"); - address private upgrader = makeAddr("upgrader"); - address private pauser = makeAddr("pauser"); - address private user1 = makeAddr("user1"); - address private user2 = makeAddr("user2"); - address private unauthorizedUser = makeAddr("unauthorizedUser"); - - uint256 private constant INITIAL_BALANCE = 100 * 10 ** 9; // 100 RLC tokens with 9 decimals - uint256 private constant TRANSFER_AMOUNT = 1 * 10 ** 9; // 1 RLC token with 9 decimals - - function setUp() public virtual override { - super.setUp(); - setUpEndpoints(2, LibraryType.UltraLightNode); - - // Set up endpoints for the deployment - address lzEndpointSource = address(endpoints[SOURCE_EID]); // Source endpoint for Sepolia - Destination endpoint for Arbitrum Sepolia - address lzEndpointDestination = address(endpoints[DEST_EID]); // Source endpoint for Arbitrum Sepolia - Destination endpoint for Sepolia - - TestUtils.DeploymentResult memory deploymentResult = TestUtils.setupDeployment( - TestUtils.DeploymentParams({ - iexecLayerZeroBridgeContractName: "IexecLayerZeroBridgeHarness", - lzEndpointSource: lzEndpointSource, - lzEndpointDestination: lzEndpointDestination, - initialAdmin: admin, - initialUpgrader: upgrader, - initialPauser: pauser - }) - ); - - address iexecLayerZeroBridgeEthereumAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithApproval); - address iexecLayerZeroBridgeChainXAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithoutApproval); - iexecLayerZeroBridgeChainX = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeEthereumAddress); - iexecLayerZeroBridgeChainX = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeChainXAddress); - rlcCrosschainToken = deploymentResult.rlcCrosschainToken; - - // Wire the contracts - address[] memory contracts = new address[](2); - contracts[0] = iexecLayerZeroBridgeEthereumAddress; // Index 0 → EID 1 - contracts[1] = iexecLayerZeroBridgeChainXAddress; // Index 1 → EID 2 - vm.startPrank(admin); - wireOApps(contracts); - vm.stopPrank(); - - // ### Setup for chainX ### - // Authorize the bridge to mint/burn tokens. - vm.startPrank(admin); - rlcCrosschainToken.grantRole(rlcCrosschainToken.TOKEN_BRIDGE_ROLE(), iexecLayerZeroBridgeChainXAddress); - vm.stopPrank(); - - // Mint RLC tokens to user1 - vm.prank(iexecLayerZeroBridgeChainXAddress); - rlcCrosschainToken.crosschainMint(user1, INITIAL_BALANCE); - - //Add label to make logs more readable - vm.label(iexecLayerZeroBridgeChainXAddress, "iexecLayerZeroBridgeChainX"); - vm.label(address(rlcCrosschainToken), "rlcCrosschainToken"); - } - - // ============ _credit ============ - function test_credit_SuccessfulMintToUser() public { - // Test successful minting to a regular user address - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - - assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - assertEq( - rlcCrosschainToken.balanceOf(user2), - initialBalance + TRANSFER_AMOUNT, - "User balance should increase by mint amount" - ); - } - - function testFuzz_credit_Address(address to) public { - // Fuzz test with different addresses including zero address - // Handle zero address redirection - address actualRecipient = (to == address(0)) ? address(0xdead) : to; - uint256 initialBalance = rlcCrosschainToken.balanceOf(actualRecipient); - - // Expect the Transfer event to the actual recipient (0xdead if input was address(0)) - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), actualRecipient, TRANSFER_AMOUNT); - - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, TRANSFER_AMOUNT, SOURCE_EID); - - assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - assertEq( - rlcCrosschainToken.balanceOf(actualRecipient), - initialBalance + TRANSFER_AMOUNT, - "Actual recipient balance should increase" - ); - - // Additional check for zero address case - if (to == address(0)) { - assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); - } - } - - function testFuzz_credit_Amount(uint256 amount) public { - // Fuzz test with different amounts for testing edge case (0 & max RLC supply) - vm.assume(amount <= INITIAL_BALANCE); - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, amount, SOURCE_EID); - - assertEq(amountReceived, amount, "Amount received should equal mint amount"); - assertEq( - rlcCrosschainToken.balanceOf(user2), initialBalance + amount, "User balance should increase by mint amount" - ); - } - - // function test_credit_RevertsWhenFullyPaused() public { - // // Test that _credit reverts when contract is fully paused - // uint256 mintAmount = TRANSFER_AMOUNT; - - // // Pause the contract - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pause(); - - // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - // iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - // } - - // function test_credit_WorksWhenOnlySendPaused() public { - // // Test that _credit still works when only sends are paused (Level 2 pause) - // uint256 mintAmount = TRANSFER_AMOUNT; - // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - - // // Pause only sends - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pauseSend(); - - // vm.expectEmit(true, true, true, true); - // emit IERC20.Transfer(address(0), user2, mintAmount); - - // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - - // assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); - // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); - // } - - // function test_credit_WorksAfterUnpause() public { - // // Test that _credit works after unpausing - // uint256 mintAmount = TRANSFER_AMOUNT; - // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - - // // Pause the contract - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pause(); - - // // Verify it's paused - // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - // iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - - // // Unpause the contract - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.unpause(); - - // // Now it should work - // vm.expectEmit(true, true, true, true); - // emit IERC20.Transfer(address(0), user2, mintAmount); - - // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, mintAmount, SOURCE_EID); - - // assertEq(amountReceived, mintAmount, "Amount received should equal mint amount"); - // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + mintAmount, "User balance should increase"); - // } - - // ============ _debit ============ - // TODO:Add _debit tests -} From 21f67b1245f1b55351175c0df17645ea0efbf89c Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Fri, 4 Jul 2025 16:28:11 +0200 Subject: [PATCH 05/23] feat: Remove unused import from IexecLayerZeroBridge test file --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 9a750f33..431fddcc 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.22; -import {Script, console} from "forge-std/Script.sol"; import {MessagingFee, SendParam, IOFT} from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol"; import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; From 62f3ee5475761903cbba340facf6e140af0961d2 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Fri, 4 Jul 2025 21:02:28 +0200 Subject: [PATCH 06/23] feat: Add event emission expectations for CrosschainMint in IexecLayerZeroBridge _credit tests --- .../bridges/layerZero/IexecLayerZeroBridge.t.sol | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 431fddcc..99ec510d 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -294,8 +294,11 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { // Test successful minting to a regular user address uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + // Expect the Transfer & CrosschainMint event vm.expectEmit(true, true, true, true); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + vm.expectEmit(true, true, true, true); + emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); @@ -312,11 +315,6 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { // Handle zero address redirection address actualRecipient = (to == address(0)) ? address(0xdead) : to; uint256 initialBalance = rlcCrosschainToken.balanceOf(actualRecipient); - - // Expect the Transfer event to the actual recipient (0xdead if input was address(0)) - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), actualRecipient, TRANSFER_AMOUNT); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, TRANSFER_AMOUNT, SOURCE_EID); assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); @@ -364,6 +362,8 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { vm.expectEmit(true, true, true, true); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + vm.expectEmit(true, true, true, true); + emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); @@ -390,6 +390,8 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { // Now it should work vm.expectEmit(true, true, true, true); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + vm.expectEmit(true, true, true, true); + emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); From 960acd0e7cc06a974dc78988a4d4cef56ff20fea Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Mon, 7 Jul 2025 15:04:27 +0200 Subject: [PATCH 07/23] refactor: Rename test functions for clarity on paused states --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index d2f8468b..7c414c5b 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -343,7 +343,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { ); } - function test_credit_RevertsWhenFullyPaused() public { + function test_credit_RevertsWhenPaused() public { // Test that _credit reverts when contract is fully paused // Pause the contract vm.prank(pauser); @@ -353,13 +353,13 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); } - function test_credit_WorksWhenOnlySendPaused() public { + function test_credit_WorksWhenOutboundTransfersPaused() public { // Test that _credit still works when only sends are paused (Level 2 pause) uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); // Pause only sends vm.prank(pauser); - iexecLayerZeroBridgeChainX.pauseSend(); + iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); vm.expectEmit(true, true, true, true); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); From 6ee5934a6497ce45d4cad3e5ac35d592280c4c7c Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Mon, 7 Jul 2025 15:12:26 +0200 Subject: [PATCH 08/23] refactor: Update token type from RLCMock to IERC20 for improved interface usage --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 7c414c5b..a0974083 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -119,8 +119,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { address tokenAddress, bool approvalRequired ) internal { - // This interface can be use for both token as we only use balanceOf func - RLCMock token = RLCMock(tokenAddress); + IERC20 token = IERC20(tokenAddress); // Check initial balances uint256 initialBalance = token.balanceOf(user1); From 21b37f0c6fa10cd693dc14321b1a12d97d89ba1a Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 12:06:26 +0200 Subject: [PATCH 09/23] fix: Correct parameter order in UUPSProxyDeployer deployment --- test/units/utils/TestUtils.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/units/utils/TestUtils.sol b/test/units/utils/TestUtils.sol index ab403b43..0fe85ca4 100644 --- a/test/units/utils/TestUtils.sol +++ b/test/units/utils/TestUtils.sol @@ -87,7 +87,9 @@ library TestUtils { UUPSProxyDeployer.deployUsingCreateX( "IexecLayerZeroBridge", abi.encode( - true, bridgeableToken, approvalRequired ? params.lzEndpointSource : params.lzEndpointDestination + approvalRequired, + bridgeableToken, + approvalRequired ? params.lzEndpointSource : params.lzEndpointDestination ), abi.encodeWithSelector( IexecLayerZeroBridge.initialize.selector, From 090d5a56414d7f7ec89741c4ca8dc68251159acb Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 12:21:18 +0200 Subject: [PATCH 10/23] fix: Use dynamic contract name for IexecLayerZeroBridge deployment --- test/units/utils/TestUtils.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/utils/TestUtils.sol b/test/units/utils/TestUtils.sol index 0fe85ca4..66ef4627 100644 --- a/test/units/utils/TestUtils.sol +++ b/test/units/utils/TestUtils.sol @@ -85,7 +85,7 @@ library TestUtils { ) private returns (IexecLayerZeroBridge) { return IexecLayerZeroBridge( UUPSProxyDeployer.deployUsingCreateX( - "IexecLayerZeroBridge", + params.iexecLayerZeroBridgeContractName, abi.encode( approvalRequired, bridgeableToken, From efd97f51087b90994dee24fb8c9300e6c3a36d76 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 14:05:00 +0200 Subject: [PATCH 11/23] refactor: Simplify liquidity unifier and crosschain token deployment by using deploy scripts --- test/units/utils/TestUtils.sol | 31 ++++++++----------------------- 1 file changed, 8 insertions(+), 23 deletions(-) diff --git a/test/units/utils/TestUtils.sol b/test/units/utils/TestUtils.sol index 66ef4627..d74e6b5f 100644 --- a/test/units/utils/TestUtils.sol +++ b/test/units/utils/TestUtils.sol @@ -10,7 +10,9 @@ import {UUPSProxyDeployer} from "../../../script/lib/UUPSProxyDeployer.sol"; import {RLCMock} from "../mocks/RLCMock.sol"; import {IexecLayerZeroBridge} from "../../../src/bridges/layerZero/IexecLayerZeroBridge.sol"; import {RLCLiquidityUnifier} from "../../../src/RLCLiquidityUnifier.sol"; +import {Deploy as RLCLiquidityUnifierDeployScript} from "../../../script/RLCLiquidityUnifier.s.sol"; import {RLCCrosschainToken} from "../../../src/RLCCrosschainToken.sol"; +import {Deploy as RLCCrosschainTokenDeployScript} from "../../../script/RLCCrosschainToken.s.sol"; library TestUtils { using OptionsBuilder for bytes; @@ -44,8 +46,7 @@ library TestUtils { result.rlcToken = new RLCMock(); // Deploy Liquidity Unifier - result.rlcLiquidityUnifier = - _deployLiquidityUnifier(result.rlcToken, params.initialAdmin, params.initialUpgrader, createXFactory, salt); + result.rlcLiquidityUnifier = _deployLiquidityUnifier(params, result.rlcToken, createXFactory, salt); // Deploy IexecLayerZeroBridge for Sepolia result.iexecLayerZeroBridgeChainWithApproval = @@ -59,19 +60,14 @@ library TestUtils { } function _deployLiquidityUnifier( + DeploymentParams memory params, RLCMock rlcToken, - address initialAdmin, - address initialUpgrader, address createXFactory, bytes32 salt ) private returns (RLCLiquidityUnifier) { return RLCLiquidityUnifier( - UUPSProxyDeployer.deployUsingCreateX( - "RLCLiquidityUnifier", - abi.encode(rlcToken), - abi.encodeWithSelector(RLCLiquidityUnifier.initialize.selector, initialAdmin, initialUpgrader), - createXFactory, - salt + new RLCLiquidityUnifierDeployScript().deploy( + address(rlcToken), params.initialAdmin, params.initialUpgrader, createXFactory, salt ) ); } @@ -111,19 +107,8 @@ library TestUtils { bytes32 salt ) private returns (RLCCrosschainToken) { return RLCCrosschainToken( - UUPSProxyDeployer.deployUsingCreateX( - "RLCCrosschainToken", - abi.encode(), - abi.encodeWithSelector( - RLCCrosschainToken.initialize.selector, - name, - symbol, - params.initialAdmin, - params.initialUpgrader, - params.initialPauser - ), - createXFactory, - salt + new RLCCrosschainTokenDeployScript().deploy( + name, symbol, params.initialAdmin, params.initialUpgrader, createXFactory, salt ) ); } From 7878f718f5af44deeb4362dc8a87783b72fe311d Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 14:53:50 +0200 Subject: [PATCH 12/23] fix: Update Forge build command to include size analysis for source files --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 92bf14e3..8c209462 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: run: forge fmt --check - name: Run Forge build - run: forge build --sizes && cp .env.template .env + run: forge build && forge build './src' --sizes - name: Run Foundry coverage run: make generate-coverage From fd25e940e8334a2fb34a1e8cbf269a8c9e443ccf Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 14:55:25 +0200 Subject: [PATCH 13/23] test ci From cbffc8b81e2c2af9d1e65d7e867a7b1630eb6082 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 15:18:07 +0200 Subject: [PATCH 14/23] fix: Add environment file copy step after Forge build --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c209462..88cf85ff 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -31,7 +31,7 @@ jobs: run: forge fmt --check - name: Run Forge build - run: forge build && forge build './src' --sizes + run: forge build && forge build './src' --sizes && cp .env.template .env - name: Run Foundry coverage run: make generate-coverage From d9cbc1d7afbaa7299e6a1067883c2edcb5d6f41d Mon Sep 17 00:00:00 2001 From: Robin Le Caignec <72495599+Le-Caignec@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:58:52 +0200 Subject: [PATCH 15/23] Update test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol Co-authored-by: Zied Guesmi <26070035+zguesmi@users.noreply.github.com> --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index a0974083..24bc72ca 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -295,7 +295,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); // Expect the Transfer & CrosschainMint event - vm.expectEmit(true, true, true, true); + vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); vm.expectEmit(true, true, true, true); emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); From 34dbb200f24596f24a5b484171f58971afdc6885 Mon Sep 17 00:00:00 2001 From: Robin Le Caignec <72495599+Le-Caignec@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:59:00 +0200 Subject: [PATCH 16/23] Update test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol Co-authored-by: Zied Guesmi <26070035+zguesmi@users.noreply.github.com> --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 24bc72ca..12a2e9ab 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -297,7 +297,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { // Expect the Transfer & CrosschainMint event vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - vm.expectEmit(true, true, true, true); + vm.expectEmit(true, true, true, true, address(iexecLayerZeroBridgeChainX)); emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); From 49f461c4c29debe139faf566038273c174f0efcb Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 17:14:07 +0200 Subject: [PATCH 17/23] refactor: rename fuzz test for credit function to clarify address handling --- .../bridges/layerZero/IexecLayerZeroBridge.t.sol | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 12a2e9ab..6ffdda87 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -310,24 +310,21 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { ); } - function testFuzz_credit_Address(address to) public { + function test_credit_SendToDeadAddressInsteadOfZeroAddress() public { // Fuzz test with different addresses including zero address // Handle zero address redirection - address actualRecipient = (to == address(0)) ? address(0xdead) : to; - uint256 initialBalance = rlcCrosschainToken.balanceOf(actualRecipient); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(to, TRANSFER_AMOUNT, SOURCE_EID); + uint256 initialBalance = rlcCrosschainToken.balanceOf(address(0xdead)); + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), TRANSFER_AMOUNT, SOURCE_EID); assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); assertEq( - rlcCrosschainToken.balanceOf(actualRecipient), + rlcCrosschainToken.balanceOf(address(0xdead)), initialBalance + TRANSFER_AMOUNT, "Actual recipient balance should increase" ); - // Additional check for zero address case - if (to == address(0)) { - assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); - } + assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); + } function testFuzz_credit_Amount(uint256 amount) public { From 795cf821b3131dcc5bbeab4fc88c06239cd5df32 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 17:40:37 +0200 Subject: [PATCH 18/23] fix: correct expected emit address in credit function test --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 5 ++--- {src => test/units}/mocks/IexecLayerZeroBridgeHarness.sol | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) rename {src => test/units}/mocks/IexecLayerZeroBridgeHarness.sol (89%) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 6ffdda87..7561804b 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -8,7 +8,7 @@ import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/Pau import {IERC7802} from "@openzeppelin/contracts/interfaces/draft-IERC7802.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {TestHelperOz5} from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; -import {IexecLayerZeroBridgeHarness} from "../../../../src/mocks/IexecLayerZeroBridgeHarness.sol"; +import {IexecLayerZeroBridgeHarness} from "../../mocks/IexecLayerZeroBridgeHarness.sol"; import {DualPausableUpgradeable} from "../../../../src/bridges/utils/DualPausableUpgradeable.sol"; import {TestUtils} from "../../utils/TestUtils.sol"; import {RLCCrosschainToken} from "../../../../src/RLCCrosschainToken.sol"; @@ -297,7 +297,7 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { // Expect the Transfer & CrosschainMint event vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - vm.expectEmit(true, true, true, true, address(iexecLayerZeroBridgeChainX)); + vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); @@ -324,7 +324,6 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { ); assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); - } function testFuzz_credit_Amount(uint256 amount) public { diff --git a/src/mocks/IexecLayerZeroBridgeHarness.sol b/test/units/mocks/IexecLayerZeroBridgeHarness.sol similarity index 89% rename from src/mocks/IexecLayerZeroBridgeHarness.sol rename to test/units/mocks/IexecLayerZeroBridgeHarness.sol index c7d5e042..4e6f4da0 100644 --- a/src/mocks/IexecLayerZeroBridgeHarness.sol +++ b/test/units/mocks/IexecLayerZeroBridgeHarness.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.22; -import {IexecLayerZeroBridge} from "../bridges/layerZero/IexecLayerZeroBridge.sol"; +import {IexecLayerZeroBridge} from "../../../src/bridges/layerZero/IexecLayerZeroBridge.sol"; contract IexecLayerZeroBridgeHarness is IexecLayerZeroBridge { constructor(bool approvalRequired_, address bridgeableToken, address lzEndpoint) From f3db4883858b97bd382f5aba0156979c11b32b46 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 17:47:13 +0200 Subject: [PATCH 19/23] refactor: comment out existing tests for send functionality and credit tests --- .../layerZero/IexecLayerZeroBridge.t.sol | 532 +++++++++--------- 1 file changed, 267 insertions(+), 265 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 7561804b..02cdafd4 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -104,231 +104,233 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { //TODO: Add more tests for send functionality, in both directions // ============ BASIC BRIDGE FUNCTIONALITY TESTS ============ - function test_SendToken_WhenOperational_WithApproval() public { - vm.prank(user1); - rlcToken.approve(address(iexecLayerZeroBridgeEthereum), TRANSFER_AMOUNT); - _test_SendToken_WhenOperational(iexecLayerZeroBridgeEthereum, address(rlcToken), true); - } - - function test_SendToken_WhenOperational_WithoutApproval() public { - _test_SendToken_WhenOperational(iexecLayerZeroBridgeChainX, address(rlcCrosschainToken), false); - } - - function _test_SendToken_WhenOperational( - IexecLayerZeroBridgeHarness iexecLayerZeroBridge, - address tokenAddress, - bool approvalRequired - ) internal { - IERC20 token = IERC20(tokenAddress); - - // Check initial balances - uint256 initialBalance = token.balanceOf(user1); - assertEq(initialBalance, INITIAL_BALANCE, "Initial balance should match expected amount"); - - // Prepare send parameters using utility - (SendParam memory sendParam, MessagingFee memory fee) = TestUtils.prepareSend( - iexecLayerZeroBridge, addressToBytes32(user2), TRANSFER_AMOUNT, approvalRequired ? DEST_EID : SOURCE_EID - ); - - // For approval flow, expect Transfer event from ERC20 token - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(user1, approvalRequired ? address(rlcLiquidityUnifier) : address(0), TRANSFER_AMOUNT); - - if (!approvalRequired) { - // For non-approval flow, expect CrosschainBurn event - vm.expectEmit(true, true, true, true); - emit IERC7802.CrosschainBurn(user1, TRANSFER_AMOUNT, address(iexecLayerZeroBridge)); - } - - // Expect OFTSent event from the bridge (this should be emitted by the parent OFT contract) - vm.expectEmit(false, true, true, true); - emit IOFT.OFTSent( - bytes32(0), // ignore this value - sendParam.dstEid, - user1, - TRANSFER_AMOUNT, - TRANSFER_AMOUNT - ); - - // Send tokens - vm.prank(user1); - vm.deal(user1, fee.nativeFee); - iexecLayerZeroBridge.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - - // Verify source state - tokens should be burned/locked - assertEq(token.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT, "Tokens should be deducted from sender"); - } - - // ============ LEVEL 1 PAUSE TESTS (Complete Pause) ============ - function test_Pause_OnlyPauserRole() public { - vm.expectRevert(); - vm.prank(unauthorizedUser); - iexecLayerZeroBridgeChainX.pause(); - } - - function test_Pause_BlocksAllTransfers() public { - // TODO make check outbound and inbound transfers. - // Pause the bridge - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pause(); - - // Prepare send parameters - (SendParam memory sendParam, MessagingFee memory fee) = - TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); - - // Attempt to send tokens - should revert - vm.deal(user1, fee.nativeFee); - vm.prank(user1); - vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - - // Verify no tokens were burned - assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); - } - - function test_Unpause_RestoresFullFunctionality() public { - // Pause then unpause the bridge - vm.startPrank(pauser); - iexecLayerZeroBridgeChainX.pause(); - - iexecLayerZeroBridgeChainX.unpause(); - vm.stopPrank(); - - // Should now work normally - test_SendToken_WhenOperational_WithoutApproval(); - } - - function test_sendRLCWhenSourceLayerZeroBridgeUnpaused() public { - // Pause then unpause the bridge - vm.startPrank(pauser); - iexecLayerZeroBridgeChainX.pause(); - iexecLayerZeroBridgeChainX.unpause(); - vm.stopPrank(); - - // Prepare send parameters using utility - (SendParam memory sendParam, MessagingFee memory fee) = - TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); - - // Send tokens - vm.deal(user1, fee.nativeFee); - vm.prank(user1); - iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - - // Verify source state - tokens should be burned - assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT); - } - - // ============ LEVEL 2 PAUSE TESTS (Outbount transfer pause) ============ - - function test_PauseOutboundTransfers_OnlyPauserRole() public { - vm.expectRevert(); - vm.prank(unauthorizedUser); - iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - } - - function test_PauseOutboundTransfers_BlocksOutboundOnly() public { - // Pause outbound transfers - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - - // Verify state - assertFalse(iexecLayerZeroBridgeChainX.paused()); - assertTrue(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); - - // Prepare send parameters - (SendParam memory sendParam, MessagingFee memory fee) = - TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); - - // Attempt to send tokens - should revert with EnforcedOutboundTransfersPause - vm.deal(user1, fee.nativeFee); - vm.prank(user1); - vm.expectRevert(DualPausableUpgradeable.EnforcedOutboundTransfersPause.selector); - iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - - // Verify no tokens were burned - assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); - } - - function test_unpauseOutboundTransfers_RestoresboundgoingTransfers() public { - // Pause then unpause send - vm.startPrank(pauser); - iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - - iexecLayerZeroBridgeChainX.unpauseOutboundTransfers(); - vm.stopPrank(); - - // Should now work normally - assertFalse(iexecLayerZeroBridgeChainX.paused()); - assertFalse(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); - - test_SendToken_WhenOperational_WithoutApproval(); - } - - // ============ token and approvalRequired ============ - function test_ReturnsApprovalRequired_WithApproval() public view { - assertEq(iexecLayerZeroBridgeEthereum.approvalRequired(), true, "approvalRequired() should return true"); - } - - function test_ReturnsApprovalRequired_WithoutApproval() public view { - assertEq(iexecLayerZeroBridgeChainX.approvalRequired(), false, "approvalRequired() should return false"); - } - - function test_ReturnsBridgeableTokenAddress_WithApproval() public view { - assertEq( - iexecLayerZeroBridgeEthereum.token(), - address(rlcToken), - "token() should return the correct token contract address" - ); - } - - function test_ReturnsBridgeableTokenAddress_WithoutApproval() public view { - assertEq( - iexecLayerZeroBridgeChainX.token(), - address(rlcCrosschainToken), - "token() should return the correct token contract address" - ); - } - - // ============ _credit ============ - function test_credit_SuccessfulMintToUser() public { - // Test successful minting to a regular user address - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - - // Expect the Transfer & CrosschainMint event - vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); - emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); - emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); - - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - - assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - assertEq( - rlcCrosschainToken.balanceOf(user2), - initialBalance + TRANSFER_AMOUNT, - "User balance should increase by mint amount" - ); - } - - function test_credit_SendToDeadAddressInsteadOfZeroAddress() public { - // Fuzz test with different addresses including zero address - // Handle zero address redirection - uint256 initialBalance = rlcCrosschainToken.balanceOf(address(0xdead)); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), TRANSFER_AMOUNT, SOURCE_EID); - - assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - assertEq( - rlcCrosschainToken.balanceOf(address(0xdead)), - initialBalance + TRANSFER_AMOUNT, - "Actual recipient balance should increase" - ); - - assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); - } + // function test_SendToken_WhenOperational_WithApproval() public { + // vm.prank(user1); + // rlcToken.approve(address(iexecLayerZeroBridgeEthereum), TRANSFER_AMOUNT); + // _test_SendToken_WhenOperational(iexecLayerZeroBridgeEthereum, address(rlcToken), true); + // } + + // function test_SendToken_WhenOperational_WithoutApproval() public { + // _test_SendToken_WhenOperational(iexecLayerZeroBridgeChainX, address(rlcCrosschainToken), false); + // } + + // function _test_SendToken_WhenOperational( + // IexecLayerZeroBridgeHarness iexecLayerZeroBridge, + // address tokenAddress, + // bool approvalRequired + // ) internal { + // IERC20 token = IERC20(tokenAddress); + + // // Check initial balances + // uint256 initialBalance = token.balanceOf(user1); + // assertEq(initialBalance, INITIAL_BALANCE, "Initial balance should match expected amount"); + + // // Prepare send parameters using utility + // (SendParam memory sendParam, MessagingFee memory fee) = TestUtils.prepareSend( + // iexecLayerZeroBridge, addressToBytes32(user2), TRANSFER_AMOUNT, approvalRequired ? DEST_EID : SOURCE_EID + // ); + + // // For approval flow, expect Transfer event from ERC20 token + // vm.expectEmit(true, true, true, true); + // emit IERC20.Transfer(user1, approvalRequired ? address(rlcLiquidityUnifier) : address(0), TRANSFER_AMOUNT); + + // if (!approvalRequired) { + // // For non-approval flow, expect CrosschainBurn event + // vm.expectEmit(true, true, true, true); + // emit IERC7802.CrosschainBurn(user1, TRANSFER_AMOUNT, address(iexecLayerZeroBridge)); + // } + + // // Expect OFTSent event from the bridge (this should be emitted by the parent OFT contract) + // vm.expectEmit(false, true, true, true); + // emit IOFT.OFTSent( + // bytes32(0), // ignore this value + // sendParam.dstEid, + // user1, + // TRANSFER_AMOUNT, + // TRANSFER_AMOUNT + // ); + + // // Send tokens + // vm.prank(user1); + // vm.deal(user1, fee.nativeFee); + // iexecLayerZeroBridge.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + + // // Verify source state - tokens should be burned/locked + // assertEq(token.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT, "Tokens should be deducted from sender"); + // } + + // // ============ LEVEL 1 PAUSE TESTS (Complete Pause) ============ + // function test_Pause_OnlyPauserRole() public { + // vm.expectRevert(); + // vm.prank(unauthorizedUser); + // iexecLayerZeroBridgeChainX.pause(); + // } + + // function test_Pause_BlocksAllTransfers() public { + // // TODO make check outbound and inbound transfers. + // // Pause the bridge + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pause(); + + // // Prepare send parameters + // (SendParam memory sendParam, MessagingFee memory fee) = + // TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); + + // // Attempt to send tokens - should revert + // vm.deal(user1, fee.nativeFee); + // vm.prank(user1); + // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + // iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + + // // Verify no tokens were burned + // assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); + // } + + // function test_Unpause_RestoresFullFunctionality() public { + // // Pause then unpause the bridge + // vm.startPrank(pauser); + // iexecLayerZeroBridgeChainX.pause(); + + // iexecLayerZeroBridgeChainX.unpause(); + // vm.stopPrank(); + + // // Should now work normally + // test_SendToken_WhenOperational_WithoutApproval(); + // } + + // function test_sendRLCWhenSourceLayerZeroBridgeUnpaused() public { + // // Pause then unpause the bridge + // vm.startPrank(pauser); + // iexecLayerZeroBridgeChainX.pause(); + // iexecLayerZeroBridgeChainX.unpause(); + // vm.stopPrank(); + + // // Prepare send parameters using utility + // (SendParam memory sendParam, MessagingFee memory fee) = + // TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); + + // // Send tokens + // vm.deal(user1, fee.nativeFee); + // vm.prank(user1); + // iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + + // // Verify source state - tokens should be burned + // assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT); + // } + + // // ============ LEVEL 2 PAUSE TESTS (Outbount transfer pause) ============ + + // function test_PauseOutboundTransfers_OnlyPauserRole() public { + // vm.expectRevert(); + // vm.prank(unauthorizedUser); + // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + // } + + // function test_PauseOutboundTransfers_BlocksOutboundOnly() public { + // // Pause outbound transfers + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + + // // Verify state + // assertFalse(iexecLayerZeroBridgeChainX.paused()); + // assertTrue(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); + + // // Prepare send parameters + // (SendParam memory sendParam, MessagingFee memory fee) = + // TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); + + // // Attempt to send tokens - should revert with EnforcedOutboundTransfersPause + // vm.deal(user1, fee.nativeFee); + // vm.prank(user1); + // vm.expectRevert(DualPausableUpgradeable.EnforcedOutboundTransfersPause.selector); + // iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + + // // Verify no tokens were burned + // assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); + // } + + // function test_unpauseOutboundTransfers_RestoresboundgoingTransfers() public { + // // Pause then unpause send + // vm.startPrank(pauser); + // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + + // iexecLayerZeroBridgeChainX.unpauseOutboundTransfers(); + // vm.stopPrank(); + + // // Should now work normally + // assertFalse(iexecLayerZeroBridgeChainX.paused()); + // assertFalse(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); + + // test_SendToken_WhenOperational_WithoutApproval(); + // } + + // // ============ token and approvalRequired ============ + // function test_ReturnsApprovalRequired_WithApproval() public view { + // assertEq(iexecLayerZeroBridgeEthereum.approvalRequired(), true, "approvalRequired() should return true"); + // } + + // function test_ReturnsApprovalRequired_WithoutApproval() public view { + // assertEq(iexecLayerZeroBridgeChainX.approvalRequired(), false, "approvalRequired() should return false"); + // } + + // function test_ReturnsBridgeableTokenAddress_WithApproval() public view { + // assertEq( + // iexecLayerZeroBridgeEthereum.token(), + // address(rlcToken), + // "token() should return the correct token contract address" + // ); + // } + + // function test_ReturnsBridgeableTokenAddress_WithoutApproval() public view { + // assertEq( + // iexecLayerZeroBridgeChainX.token(), + // address(rlcCrosschainToken), + // "token() should return the correct token contract address" + // ); + // } + + // // ============ _credit ============ + // function test_credit_SuccessfulMintToUser() public { + // // Test successful minting to a regular user address + // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // // Expect the Transfer & CrosschainMint event + // vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); + // emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + // vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); + // emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); + + // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + // assertEq( + // rlcCrosschainToken.balanceOf(user2), + // initialBalance + TRANSFER_AMOUNT, + // "User balance should increase by mint amount" + // ); + // } + + // function test_credit_SendToDeadAddressInsteadOfZeroAddress() public { + // // Fuzz test with different addresses including zero address + // // Handle zero address redirection + // uint256 initialBalance = rlcCrosschainToken.balanceOf(address(0xdead)); + // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), TRANSFER_AMOUNT, SOURCE_EID); + + // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + // assertEq( + // rlcCrosschainToken.balanceOf(address(0xdead)), + // initialBalance + TRANSFER_AMOUNT, + // "Actual recipient balance should increase" + // ); + + // assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); + // } function testFuzz_credit_Amount(uint256 amount) public { // Fuzz test with different amounts for testing edge case (0 & max RLC supply) - vm.assume(amount <= INITIAL_BALANCE); + uint256 totalSupply = 87_000_000 * 10 ** 9; // 87 million tokens with 9 decimals + vm.assume(amount <= totalSupply); + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, amount, SOURCE_EID); @@ -338,60 +340,60 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { ); } - function test_credit_RevertsWhenPaused() public { - // Test that _credit reverts when contract is fully paused - // Pause the contract - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pause(); + // function test_credit_RevertsWhenPaused() public { + // // Test that _credit reverts when contract is fully paused + // // Pause the contract + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pause(); - vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - } + // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + // iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + // } - function test_credit_WorksWhenOutboundTransfersPaused() public { - // Test that _credit still works when only sends are paused (Level 2 pause) - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + // function test_credit_WorksWhenOutboundTransfersPaused() public { + // // Test that _credit still works when only sends are paused (Level 2 pause) + // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - // Pause only sends - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + // // Pause only sends + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - vm.expectEmit(true, true, true, true); - emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); + // vm.expectEmit(true, true, true, true); + // emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + // vm.expectEmit(true, true, true, true); + // emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); - } + // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); + // } - function test_credit_WorksAfterUnpause() public { - // Test that _credit works after unpausing - uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + // function test_credit_WorksAfterUnpause() public { + // // Test that _credit works after unpausing + // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - // Pause the contract - vm.prank(pauser); - iexecLayerZeroBridgeChainX.pause(); + // // Pause the contract + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.pause(); - // Verify it's paused - vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + // // Verify it's paused + // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + // iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - // Unpause the contract - vm.prank(pauser); - iexecLayerZeroBridgeChainX.unpause(); + // // Unpause the contract + // vm.prank(pauser); + // iexecLayerZeroBridgeChainX.unpause(); - // Now it should work - vm.expectEmit(true, true, true, true); - emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - vm.expectEmit(true, true, true, true); - emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); + // // Now it should work + // vm.expectEmit(true, true, true, true); + // emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + // vm.expectEmit(true, true, true, true); + // emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); - uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); - } + // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); + // } } From c5836a959ff4c32e9ab5c0a1f4edee217902db90 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Tue, 8 Jul 2025 17:51:06 +0200 Subject: [PATCH 20/23] refactor: update variable names for IexecLayerZeroBridge to improve clarity --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 4 ++-- .../bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol | 2 +- test/units/utils/TestUtils.sol | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 02cdafd4..2937b35d 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -57,8 +57,8 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { }) ); - address iexecLayerZeroBridgeEthereumAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithApproval); - address iexecLayerZeroBridgeChainXAddress = address(deploymentResult.iexecLayerZeroBridgeChainWithoutApproval); + address iexecLayerZeroBridgeEthereumAddress = address(deploymentResult.iexecLayerZeroBridgeWithApproval); + address iexecLayerZeroBridgeChainXAddress = address(deploymentResult.iexecLayerZeroBridgeWithoutApproval); iexecLayerZeroBridgeEthereum = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeEthereumAddress); iexecLayerZeroBridgeChainX = IexecLayerZeroBridgeHarness(iexecLayerZeroBridgeChainXAddress); diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol index 0cb8b905..1b83f317 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridgeUpgrade.t.sol @@ -40,7 +40,7 @@ contract IexecLayerZeroBridgeUpgradeTest is TestHelperOz5 { }) ); - iexecLayerZeroBridgeV1 = deploymentResult1.iexecLayerZeroBridgeChainWithoutApproval; + iexecLayerZeroBridgeV1 = deploymentResult1.iexecLayerZeroBridgeWithoutApproval; rlcCrosschainToken = deploymentResult1.rlcCrosschainToken; proxyAddress = address(iexecLayerZeroBridgeV1); diff --git a/test/units/utils/TestUtils.sol b/test/units/utils/TestUtils.sol index d74e6b5f..b08dcd3d 100644 --- a/test/units/utils/TestUtils.sol +++ b/test/units/utils/TestUtils.sol @@ -29,8 +29,8 @@ library TestUtils { // Struct to hold deployment results struct DeploymentResult { - IexecLayerZeroBridge iexecLayerZeroBridgeChainWithApproval; - IexecLayerZeroBridge iexecLayerZeroBridgeChainWithoutApproval; + IexecLayerZeroBridge iexecLayerZeroBridgeWithApproval; + IexecLayerZeroBridge iexecLayerZeroBridgeWithoutApproval; RLCMock rlcToken; RLCCrosschainToken rlcCrosschainToken; RLCLiquidityUnifier rlcLiquidityUnifier; @@ -49,13 +49,13 @@ library TestUtils { result.rlcLiquidityUnifier = _deployLiquidityUnifier(params, result.rlcToken, createXFactory, salt); // Deploy IexecLayerZeroBridge for Sepolia - result.iexecLayerZeroBridgeChainWithApproval = + result.iexecLayerZeroBridgeWithApproval = _deployBridge(params, true, address(result.rlcLiquidityUnifier), createXFactory, salt); // Deploy RLC Crosschain token and Bridge for ChainX result.rlcCrosschainToken = _deployCrosschainToken(params, name, symbol, createXFactory, salt); - result.iexecLayerZeroBridgeChainWithoutApproval = + result.iexecLayerZeroBridgeWithoutApproval = _deployBridge(params, false, address(result.rlcCrosschainToken), createXFactory, salt); } From c57974a36d0580188c43d60f57641d799e0631b1 Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Wed, 9 Jul 2025 09:03:16 +0200 Subject: [PATCH 21/23] fix: uncomment --- .../layerZero/IexecLayerZeroBridge.t.sol | 364 +++++++++--------- 1 file changed, 182 insertions(+), 182 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 2937b35d..cf885f21 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -104,227 +104,227 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { //TODO: Add more tests for send functionality, in both directions // ============ BASIC BRIDGE FUNCTIONALITY TESTS ============ - // function test_SendToken_WhenOperational_WithApproval() public { - // vm.prank(user1); - // rlcToken.approve(address(iexecLayerZeroBridgeEthereum), TRANSFER_AMOUNT); - // _test_SendToken_WhenOperational(iexecLayerZeroBridgeEthereum, address(rlcToken), true); - // } + function test_SendToken_WhenOperational_WithApproval() public { + vm.prank(user1); + rlcToken.approve(address(iexecLayerZeroBridgeEthereum), TRANSFER_AMOUNT); + _test_SendToken_WhenOperational(iexecLayerZeroBridgeEthereum, address(rlcToken), true); + } - // function test_SendToken_WhenOperational_WithoutApproval() public { - // _test_SendToken_WhenOperational(iexecLayerZeroBridgeChainX, address(rlcCrosschainToken), false); - // } + function test_SendToken_WhenOperational_WithoutApproval() public { + _test_SendToken_WhenOperational(iexecLayerZeroBridgeChainX, address(rlcCrosschainToken), false); + } - // function _test_SendToken_WhenOperational( - // IexecLayerZeroBridgeHarness iexecLayerZeroBridge, - // address tokenAddress, - // bool approvalRequired - // ) internal { - // IERC20 token = IERC20(tokenAddress); + function _test_SendToken_WhenOperational( + IexecLayerZeroBridgeHarness iexecLayerZeroBridge, + address tokenAddress, + bool approvalRequired + ) internal { + IERC20 token = IERC20(tokenAddress); - // // Check initial balances - // uint256 initialBalance = token.balanceOf(user1); - // assertEq(initialBalance, INITIAL_BALANCE, "Initial balance should match expected amount"); + // Check initial balances + uint256 initialBalance = token.balanceOf(user1); + assertEq(initialBalance, INITIAL_BALANCE, "Initial balance should match expected amount"); - // // Prepare send parameters using utility - // (SendParam memory sendParam, MessagingFee memory fee) = TestUtils.prepareSend( - // iexecLayerZeroBridge, addressToBytes32(user2), TRANSFER_AMOUNT, approvalRequired ? DEST_EID : SOURCE_EID - // ); + // Prepare send parameters using utility + (SendParam memory sendParam, MessagingFee memory fee) = TestUtils.prepareSend( + iexecLayerZeroBridge, addressToBytes32(user2), TRANSFER_AMOUNT, approvalRequired ? DEST_EID : SOURCE_EID + ); - // // For approval flow, expect Transfer event from ERC20 token - // vm.expectEmit(true, true, true, true); - // emit IERC20.Transfer(user1, approvalRequired ? address(rlcLiquidityUnifier) : address(0), TRANSFER_AMOUNT); - - // if (!approvalRequired) { - // // For non-approval flow, expect CrosschainBurn event - // vm.expectEmit(true, true, true, true); - // emit IERC7802.CrosschainBurn(user1, TRANSFER_AMOUNT, address(iexecLayerZeroBridge)); - // } - - // // Expect OFTSent event from the bridge (this should be emitted by the parent OFT contract) - // vm.expectEmit(false, true, true, true); - // emit IOFT.OFTSent( - // bytes32(0), // ignore this value - // sendParam.dstEid, - // user1, - // TRANSFER_AMOUNT, - // TRANSFER_AMOUNT - // ); - - // // Send tokens - // vm.prank(user1); - // vm.deal(user1, fee.nativeFee); - // iexecLayerZeroBridge.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - - // // Verify source state - tokens should be burned/locked - // assertEq(token.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT, "Tokens should be deducted from sender"); - // } + // For approval flow, expect Transfer event from ERC20 token + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(user1, approvalRequired ? address(rlcLiquidityUnifier) : address(0), TRANSFER_AMOUNT); + + if (!approvalRequired) { + // For non-approval flow, expect CrosschainBurn event + vm.expectEmit(true, true, true, true); + emit IERC7802.CrosschainBurn(user1, TRANSFER_AMOUNT, address(iexecLayerZeroBridge)); + } + + // Expect OFTSent event from the bridge (this should be emitted by the parent OFT contract) + vm.expectEmit(false, true, true, true); + emit IOFT.OFTSent( + bytes32(0), // ignore this value + sendParam.dstEid, + user1, + TRANSFER_AMOUNT, + TRANSFER_AMOUNT + ); - // // ============ LEVEL 1 PAUSE TESTS (Complete Pause) ============ - // function test_Pause_OnlyPauserRole() public { - // vm.expectRevert(); - // vm.prank(unauthorizedUser); - // iexecLayerZeroBridgeChainX.pause(); - // } + // Send tokens + vm.prank(user1); + vm.deal(user1, fee.nativeFee); + iexecLayerZeroBridge.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - // function test_Pause_BlocksAllTransfers() public { - // // TODO make check outbound and inbound transfers. - // // Pause the bridge - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pause(); + // Verify source state - tokens should be burned/locked + assertEq(token.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT, "Tokens should be deducted from sender"); + } - // // Prepare send parameters - // (SendParam memory sendParam, MessagingFee memory fee) = - // TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); + // ============ LEVEL 1 PAUSE TESTS (Complete Pause) ============ + function test_Pause_OnlyPauserRole() public { + vm.expectRevert(); + vm.prank(unauthorizedUser); + iexecLayerZeroBridgeChainX.pause(); + } - // // Attempt to send tokens - should revert - // vm.deal(user1, fee.nativeFee); - // vm.prank(user1); - // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - // iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + function test_Pause_BlocksAllTransfers() public { + // TODO make check outbound and inbound transfers. + // Pause the bridge + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); - // // Verify no tokens were burned - // assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); - // } + // Prepare send parameters + (SendParam memory sendParam, MessagingFee memory fee) = + TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); - // function test_Unpause_RestoresFullFunctionality() public { - // // Pause then unpause the bridge - // vm.startPrank(pauser); - // iexecLayerZeroBridgeChainX.pause(); + // Attempt to send tokens - should revert + vm.deal(user1, fee.nativeFee); + vm.prank(user1); + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - // iexecLayerZeroBridgeChainX.unpause(); - // vm.stopPrank(); + // Verify no tokens were burned + assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); + } - // // Should now work normally - // test_SendToken_WhenOperational_WithoutApproval(); - // } + function test_Unpause_RestoresFullFunctionality() public { + // Pause then unpause the bridge + vm.startPrank(pauser); + iexecLayerZeroBridgeChainX.pause(); - // function test_sendRLCWhenSourceLayerZeroBridgeUnpaused() public { - // // Pause then unpause the bridge - // vm.startPrank(pauser); - // iexecLayerZeroBridgeChainX.pause(); - // iexecLayerZeroBridgeChainX.unpause(); - // vm.stopPrank(); + iexecLayerZeroBridgeChainX.unpause(); + vm.stopPrank(); - // // Prepare send parameters using utility - // (SendParam memory sendParam, MessagingFee memory fee) = - // TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); + // Should now work normally + test_SendToken_WhenOperational_WithoutApproval(); + } - // // Send tokens - // vm.deal(user1, fee.nativeFee); - // vm.prank(user1); - // iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + function test_sendRLCWhenSourceLayerZeroBridgeUnpaused() public { + // Pause then unpause the bridge + vm.startPrank(pauser); + iexecLayerZeroBridgeChainX.pause(); + iexecLayerZeroBridgeChainX.unpause(); + vm.stopPrank(); - // // Verify source state - tokens should be burned - // assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT); - // } + // Prepare send parameters using utility + (SendParam memory sendParam, MessagingFee memory fee) = + TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); - // // ============ LEVEL 2 PAUSE TESTS (Outbount transfer pause) ============ + // Send tokens + vm.deal(user1, fee.nativeFee); + vm.prank(user1); + iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - // function test_PauseOutboundTransfers_OnlyPauserRole() public { - // vm.expectRevert(); - // vm.prank(unauthorizedUser); - // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - // } + // Verify source state - tokens should be burned + assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE - TRANSFER_AMOUNT); + } - // function test_PauseOutboundTransfers_BlocksOutboundOnly() public { - // // Pause outbound transfers - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + // ============ LEVEL 2 PAUSE TESTS (Outbount transfer pause) ============ - // // Verify state - // assertFalse(iexecLayerZeroBridgeChainX.paused()); - // assertTrue(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); + function test_PauseOutboundTransfers_OnlyPauserRole() public { + vm.expectRevert(); + vm.prank(unauthorizedUser); + iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + } - // // Prepare send parameters - // (SendParam memory sendParam, MessagingFee memory fee) = - // TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); + function test_PauseOutboundTransfers_BlocksOutboundOnly() public { + // Pause outbound transfers + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - // // Attempt to send tokens - should revert with EnforcedOutboundTransfersPause - // vm.deal(user1, fee.nativeFee); - // vm.prank(user1); - // vm.expectRevert(DualPausableUpgradeable.EnforcedOutboundTransfersPause.selector); - // iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); + // Verify state + assertFalse(iexecLayerZeroBridgeChainX.paused()); + assertTrue(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); - // // Verify no tokens were burned - // assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); - // } + // Prepare send parameters + (SendParam memory sendParam, MessagingFee memory fee) = + TestUtils.prepareSend(iexecLayerZeroBridgeChainX, addressToBytes32(user2), TRANSFER_AMOUNT, SOURCE_EID); - // function test_unpauseOutboundTransfers_RestoresboundgoingTransfers() public { - // // Pause then unpause send - // vm.startPrank(pauser); - // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + // Attempt to send tokens - should revert with EnforcedOutboundTransfersPause + vm.deal(user1, fee.nativeFee); + vm.prank(user1); + vm.expectRevert(DualPausableUpgradeable.EnforcedOutboundTransfersPause.selector); + iexecLayerZeroBridgeChainX.send{value: fee.nativeFee}(sendParam, fee, payable(user1)); - // iexecLayerZeroBridgeChainX.unpauseOutboundTransfers(); - // vm.stopPrank(); + // Verify no tokens were burned + assertEq(rlcCrosschainToken.balanceOf(user1), INITIAL_BALANCE); + } - // // Should now work normally - // assertFalse(iexecLayerZeroBridgeChainX.paused()); - // assertFalse(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); + function test_unpauseOutboundTransfers_RestoresboundgoingTransfers() public { + // Pause then unpause send + vm.startPrank(pauser); + iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - // test_SendToken_WhenOperational_WithoutApproval(); - // } + iexecLayerZeroBridgeChainX.unpauseOutboundTransfers(); + vm.stopPrank(); - // // ============ token and approvalRequired ============ - // function test_ReturnsApprovalRequired_WithApproval() public view { - // assertEq(iexecLayerZeroBridgeEthereum.approvalRequired(), true, "approvalRequired() should return true"); - // } + // Should now work normally + assertFalse(iexecLayerZeroBridgeChainX.paused()); + assertFalse(iexecLayerZeroBridgeChainX.outbountTransfersPaused()); - // function test_ReturnsApprovalRequired_WithoutApproval() public view { - // assertEq(iexecLayerZeroBridgeChainX.approvalRequired(), false, "approvalRequired() should return false"); - // } + test_SendToken_WhenOperational_WithoutApproval(); + } - // function test_ReturnsBridgeableTokenAddress_WithApproval() public view { - // assertEq( - // iexecLayerZeroBridgeEthereum.token(), - // address(rlcToken), - // "token() should return the correct token contract address" - // ); - // } + // ============ token and approvalRequired ============ + function test_ReturnsApprovalRequired_WithApproval() public view { + assertEq(iexecLayerZeroBridgeEthereum.approvalRequired(), true, "approvalRequired() should return true"); + } - // function test_ReturnsBridgeableTokenAddress_WithoutApproval() public view { - // assertEq( - // iexecLayerZeroBridgeChainX.token(), - // address(rlcCrosschainToken), - // "token() should return the correct token contract address" - // ); - // } + function test_ReturnsApprovalRequired_WithoutApproval() public view { + assertEq(iexecLayerZeroBridgeChainX.approvalRequired(), false, "approvalRequired() should return false"); + } - // // ============ _credit ============ - // function test_credit_SuccessfulMintToUser() public { - // // Test successful minting to a regular user address - // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + function test_ReturnsBridgeableTokenAddress_WithApproval() public view { + assertEq( + iexecLayerZeroBridgeEthereum.token(), + address(rlcToken), + "token() should return the correct token contract address" + ); + } - // // Expect the Transfer & CrosschainMint event - // vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); - // emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - // vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); - // emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); + function test_ReturnsBridgeableTokenAddress_WithoutApproval() public view { + assertEq( + iexecLayerZeroBridgeChainX.token(), + address(rlcCrosschainToken), + "token() should return the correct token contract address" + ); + } - // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + // ============ _credit ============ + function test_credit_SuccessfulMintToUser() public { + // Test successful minting to a regular user address + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - // assertEq( - // rlcCrosschainToken.balanceOf(user2), - // initialBalance + TRANSFER_AMOUNT, - // "User balance should increase by mint amount" - // ); - // } + // Expect the Transfer & CrosschainMint event + vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + vm.expectEmit(true, true, true, true, address(rlcCrosschainToken)); + emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); - // function test_credit_SendToDeadAddressInsteadOfZeroAddress() public { - // // Fuzz test with different addresses including zero address - // // Handle zero address redirection - // uint256 initialBalance = rlcCrosschainToken.balanceOf(address(0xdead)); - // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), TRANSFER_AMOUNT, SOURCE_EID); + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - // assertEq( - // rlcCrosschainToken.balanceOf(address(0xdead)), - // initialBalance + TRANSFER_AMOUNT, - // "Actual recipient balance should increase" - // ); + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(user2), + initialBalance + TRANSFER_AMOUNT, + "User balance should increase by mint amount" + ); + } - // assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); - // } + function test_credit_SendToDeadAddressInsteadOfZeroAddress() public { + // Fuzz test with different addresses including zero address + // Handle zero address redirection + uint256 initialBalance = rlcCrosschainToken.balanceOf(address(0xdead)); + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq( + rlcCrosschainToken.balanceOf(address(0xdead)), + initialBalance + TRANSFER_AMOUNT, + "Actual recipient balance should increase" + ); + + assertEq(rlcCrosschainToken.balanceOf(address(0)), 0, "Zero address balance should remain zero"); + } function testFuzz_credit_Amount(uint256 amount) public { // Fuzz test with different amounts for testing edge case (0 & max RLC supply) From f076db0f2f4f72eee8079aec55b539a44c80c97b Mon Sep 17 00:00:00 2001 From: Le-Caignec Date: Wed, 9 Jul 2025 09:04:32 +0200 Subject: [PATCH 22/23] test: uncomment tests --- .../layerZero/IexecLayerZeroBridge.t.sol | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index cf885f21..232616a2 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -340,60 +340,60 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { ); } - // function test_credit_RevertsWhenPaused() public { - // // Test that _credit reverts when contract is fully paused - // // Pause the contract - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pause(); - - // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - // iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - // } - - // function test_credit_WorksWhenOutboundTransfersPaused() public { - // // Test that _credit still works when only sends are paused (Level 2 pause) - // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - - // // Pause only sends - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); - - // vm.expectEmit(true, true, true, true); - // emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - // vm.expectEmit(true, true, true, true); - // emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); - - // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - - // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); - // } - - // function test_credit_WorksAfterUnpause() public { - // // Test that _credit works after unpausing - // uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); - - // // Pause the contract - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.pause(); - - // // Verify it's paused - // vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); - // iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - - // // Unpause the contract - // vm.prank(pauser); - // iexecLayerZeroBridgeChainX.unpause(); - - // // Now it should work - // vm.expectEmit(true, true, true, true); - // emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); - // vm.expectEmit(true, true, true, true); - // emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); - - // uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); - - // assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); - // assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); - // } + function test_credit_RevertsWhenPaused() public { + // Test that _credit reverts when contract is fully paused + // Pause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); + + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + } + + function test_credit_WorksWhenOutboundTransfersPaused() public { + // Test that _credit still works when only sends are paused (Level 2 pause) + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // Pause only sends + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pauseOutboundTransfers(); + + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + vm.expectEmit(true, true, true, true); + emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); + } + + function test_credit_WorksAfterUnpause() public { + // Test that _credit works after unpausing + uint256 initialBalance = rlcCrosschainToken.balanceOf(user2); + + // Pause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.pause(); + + // Verify it's paused + vm.expectRevert(PausableUpgradeable.EnforcedPause.selector); + iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + // Unpause the contract + vm.prank(pauser); + iexecLayerZeroBridgeChainX.unpause(); + + // Now it should work + vm.expectEmit(true, true, true, true); + emit IERC20.Transfer(address(0), user2, TRANSFER_AMOUNT); + vm.expectEmit(true, true, true, true); + emit IERC7802.CrosschainMint(user2, TRANSFER_AMOUNT, address(iexecLayerZeroBridgeChainX)); + + uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(user2, TRANSFER_AMOUNT, SOURCE_EID); + + assertEq(amountReceived, TRANSFER_AMOUNT, "Amount received should equal mint amount"); + assertEq(rlcCrosschainToken.balanceOf(user2), initialBalance + TRANSFER_AMOUNT, "User balance should increase"); + } } From cda19cd3f8dfff97a6254f25361c2450422078c1 Mon Sep 17 00:00:00 2001 From: Zied Guesmi <26070035+zguesmi@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:54:03 +0200 Subject: [PATCH 23/23] Update test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol --- test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol index 232616a2..51dd7c59 100644 --- a/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol +++ b/test/units/bridges/layerZero/IexecLayerZeroBridge.t.sol @@ -311,8 +311,6 @@ contract IexecLayerZeroBridgeTest is TestHelperOz5 { } function test_credit_SendToDeadAddressInsteadOfZeroAddress() public { - // Fuzz test with different addresses including zero address - // Handle zero address redirection uint256 initialBalance = rlcCrosschainToken.balanceOf(address(0xdead)); uint256 amountReceived = iexecLayerZeroBridgeChainX.exposed_credit(address(0), TRANSFER_AMOUNT, SOURCE_EID);