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

🗞️ feat: add ONFT721Enumerable to onft-evm #1184

Open
wants to merge 1 commit 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/nervous-cars-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@layerzerolabs/onft-evm": minor
---

Added ONFT721Enumerable
310 changes: 232 additions & 78 deletions packages/onft-evm/artifacts/ONFT721Base.sol/ONFT721Base.json

Large diffs are not rendered by default.

65 changes: 65 additions & 0 deletions packages/onft-evm/contracts/onft721/ONFT721Enumerable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.22;

import { ERC721Enumerable, ERC721 } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

import { ONFT721Core } from "./ONFT721Core.sol";

/**
* @title ONFT721Enumerable Contract
* @dev ONFT721 is an ERC-721 token that extends the functionality of the ONFT721Core contract.
*/
abstract contract ONFT721Enumerable is ONFT721Core, ERC721Enumerable {
string internal baseTokenURI;

event BaseURISet(string baseURI);

/**
* @dev Constructor for the ONFT721 contract.
* @param _name The name of the ONFT.
* @param _symbol The symbol of the ONFT.
* @param _lzEndpoint The LayerZero endpoint address.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) ERC721(_name, _symbol) ONFT721Core(_lzEndpoint, _delegate) {}

/**
* @notice Retrieves the address of the underlying ERC721 implementation (ie. this contract).
*/
function token() external view returns (address) {
return address(this);
}

function setBaseURI(string calldata _baseTokenURI) external onlyOwner {
baseTokenURI = _baseTokenURI;
emit BaseURISet(baseTokenURI);
}

function _baseURI() internal view override returns (string memory) {
return baseTokenURI;
}

/**
* @notice Indicates whether the ONFT721 contract requires approval of the 'token()' to send.
* @dev In the case of ONFT where the contract IS the token, approval is NOT required.
* @return requiresApproval Needs approval of the underlying token implementation.
*/
function approvalRequired() external pure virtual returns (bool) {
return false;
}

function _debit(address _from, uint256 _tokenId, uint32 /*_dstEid*/) internal virtual override {
if (_from != ERC721.ownerOf(_tokenId)) revert OnlyNFTOwner(_from, ERC721.ownerOf(_tokenId));
_burn(_tokenId);
}

function _credit(address _to, uint256 _tokenId, uint32 /*_srcEid*/) internal virtual override {
_mint(_to, _tokenId);
}
}
5 changes: 4 additions & 1 deletion packages/onft-evm/test/onft/ONFTBaseTestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ abstract contract ONFTBaseTestHelper is TestHelperOz5 {
uint32 internal constant A_EID = 1;
uint32 internal constant B_EID = 2;
uint32 internal constant C_EID = 3;
uint8 internal constant NUM_ENDPOINTS = 3;
uint32 internal constant D_EID = 4;
uint8 internal constant NUM_ENDPOINTS = 4;

address internal alice = makeAddr("alice");
address internal bob = makeAddr("bob");
address internal charlie = makeAddr("charlie");
address internal david = makeAddr("david");

function setUp() public virtual override {
super.setUp();
Expand All @@ -26,6 +28,7 @@ abstract contract ONFTBaseTestHelper is TestHelperOz5 {
vm.deal(alice, INITIAL_NATIVE_BALANCE);
vm.deal(bob, INITIAL_NATIVE_BALANCE);
vm.deal(charlie, INITIAL_NATIVE_BALANCE);
vm.deal(david, INITIAL_NATIVE_BALANCE);
}

function sliceUintArray(uint[] memory array, uint start, uint end) public pure returns (uint[] memory) {
Expand Down
152 changes: 89 additions & 63 deletions packages/onft-evm/test/onft/onft721/ONFT721.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ import { SendParam } from "../../../contracts/onft721/interfaces/IONFT721.sol";

import { ONFT721Mock } from "./mocks/ONFT721Mock.sol";
import { ONFT721AdapterMock } from "./mocks/ONFT721AdapterMock.sol";
import { ONFT721EnumerableMock } from "./mocks/ONFT721EnumerableMock.sol";
import { ONFT721Base } from "./ONFT721Base.sol";
// Forge imports
import "forge-std/console.sol";

contract ONFT721Test is ONFT721Base {
using OptionsBuilder for bytes;
Expand All @@ -34,20 +37,24 @@ contract ONFT721Test is ONFT721Base {
assertEq(aONFT.owner(), address(this));
assertEq(bONFT.owner(), address(this));
assertEq(cONFTAdapter.owner(), address(this));
assertEq(dONFTEnumerable.owner(), address(this));

assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(dONFTEnumerable.token()).balanceOf(david), DEFAULT_INITIAL_ONFTS_PER_EID);

assertEq(aONFT.token(), address(aONFT));
assertEq(bONFT.token(), address(bONFT));
assertEq(cONFTAdapter.token(), address(cERC721Mock));
assertEq(dONFTEnumerable.token(), address(dONFTEnumerable));
}

function test_approvalRequired() public {
assertFalse(aONFT.approvalRequired());
assertFalse(bONFT.approvalRequired());
assertTrue(cONFTAdapter.approvalRequired());
assertFalse(dONFTEnumerable.approvalRequired());
}

function test_onftVersion() public {
Expand All @@ -58,66 +65,85 @@ contract ONFT721Test is ONFT721Base {
}

function test_send(uint16 _tokenToSend) public {
// 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter
vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3);

// 2. Set enforced options for SEND
_setMeshDefaultEnforcedSendOption();
// 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter
vm.assume(_tokenToSend >= 256 * 3 && _tokenToSend < 256 * 4);

// 2. Set enforced options for SEND
_setMeshDefaultEnforcedSendOption();

// 3. Sanity check token balances and _tokenToSend ownership
assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(dONFTEnumerable.balanceOf(david), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).ownerOf(_tokenToSend), charlie);

// 4. Send the same ONFT in a circle 10 times.
// a) C->D
// b) D->A
// c) A->B
// d) B->C
for (uint8 i = 0; i < 10; i++) {
vm.startPrank(charlie);
IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), _tokenToSend);
vm.stopPrank();
_sendAndCheck(
_tokenToSend,
C_EID,
D_EID,
charlie,
david,
DEFAULT_INITIAL_ONFTS_PER_EID,
DEFAULT_INITIAL_ONFTS_PER_EID,
true,
false
);
console.log("C->D success");
_sendAndCheck(
_tokenToSend,
D_EID,
A_EID,
david,
alice,
DEFAULT_INITIAL_ONFTS_PER_EID + 1,
DEFAULT_INITIAL_ONFTS_PER_EID,
false,
false
);
console.log("D->A success");
_sendAndCheck(
_tokenToSend,
A_EID,
B_EID,
alice,
bob,
DEFAULT_INITIAL_ONFTS_PER_EID + 1,
DEFAULT_INITIAL_ONFTS_PER_EID,
false,
false
);
console.log("A->B success");
_sendAndCheck(
_tokenToSend,
B_EID,
C_EID,
bob,
charlie,
DEFAULT_INITIAL_ONFTS_PER_EID + 1,
DEFAULT_INITIAL_ONFTS_PER_EID - 1,
false,
true
);
console.log("B->C success");
}

// 3. Sanity check token balances and _tokenToSend ownership
assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).ownerOf(_tokenToSend), charlie);

