> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing LayerZero Contracts

> Test LayerZero contracts with Hardhat and Foundry. Use EndpointV2Mock and TestHelper for simulating crosschain message passing. Step-by-step instructions fo...

The LayerZero sample project supports unit testing using both the hardhat and foundry forge development framework, with specific test helpers for each:

* `EndpointV2Mock.sol`: a mock LayerZero V2 Endpoint contract, meant for local testing of LayerZero message passing.

* `TestHelper.sol`: an extensive LayerZero V2 testing framework, designed for simulating LayerZero state changes and contract interactions.

To run your unit tests for both Hardhat and Foundry, run the `test` command using your package manager:

```bash wrap theme={null}
pnpm test
```

### Example Hardhat Test

```typescript wrap theme={null}
import {SignerWithAddress} from '@nomiclabs/hardhat-ethers/signers';
import {expect} from 'chai';
import {Contract, ContractFactory} from 'ethers';
import {deployments, ethers} from 'hardhat';

import {Options} from '@layerzerolabs/lz-v2-utilities';

describe('MyOFT Test', function () {
  // Constant representing a mock Endpoint ID for testing purposes
  const eidA = 1;
  const eidB = 2;
  // Declaration of variables to be used in the test suite
  let MyOFT: ContractFactory;
  let EndpointV2Mock: ContractFactory;
  let ownerA: SignerWithAddress;
  let ownerB: SignerWithAddress;
  let endpointOwner: SignerWithAddress;
  let myOFTA: Contract;
  let myOFTB: Contract;
  let mockEndpointA: Contract;
  let mockEndpointB: Contract;

  // Before hook for setup that runs once before all tests in the block
  before(async function () {
    // Contract factory for our tested contract
    MyOFT = await ethers.getContractFactory('MyOFT');

    // Fetching the first three signers (accounts) from Hardhat's local Ethereum network
    const signers = await ethers.getSigners();

    ownerA = signers.at(0)!;
    ownerB = signers.at(1)!;
    endpointOwner = signers.at(2)!;

    // The EndpointV2Mock contract comes from @layerzerolabs/test-devtools-evm-hardhat package
    // and its artifacts are connected as external artifacts to this project
    //
    // Unfortunately, hardhat itself does not yet provide a way of connecting external artifacts
    // so we rely on hardhat-deploy to create a ContractFactory for EndpointV2Mock
    //
    // See https://github.com/NomicFoundation/hardhat/issues/1040
    const EndpointV2MockArtifact = await deployments.getArtifact('EndpointV2Mock');
    EndpointV2Mock = new ContractFactory(
      EndpointV2MockArtifact.abi,
      EndpointV2MockArtifact.bytecode,
      endpointOwner,
    );
  });

  // beforeEach hook for setup that runs before each test in the block
  beforeEach(async function () {
    // Deploying a mock LZEndpoint with the given Endpoint ID
    mockEndpointA = await EndpointV2Mock.deploy(eidA);
    mockEndpointB = await EndpointV2Mock.deploy(eidB);

    // Deploying two instances of MyOFT contract with different identifiers and linking them to the mock LZEndpoint
    myOFTA = await MyOFT.deploy('aOFT', 'aOFT', mockEndpointA.address, ownerA.address);
    myOFTB = await MyOFT.deploy('bOFT', 'bOFT', mockEndpointB.address, ownerB.address);

    // Setting destination endpoints in the LZEndpoint mock for each MyOFT instance
    await mockEndpointA.setDestLzEndpoint(myOFTB.address, mockEndpointB.address);
    await mockEndpointB.setDestLzEndpoint(myOFTA.address, mockEndpointA.address);

    // Setting each MyOFT instance as a peer of the other in the mock LZEndpoint
    await myOFTA.connect(ownerA).setPeer(eidB, ethers.utils.zeroPad(myOFTB.address, 32));
    await myOFTB.connect(ownerB).setPeer(eidA, ethers.utils.zeroPad(myOFTA.address, 32));
  });

  // A test case to verify token transfer functionality
  it('should send a token from A address to B address via each OFT', async function () {
    // Minting an initial amount of tokens to ownerA's address in the myOFTA contract
    const initialAmount = ethers.utils.parseEther('100');
    await myOFTA.mint(ownerA.address, initialAmount);

    // Defining the amount of tokens to send and constructing the parameters for the send operation
    const tokensToSend = ethers.utils.parseEther('1');
    const sendParam = [eidB, ethers.utils.zeroPad(ownerB.address, 32), tokensToSend, tokensToSend];

    // Defining extra message execution options for the send operation
    const options = Options.newOptions().addExecutorLzReceiveOption(200000, 0).toHex().toString();

    // Fetching the native fee for the token send operation
    const [nativeFee] = await myOFTA.quoteSend(sendParam, options, false, `0x`, `0x`);

    // Executing the send operation from myOFTA contract
    await myOFTA.send(sendParam, options, [nativeFee, 0], ownerA.address, '0x', '0x', {
      value: nativeFee,
    });

    // Fetching the final token balances of ownerA and ownerB
    const finalBalanceA = await myOFTA.balanceOf(ownerA.address);
    const finalBalanceB = await myOFTB.balanceOf(ownerB.address);

    // Asserting that the final balances are as expected after the send operation
    expect(finalBalanceA.eq(initialAmount.sub(tokensToSend))).to.be.true;
    expect(finalBalanceB.eq(tokensToSend)).to.be.true;
  });
});
```

### Example Foundry Test

