// 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
}
}