Skip to content

Commit

Permalink
🧹 Added unit tests for MyONFT721.sol example (#964)
Browse files Browse the repository at this point in the history
Co-authored-by: Radek Sienkiewicz <[email protected]>
  • Loading branch information
EWCunha and Radek Sienkiewicz authored Nov 7, 2024
1 parent b9778dc commit 4d35182
Show file tree
Hide file tree
Showing 16 changed files with 469 additions and 698 deletions.
5 changes: 5 additions & 0 deletions .changeset/honest-rats-cry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@layerzerolabs/onft721-example": patch
---

Added unit tests for `MyONFT721.sol` example
4 changes: 2 additions & 2 deletions examples/oft/test/foundry/MyOFT.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ contract MyOFTTest is TestHelperOz5 {
OFTMock private aOFT;
OFTMock private bOFT;

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

function setUp() public virtual override {
Expand Down
2 changes: 1 addition & 1 deletion examples/onft721/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"lint:fix": "eslint --fix '**/*.{js,ts,json}' && prettier --write . && solhint 'contracts/**/*.sol' --fix --noPrompt",
"lint:js": "eslint '**/*.{js,ts,json}' && prettier --check .",
"lint:sol": "solhint 'contracts/**/*.sol'",
"test": "echo 'No tests yet'",
"test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat",
"test:forge": "forge test",
"test:hardhat": "hardhat test"
},
Expand Down
152 changes: 152 additions & 0 deletions examples/onft721/test/foundry/MyONFT721.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.20;

// Mock imports
import { ONFT721Mock } from "../mocks/ONFT721Mock.sol";
import { ONFT721ComposerMock } from "../mocks/ONFT721ComposerMock.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 { IONFT721, SendParam } from "@layerzerolabs/onft-evm/contracts/onft721/interfaces/IONFT721.sol";
import { MessagingFee, MessagingReceipt } from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721Core.sol";
import { ONFT721MsgCodec } from "@layerzerolabs/onft-evm/contracts/onft721/libs/ONFT721MsgCodec.sol";
import { ONFTComposeMsgCodec } from "@layerzerolabs/onft-evm/contracts/libs/ONFTComposeMsgCodec.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 MyONFT721Test is TestHelperOz5 {
using OptionsBuilder for bytes;

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

ONFT721Mock private aONFT721;
ONFT721Mock private bONFT721;

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

function setUp() public virtual override {
vm.deal(userA, 1000 ether);
vm.deal(userB, 1000 ether);

super.setUp();
setUpEndpoints(2, LibraryType.UltraLightNode);

aONFT721 = ONFT721Mock(
_deployOApp(
type(ONFT721Mock).creationCode,
abi.encode("aONFT721", "aONFT721", address(endpoints[aEid]), address(this))
)
);

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

// config and wire the onfts
address[] memory onfts = new address[](2);
onfts[0] = address(aONFT721);
onfts[1] = address(bONFT721);
this.wireOApps(onfts);

// mint tokens
aONFT721.mint(userA, 0);
}

function test_constructor() public {
assertEq(aONFT721.owner(), address(this));
assertEq(bONFT721.owner(), address(this));

assertEq(aONFT721.balanceOf(userA), 1);
assertEq(bONFT721.balanceOf(userB), 0);

assertEq(aONFT721.token(), address(aONFT721));
assertEq(bONFT721.token(), address(bONFT721));
}

function test_send_onft721() public {
uint256 tokenId = 0;
bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0);
SendParam memory sendParam = SendParam(bEid, addressToBytes32(userB), tokenId, options, "", "");
MessagingFee memory fee = aONFT721.quoteSend(sendParam, false);

assertEq(aONFT721.balanceOf(userA), 1);
assertEq(bONFT721.balanceOf(userB), 0);

vm.prank(userA);
aONFT721.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this)));
verifyPackets(bEid, addressToBytes32(address(bONFT721)));

assertEq(aONFT721.balanceOf(userA), 0);
assertEq(bONFT721.balanceOf(userB), 1);
}

function test_send_oft_compose_msg() public {
uint256 tokenIdToSend = 0;

ONFT721ComposerMock composer = new ONFT721ComposerMock();

bytes memory options = OptionsBuilder
.newOptions()
.addExecutorLzReceiveOption(200000, 0)
.addExecutorLzComposeOption(0, 500000, 0);
bytes memory composeMsg = hex"1234";
SendParam memory sendParam = SendParam(
bEid,
addressToBytes32(address(composer)),
tokenIdToSend,
options,
composeMsg,
""
);
MessagingFee memory fee = aONFT721.quoteSend(sendParam, false);

assertEq(aONFT721.balanceOf(userA), 1);
assertEq(bONFT721.balanceOf(address(composer)), 0);

vm.prank(userA);
MessagingReceipt memory msgReceipt = aONFT721.send{ value: fee.nativeFee }(
sendParam,
fee,
payable(address(this))
);
verifyPackets(bEid, addressToBytes32(address(bONFT721)));

// lzCompose params
uint32 dstEid_ = bEid;
address from_ = address(bONFT721);
bytes memory options_ = options;
bytes32 guid_ = msgReceipt.guid;
address to_ = address(composer);
bytes memory composerMsg_ = ONFTComposeMsgCodec.encode(
msgReceipt.nonce,
aEid,
abi.encodePacked(addressToBytes32(userA), composeMsg)
);
this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_);