```solidity wrap theme={null}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

// Mock imports
import { OFTMock } from "../mocks/OFTMock.sol";
import { ERC20Mock } from "../mocks/ERC20Mock.sol";
import { OFTComposerMock } from "../mocks/OFTComposerMock.sol";

// OApp imports
import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol";
import { OptionsBuilder } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OptionsBuilder.sol";

// OFT imports
import { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/oft-evm/contracts/interfaces/IOFT.sol";
import { MessagingFee, MessagingReceipt } from "@layerzerolabs/oft-evm/contracts/OFTCore.sol";
import { OFTMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTMsgCodec.sol";
import { OFTComposeMsgCodec } from "@layerzerolabs/oft-evm/contracts/libs/OFTComposeMsgCodec.sol";

// OZ imports
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

// Forge imports
import "forge-std/console.sol";

// DevTools imports
import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol";

contract MyOFTTest is TestHelperOz5 {
    using OptionsBuilder for bytes;

    uint32 private aEid = 1;
    uint32 private bEid = 2;

    OFTMock private aOFT;
    OFTMock private bOFT;

    address private userA = address(0x1);
    address private userB = address(0x2);
    uint256 private initialBalance = 100 ether;

    function setUp() public virtual override {
        // Provide initial Ether balances to users for testing purposes
        vm.deal(userA, 1000 ether);
        vm.deal(userB, 1000 ether);

        // Call the base setup function from the TestHelperOz5 contract
        super.setUp();

        // Initialize 2 endpoints, using UltraLightNode as the library type
        setUpEndpoints(2, LibraryType.UltraLightNode);

        // Deploy two instances of OFTMock for testing, associating them with respective endpoints
        aOFT = OFTMock(
            _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this)))
        );

        bOFT = OFTMock(
            _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this)))
        );

        // Configure and wire the OFTs together
        address[] memory ofts = new address[](2);
        ofts[0] = address(aOFT);
        ofts[1] = address(bOFT);
        this.wireOApps(ofts);

        // Mint initial tokens for userA and userB
        aOFT.mint(userA, initialBalance);
        bOFT.mint(userB, initialBalance);
    }

    // Test the constructor to ensure initial setup and state are correct
    function test_constructor() public {
        // Check that the contract owner is correctly set
        assertEq(aOFT.owner(), address(this));
        assertEq(bOFT.owner(), address(this));

        // Verify initial token balances for userA and userB
        assertEq(aOFT.balanceOf(userA), initialBalance);
        assertEq(bOFT.balanceOf(userB), initialBalance);

        // Verify that the token address is correctly set to the respective OFT instances
        assertEq(aOFT.token(), address(aOFT));
        assertEq(bOFT.token(), address(bOFT));
    }

    // Test sending OFT tokens from one user to another
    function test_send_oft() public {
        uint256 tokensToSend = 1 ether;

        // Build options for the send operation
        bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);

        // Set up parameters for the send operation
        SendParam memory sendParam = SendParam(
            bEid,
            addressToBytes32(userB),
            tokensToSend,
            tokensToSend,
            options,
            "",
            ""
        );

        // Quote the fee for sending tokens
        MessagingFee memory fee = aOFT.quoteSend(sendParam, false);

        // Verify initial balances before the send operation
        assertEq(aOFT.balanceOf(userA), initialBalance);
        assertEq(bOFT.balanceOf(userB), initialBalance);

        // Perform the send operation
        vm.prank(userA);
        aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));

        // Verify that the packets were correctly sent to the destination chain.
        // @param _dstEid The endpoint ID of the destination chain.
        // @param _dstAddress The OApp address on the destination chain.
        verifyPackets(bEid, addressToBytes32(address(bOFT)));

        // Check balances after the send operation
        assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend);
        assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend);
    }

    // Test sending OFT tokens with a composed message
    function test_send_oft_compose_msg() public {
        uint256 tokensToSend = 1 ether;

        // Create an instance of the OFTComposerMock contract
        OFTComposerMock composer = new OFTComposerMock();

        // Build options for the send operation with a composed message
        bytes memory options = OptionsBuilder
            .newOptions()
            .addExecutorLzReceiveOption(200000, 0)
            .addExecutorLzComposeOption(0, 500000, 0);
        bytes memory composeMsg = hex"1234";

        // Set up parameters for the send operation
        SendParam memory sendParam = SendParam(
            bEid,
            addressToBytes32(address(composer)),
            tokensToSend,
            tokensToSend,
            options,
            composeMsg,
            ""
        );

        // Quote the fee for sending tokens
        MessagingFee memory fee = aOFT.quoteSend(sendParam, false);

        // Verify initial balances before the send operation
        assertEq(aOFT.balanceOf(userA), initialBalance);
        assertEq(bOFT.balanceOf(address(composer)), 0);

        // Perform the send operation
        vm.prank(userA);
        (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send{ value: fee.nativeFee }(
            sendParam,
            fee,
            payable(address(this))
        );

        // Verify that the packets were correctly sent to the destination chain.
        // @param _dstEid The endpoint ID of the destination chain.
        // @param _dstAddress The OApp address on the destination chain.
        verifyPackets(bEid, addressToBytes32(address(bOFT)));

        // Set up parameters for the composed message
        uint32 dstEid_ = bEid;
        address from_ = address(bOFT);
        bytes memory options_ = options;
        bytes32 guid_ = msgReceipt.guid;
        address to_ = address(composer);
        bytes memory composerMsg_ = OFTComposeMsgCodec.encode(
            msgReceipt.nonce,
            aEid,
            oftReceipt.amountReceivedLD,
            abi.encodePacked(addressToBytes32(userA), composeMsg)
        );

        // Execute the composed message
        this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_);

        // Check balances after the send operation
        assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend);
        assertEq(bOFT.balanceOf(address(composer)), tokensToSend);

        // Verify the state of the composer contract
        assertEq(composer.from(), from_);
        assertEq(composer.guid(), guid_);
        assertEq(composer.message(), composerMsg_);
        assertEq(composer.executor(), address(this));
        assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test
    }
}
```
