-
Notifications
You must be signed in to change notification settings - Fork 185
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🧹 Added unit tests for MyONFT721.sol example (#964)
Co-authored-by: Radek Sienkiewicz <[email protected]>
- Loading branch information
Showing
16 changed files
with
469 additions
and
698 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.