// 4. Send the same ONFT in a circle 10 times.
// a) C->A
// b) A->B
// c) B->C
for (uint8 i = 0; i < 10; i++) {
vm.startPrank(charlie);
IERC721(cONFTAdapter.token()).approve(address(cONFTAdapter), _tokenToSend);
vm.stopPrank();
_sendAndCheck(
_tokenToSend,
C_EID,
A_EID,
charlie,
alice,
DEFAULT_INITIAL_ONFTS_PER_EID,
DEFAULT_INITIAL_ONFTS_PER_EID,
true,
false
);
_sendAndCheck(
_tokenToSend,
A_EID,
B_EID,
alice,
bob,
DEFAULT_INITIAL_ONFTS_PER_EID + 1,
DEFAULT_INITIAL_ONFTS_PER_EID,
false,
false
);
_sendAndCheck(
_tokenToSend,
B_EID,
C_EID,
bob,
charlie,
DEFAULT_INITIAL_ONFTS_PER_EID + 1,
DEFAULT_INITIAL_ONFTS_PER_EID - 1,
false,
true
);
}
// 5. Check the final balances
assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(dONFTEnumerable.balanceOf(david), DEFAULT_INITIAL_ONFTS_PER_EID);
}

// 5. Check the final balances
assertEq(aONFT.balanceOf(alice), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(bONFT.balanceOf(bob), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(IERC721(cONFTAdapter.token()).balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
}

/// @dev Test to ensure that the quoteSend function reverts when the receiver is invalid.
function test_quoteSend_InvalidReceiver(uint16 _tokenToSend) public {
Expand All @@ -135,7 +161,7 @@ contract ONFT721Test is ONFT721Base {
/// @dev Test to ensure that the send function reverts when the receiver is invalid.
function test_send_InvalidReceiver(uint16 _tokenToSend) public {
// 1. Assume that the token is owned by charlie on C_EID ONFT721Adapter
vm.assume(_tokenToSend >= 256 * 2 && _tokenToSend < 256 * 3);
vm.assume(_tokenToSend >= 256 * 3 && _tokenToSend < 256 * 4);

// 2. Set enforced options for SEND
_setMeshDefaultEnforcedSendOption();
Expand Down Expand Up @@ -259,14 +285,14 @@ contract ONFT721Test is ONFT721Base {

function test_ONFTAdapter_debitAndCredit(uint16 _tokenId) public {
// Ensure that the tokenId is owned by userC
vm.assume(_tokenId >= DEFAULT_INITIAL_ONFTS_PER_EID * 2 && _tokenId < DEFAULT_INITIAL_ONFTS_PER_EID * 3);
vm.assume(_tokenId >= DEFAULT_INITIAL_ONFTS_PER_EID * 3 && _tokenId < DEFAULT_INITIAL_ONFTS_PER_EID * 4);
vm.assume(cERC721Mock.ownerOf(_tokenId) == charlie);

uint32 dstEid = C_EID;
uint32 srcEid = C_EID;

assertEq(cERC721Mock.balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID);
assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2);
assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 3);

vm.prank(charlie);
cERC721Mock.approve(address(cONFTAdapter), _tokenId);
Expand All @@ -278,7 +304,7 @@ contract ONFT721Test is ONFT721Base {
// 2. The Adapter balance is incremented by 1.
// 3. The Adapter is the owner of the token
assertEq(cERC721Mock.balanceOf(charlie), DEFAULT_INITIAL_ONFTS_PER_EID - 1);
assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2 + 1);
assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 3 + 1);
assertEq(cERC721Mock.ownerOf(_tokenId), address(cONFTAdapter));

vm.prank(charlie);
Expand All @@ -289,7 +315,7 @@ contract ONFT721Test is ONFT721Base {
// 2. The Adapter balance is decremented by 1.
// 3. userB owns the token
assertEq(cERC721Mock.balanceOf(address(bob)), 1);
assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 2);
assertEq(cERC721Mock.balanceOf(address(cONFTAdapter)), DEFAULT_INITIAL_ONFTS_PER_EID * 3);
assertEq(cERC721Mock.ownerOf(_tokenId), bob);
}

Expand Down
Loading
Loading