assertEq(aONFT721.balanceOf(userA), 0);
assertEq(bONFT721.balanceOf(address(composer)), 1);

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
}
}
92 changes: 92 additions & 0 deletions examples/onft721/test/hardhat/MyONFT721.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
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('MyONFT721 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 MyONFT721: ContractFactory
let EndpointV2Mock: ContractFactory
let ownerA: SignerWithAddress
let ownerB: SignerWithAddress
let endpointOwner: SignerWithAddress
let myONFT721A: Contract
let myONFT721B: Contract
let mockEndpointV2A: Contract
let mockEndpointV2B: Contract

// Before hook for setup that runs once before all tests in the block
before(async function () {
// Contract factory for our tested contract
//
// We are using a derived contract that exposes a mint() function for testing purposes
MyONFT721 = await ethers.getContractFactory('MyONFT721Mock')

// 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
mockEndpointV2A = await EndpointV2Mock.deploy(eidA)
mockEndpointV2B = await EndpointV2Mock.deploy(eidB)

// Deploying two instances of MyOFT contract with different identifiers and linking them to the mock LZEndpoint
myONFT721A = await MyONFT721.deploy('aONFT721', 'aONFT721', mockEndpointV2A.address, ownerA.address)
myONFT721B = await MyONFT721.deploy('bONFT721', 'bONFT721', mockEndpointV2B.address, ownerB.address)

// Setting destination endpoints in the LZEndpoint mock for each MyONFT721 instance
await mockEndpointV2A.setDestLzEndpoint(myONFT721B.address, mockEndpointV2B.address)
await mockEndpointV2B.setDestLzEndpoint(myONFT721A.address, mockEndpointV2A.address)

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

// A test case to verify token transfer functionality
it('should send a token from A address to B address', async function () {
// Minting an initial amount of tokens to ownerA's address in the myONFT721A contract
const initialTokenId = 0
await myONFT721A.mint(ownerA.address, initialTokenId)

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

const sendParam = [eidB, ethers.utils.zeroPad(ownerB.address, 32), initialTokenId, options, '0x', '0x']

// Fetching the native fee for the token send operation
const [nativeFee] = await myONFT721A.quoteSend(sendParam, false)

// Executing the send operation from myONFT721A contract
await myONFT721A.send(sendParam, [nativeFee, 0], ownerA.address, { value: nativeFee })

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

// Asserting that the final balances are as expected after the send operation
expect(finalBalanceA).eql(ethers.BigNumber.from(0))
expect(finalBalanceB).eql(ethers.BigNumber.from(1))
})
})
27 changes: 27 additions & 0 deletions examples/onft721/test/mocks/ONFT721ComposerMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { IOAppComposer } from "@layerzerolabs/oapp-evm/contracts/oapp/interfaces/IOAppComposer.sol";

contract ONFT721ComposerMock is IOAppComposer {
// default empty values for testing a lzCompose received message
address public from;
bytes32 public guid;
bytes public message;
address public executor;
bytes public extraData;

function lzCompose(
address _from,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata /*_extraData*/
) external payable {
from = _from;
guid = _guid;
message = _message;
executor = _executor;
extraData = _message;
}
}
33 changes: 33 additions & 0 deletions examples/onft721/test/mocks/ONFT721Mock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.0;

import { ONFT721 } from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721.sol";
import { SendParam } from "@layerzerolabs/onft-evm/contracts/onft721/interfaces/IONFT721.sol";

contract ONFT721Mock is ONFT721 {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {}

function mint(address _to, uint256 _tokenId) public {
_mint(_to, _tokenId);
}

// @dev expose internal functions for testing purposes
function debit(uint256 _tokenId, uint32 _dstEid) public {
_debit(msg.sender, _tokenId, _dstEid);
}

function credit(address _to, uint256 _tokenId, uint32 _srcEid) public {
_credit(_to, _tokenId, _srcEid);
}

function buildMsgAndOptions(
SendParam calldata _sendParam
) public view returns (bytes memory message, bytes memory options) {
return _buildMsgAndOptions(_sendParam);
}
}
Loading

0 comments on commit 4d35182

Please sign in to comment.