Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🧹 Added unit tests for MyONFT721.sol example #964

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
Copy link
Contributor

Choose a reason for hiding this comment

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

These tests are not run. You need to modify the "test" script in package.json to:

"test": "$npm_execpath run test:forge && $npm_execpath run test:hardhat",

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 {
Copy link
Contributor

Choose a reason for hiding this comment

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

using OptionsBuilder for bytes;

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

ONFT721Mock private aONFT721;
ONFT721Mock private bONFT721;

address private userA = address(0x1);
Copy link
Contributor

Choose a reason for hiding this comment

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

Use makeAddr("userA") and makeAddr("userB") instead. This will enhance the error logs by replacing the address with the canonical name.

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