From 8bc5d5f793e0307ad500dc1f83b70d870c1d82a9 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 11 Jan 2025 10:48:22 +0700 Subject: [PATCH 01/53] chore: change file name to 7858 Signed-off-by: MASDXI --- contracts/tokens/ERC721/ERC721EXPBase.sol | 10 +++++----- .../extensions/{IERC5007Mod.sol => IERC7858.sol} | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) rename contracts/tokens/ERC721/extensions/{IERC5007Mod.sol => IERC7858.sol} (92%) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index abe175be..b040017c 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -5,11 +5,11 @@ pragma solidity >=0.8.0 <0.9.0; /// @dev ERC721EXP Base contract each token have individual expiration date. /// @author Kiwari Labs -import {IERC5007Mod} from "./extensions/IERC5007Mod.sol"; +import {IERC7858} from "./extensions/IERC7858.sol"; import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -abstract contract ERC721EXPBase is ERC721Enumerable, IERC5007Mod { +abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { struct Timestamp { uint256 startBlock; uint256 endBlock; @@ -61,19 +61,19 @@ abstract contract ERC721EXPBase is ERC721Enumerable, IERC5007Mod { } } - /// @inheritdoc IERC5007Mod + /// @inheritdoc IERC7858 function startTime(uint256 tokenId) public view virtual override returns (uint256) { return _tokensTimestamp[tokenId].startBlock; } - /// @inheritdoc IERC5007Mod + /// @inheritdoc IERC7858 function endTime(uint256 tokenId) public view virtual override returns (uint256) { return _tokensTimestamp[tokenId].endBlock; } // @TODO function support interface function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721Enumerable) returns (bool) { - return interfaceId == type(IERC5007Mod).interfaceId || super.supportsInterface(interfaceId); + return interfaceId == type(IERC7858).interfaceId || super.supportsInterface(interfaceId); } // @TODO other useful function like isTokenValid diff --git a/contracts/tokens/ERC721/extensions/IERC5007Mod.sol b/contracts/tokens/ERC721/extensions/IERC7858.sol similarity index 92% rename from contracts/tokens/ERC721/extensions/IERC5007Mod.sol rename to contracts/tokens/ERC721/extensions/IERC7858.sol index 997c7487..6a03cae9 100644 --- a/contracts/tokens/ERC721/extensions/IERC5007Mod.sol +++ b/contracts/tokens/ERC721/extensions/IERC7858.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; /// @title ERC-5007 modified to support block-based import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; -interface IERC5007Mod is IERC721 { +interface IERC7858 is IERC721 { error ERC5007TransferredInvalidToken(); From 1c3a9a3b6331b1efc3bed8505a393001ebcc33a6 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 13 Jan 2025 10:20:01 +0700 Subject: [PATCH 02/53] refactor: lightweight sorted list Signed-off-by: MASDXI --- contracts/tokens/ERC20/ERC20EXPBase.sol | 33 +++++++----- contracts/utils/datastructures/SortedList.sol | 52 ++++++++++++------- mocks/contracts/utils/MockSortedList.sol | 12 ++--- 3 files changed, 57 insertions(+), 40 deletions(-) diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index 6eff99ee..49c83694 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.0 <0.9.0; /// @title ERC20EXP Base /// @author Kiwari Labs -import {SCDLL} from "../../utils/datastructures/LSCDLL.sol"; +import {SortedList as SCDLL} from "../../utils/datastructures/SortedList.sol"; import {Context} from "@openzeppelin/contracts/utils/Context.sol"; import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; @@ -81,8 +81,8 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 /// @return element The index of the first valid block balance. function _findValidBalance(address account, uint256 epoch, uint256 pointer, uint256 duration) private view returns (uint256 element) { SCDLL.List storage list = _balances[epoch][account].list; - if (list.size() > 0) { - element = list.head(); + if (!list.isEmpty()) { + element = list.front(); unchecked { while (pointer - element >= duration) { element = list.next(element); @@ -100,8 +100,8 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 /// @param duration The duration to determine outdated elements. function _refreshBalanceAtEpoch(address account, uint256 epoch, uint256 pointer, uint256 duration) private { Epoch storage _account = _balances[epoch][account]; - if (_account.list.size() > 0) { - uint256 element = _account.list.head(); + if (!_account.list.isEmpty()) { + uint256 element = _account.list.front(); uint256 balance; unchecked { while (pointer - element >= duration) { @@ -110,7 +110,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 } } if (balance > 0) { - _account.list.lazyShrink(element); + _account.list.shrink(element); _account.totalBalance -= balance; } } @@ -144,7 +144,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.balances[pointer] += value; _worldStateBalances[pointer] += value; } - _recipient.list.insert(pointer); + _recipient.list.insert(pointer, false); } else { uint256 blockLengthCache = _getPointersInWindow(); (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); @@ -158,7 +158,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 // burn token from while (fromEpoch <= toEpoch && pendingValue > 0) { Epoch storage _spender = _balances[fromEpoch][from]; - uint256 element = _spender.list.head(); + uint256 element = _spender.list.front(); while (element > 0 && pendingValue > 0) { balance = _spender.balances[element]; if (balance <= pendingValue) { @@ -188,7 +188,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 while (fromEpoch <= toEpoch && pendingValue > 0) { Epoch storage _spender = _balances[fromEpoch][from]; Epoch storage _recipient = _balances[fromEpoch][to]; - uint256 element = _spender.list.head(); + uint256 element = _spender.list.front(); while (element > 0 && pendingValue > 0) { balance = _spender.balances[element]; if (balance <= pendingValue) { @@ -199,7 +199,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.totalBalance += balance; _recipient.balances[element] += balance; } - _recipient.list.insert(element); + _recipient.list.insert(element, false); element = _spender.list.next(element); _spender.list.remove(_spender.list.previous(element)); } else { @@ -209,7 +209,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.totalBalance += pendingValue; _recipient.balances[element] += pendingValue; } - _recipient.list.insert(element); + _recipient.list.insert(element, false); pendingValue = 0; } } @@ -265,7 +265,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.totalBalance += balance; _recipient.balances[element] += balance; } - _recipient.list.insert(element); + _recipient.list.insert(element, false); element = _spender.list.next(element); _spender.list.remove(_spender.list.previous(element)); } else { @@ -275,7 +275,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.totalBalance += pendingValue; _recipient.balances[element] += pendingValue; } - _recipient.list.insert(element); + _recipient.list.insert(element, false); pendingValue = 0; } } @@ -398,7 +398,12 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 /// @custom:gas-inefficiency if not limit the size of array function tokenList(address account, uint256 epoch) external view virtual returns (uint256[] memory list) { - list = _balances[epoch][account].list.ascending(); + list = _balances[epoch][account].list.toArray(); + } + + /// @custom:gas-inefficiency if not limit the size of array + function tokenList(address account, uint256 epoch, uint256 start) external view virtual returns (uint256[] memory list) { + list = _balances[epoch][account].list.toArray(start); } /// @dev See {IERC20Metadata-name}. diff --git a/contracts/utils/datastructures/SortedList.sol b/contracts/utils/datastructures/SortedList.sol index acdc7a25..83ddda7d 100644 --- a/contracts/utils/datastructures/SortedList.sol +++ b/contracts/utils/datastructures/SortedList.sol @@ -2,7 +2,7 @@ pragma solidity >=0.8.0 <0.9.0; /* - * @title Sorted List + * @title Lightweight Sorted List * @author Kiwari Labs */ @@ -11,8 +11,8 @@ library SortedList { mapping(uint256 => mapping(bool => uint256)) _nodes; } - uint8 private constant SENTINEL = 0; - bool private constant PREVIOUS = false; + uint8 private constant SENTINEL = 0x00; + private constant PREVIOUS = false; bool private constant NEXT = true; /** @@ -23,16 +23,14 @@ library SortedList { * @param length The size of array * @return array containing the indices of nodes in the linked list, ordered according to the specified direction. */ - function _toArray(List storage self, uint256 length) private view returns (uint256[] memory array) { - // return early pattern - uint256 element = front(self); + function _toArray(List storage self, uint256 start, uint256 length) private view returns (uint256[] memory array) { + if (start == SENTINEL) return array; array = new uint256[](length); - if (element == SENTINEL) return array; uint128 index; unchecked { - for (; element != SENTINEL; index++) { - array[index] = element; - element = next(self, element); + for (; start != SENTINEL; index++) { + array[index] = start; + start = next(self, start); } } assembly { @@ -46,8 +44,8 @@ library SortedList { * @param self The linked list. * @param index The element at which to insert the data. */ - function insert(List storage self, uint256 index, bool lazy) internal { - if (!lazy) { + function insert(List storage self, uint256 index, bool check) internal { + if (check) { if (contains(self, index)) return; } uint256 last = self._nodes[SENTINEL][PREVIOUS]; @@ -78,10 +76,10 @@ library SortedList { while (index > cursor) { cursor = self._nodes[cursor][NEXT]; } - uint256 tmpPrev = self._nodes[cursor][PREVIOUS]; - self._nodes[tmpPrev][NEXT] = index; + uint256 before = self._nodes[cursor][PREVIOUS]; + self._nodes[before][NEXT] = index; self._nodes[cursor][PREVIOUS] = index; - self._nodes[index][PREVIOUS] = tmpPrev; + self._nodes[index][PREVIOUS] = before; self._nodes[index][NEXT] = cursor; } @@ -103,7 +101,7 @@ library SortedList { assembly { let slot := self.slot - sstore(slot, sub(sload(slot), 1)) + sstore(slot, sub(sload(slot), 0x01)) } } } @@ -132,7 +130,7 @@ library SortedList { uint256 beforeElement = self._nodes[element][PREVIOUS]; uint256 afterSentinel = self._nodes[SENTINEL][NEXT]; assembly { - result := or(eq(afterSentinel, element), gt(beforeElement, 0)) + result := or(eq(afterSentinel, element), gt(beforeElement, 0x00)) } } @@ -184,8 +182,15 @@ library SortedList { * @param self The linked list. * @return The _size of the linked list. */ - function size(List storage self) internal view returns (uint256) { - return _toArray(self, 512).length; + function size() internal pure returns (uint256) { + return 0x200; + } + + /* + * @dev check is the list empty or not. + */ + function isEmpty(List storage self) internal view returns (bool) { + return (front(self) == SENTINEL); } /* @@ -195,6 +200,13 @@ library SortedList { * @return array containing the indices of nodes in ascending order. */ function toArray(List storage self) internal view returns (uint256[] memory array) { - return _toArray(self, 512); + return _toArray(self, front(self), 0x200); + } + + /* + * @dev pagination like with static length set to 512. + */ + function toArray(List storage self, uint256 start) internal view returns (uint256[] memory array) { + return _toArray(self, start, 0x200); } } diff --git a/mocks/contracts/utils/MockSortedList.sol b/mocks/contracts/utils/MockSortedList.sol index 9fd3d263..8287c662 100644 --- a/mocks/contracts/utils/MockSortedList.sol +++ b/mocks/contracts/utils/MockSortedList.sol @@ -28,10 +28,6 @@ contract MockSortedList { list.insert(index, true); } - // function updateNodeData(uint256 index, bytes memory data) public { - // list.updateNodeData(index, data); - // } - function shrink(uint256 index) public { list.shrink(index); } @@ -40,8 +36,8 @@ contract MockSortedList { list.remove(index); } - function size() public view returns (uint256) { - return list.size(); + function size() public pure returns (uint256) { + return SortedList.size(); } function front() public view returns (uint256) { @@ -55,4 +51,8 @@ contract MockSortedList { function array() public view returns (uint256[] memory) { return list.toArray(); } + + function arrayWithStart(uint256 start) public view returns (uint256[] memory) { + return list.toArray(start); + } } From ed9475a458360c18edbb194b31ef350572072a73 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 13 Jan 2025 13:05:03 +0700 Subject: [PATCH 03/53] test: finish test fo sorted list Signed-off-by: MASDXI --- contracts/utils/datastructures/SortedList.sol | 4 +- mocks/contracts/utils/MockSortedList.sol | 4 ++ test/constant.test.ts | 2 +- test/index.test.ts | 2 +- .../SortedList/deployer.test.ts | 9 ++-- .../SortedList/general.test.ts | 30 +++++++++-- .../datatstructures/SortedList/insert.test.ts | 31 +++++++++-- .../datatstructures/SortedList/remove.test.ts | 36 +++++++++++-- .../datatstructures/SortedList/shrink.test.ts | 54 +++++++++++++++++-- test/utils/index.test.ts | 2 +- 10 files changed, 149 insertions(+), 25 deletions(-) diff --git a/contracts/utils/datastructures/SortedList.sol b/contracts/utils/datastructures/SortedList.sol index 83ddda7d..f5652647 100644 --- a/contracts/utils/datastructures/SortedList.sol +++ b/contracts/utils/datastructures/SortedList.sol @@ -12,7 +12,7 @@ library SortedList { } uint8 private constant SENTINEL = 0x00; - private constant PREVIOUS = false; + bool private constant PREVIOUS = false; bool private constant NEXT = true; /** @@ -46,7 +46,7 @@ library SortedList { */ function insert(List storage self, uint256 index, bool check) internal { if (check) { - if (contains(self, index)) return; + if (!contains(self, index)) return; } uint256 last = self._nodes[SENTINEL][PREVIOUS]; uint256 first = self._nodes[SENTINEL][NEXT]; diff --git a/mocks/contracts/utils/MockSortedList.sol b/mocks/contracts/utils/MockSortedList.sol index 8287c662..05992fd2 100644 --- a/mocks/contracts/utils/MockSortedList.sol +++ b/mocks/contracts/utils/MockSortedList.sol @@ -20,6 +20,10 @@ contract MockSortedList { return list.previous(index); } + function isEmpty() public view returns (bool) { + return list.isEmpty(); + } + function insert(uint256 index) public { list.insert(index, false); } diff --git a/test/constant.test.ts b/test/constant.test.ts index c45f137d..53df7e49 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -158,7 +158,7 @@ export const BLSWLibrary = { }; export const SortedCircularDoublyLinkedListLibrary = { - name: "MockSortedCircularDoublyLinkedListLibrary", + name: "MockSortedList", errors: {}, events: {}, }; diff --git a/test/index.test.ts b/test/index.test.ts index 59a654ab..2401b6f8 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -5,6 +5,6 @@ import {EventEmitter} from "events"; describe("Scenario", async function () { EventEmitter.setMaxListeners(1000); // abstracts.run(); - tokens.run(); + // tokens.run(); utils.run(); }); diff --git a/test/utils/datatstructures/SortedList/deployer.test.ts b/test/utils/datatstructures/SortedList/deployer.test.ts index 01dba1a0..ae32def1 100644 --- a/test/utils/datatstructures/SortedList/deployer.test.ts +++ b/test/utils/datatstructures/SortedList/deployer.test.ts @@ -1,6 +1,5 @@ import {ethers} from "hardhat"; -import {BLSWLibrary, SortedCircularDoublyLinkedListLibrary} from "../../../constant.test"; -import {NumberLike} from "@nomicfoundation/hardhat-network-helpers/dist/src/types"; +import {SortedCircularDoublyLinkedListLibrary} from "../../../constant.test"; export const deploySortedList = async function (contract: string) { // @TODO selector @@ -11,9 +10,9 @@ export const deploySortedList = async function (contract: string) { contract = SortedCircularDoublyLinkedListLibrary.name; } const [deployer, alice, bob, charlie] = await ethers.getSigners(); - const SortedList = await ethers.getContractFactory(contract, deployer); - const sortedlist = await SortedList.deploy(); - await sortedlist.deployed(); + const SORTED_LIST = await ethers.getContractFactory(contract, deployer); + const sortedlist = await SORTED_LIST.deploy(); + await sortedlist.waitForDeployment(); return { sortedlist, deployer, diff --git a/test/utils/datatstructures/SortedList/general.test.ts b/test/utils/datatstructures/SortedList/general.test.ts index 8bf6f496..9d2a42f6 100644 --- a/test/utils/datatstructures/SortedList/general.test.ts +++ b/test/utils/datatstructures/SortedList/general.test.ts @@ -1,26 +1,48 @@ import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; +import {deploySortedList} from "./deployer.test"; export const run = async () => { describe("General", async function () { + const element = 1; + const sentinel = 0; + afterEach(async function () { await hardhat_reset(); }); it("[SUCCESS] contains", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.contains(element)).to.equal(false); + expect(await sortedlist.contains(sentinel)).to.equal(true); }); it("[SUCCESS] previous", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.previous(element)).to.equal(sentinel); }); it("[SUCCESS] next", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.next(element)).to.equal(sentinel); }); it("[FAILED] toArray", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.array()).to.deep.equal([]); + }); + + it("[FAILED] toArray with start point", async function () { + const {sortedlist} = await deploySortedList(""); + for (let index = 1; index <= 10; index++) { + await sortedlist.insert(index); + } + expect(await sortedlist.arrayWithStart(5)).to.deep.equal([5, 6, 7, 8, 9, 10]); + }); + + it("[FAILED] size", async function () { + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.size()).to.deep.equal(512); }); }); }; diff --git a/test/utils/datatstructures/SortedList/insert.test.ts b/test/utils/datatstructures/SortedList/insert.test.ts index d9ed7ed0..788ce461 100644 --- a/test/utils/datatstructures/SortedList/insert.test.ts +++ b/test/utils/datatstructures/SortedList/insert.test.ts @@ -1,26 +1,49 @@ import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; +import {deploySortedList} from "./deployer.test"; export const run = async () => { describe("Insert", async function () { + const front = 1; + const element = 2; + const back = 3; + const sentinel = 0; + afterEach(async function () { await hardhat_reset(); }); it("[SUCCESS] insert before front", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(element); + expect(await sortedlist.front()).to.equal(element); + await sortedlist.insert(front); + expect(await sortedlist.front()).to.equal(front); }); it("[SUCCESS] insert after back", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(element); + expect(await sortedlist.back()).to.equal(element); + await sortedlist.insert(back); + expect(await sortedlist.back()).to.equal(back); }); it("[SUCCESS] insert between front and back", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(back); + expect(await sortedlist.front()).to.equal(front); + expect(await sortedlist.back()).to.equal(back); + await sortedlist.insert(element); + expect(await sortedlist.contains(element)).to.equal(true); }); it("[FAILED] insert the exist element", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.isEmpty()).to.equal(true); + await sortedlist.insert(0); + expect(await sortedlist.isEmpty()).to.equal(true); }); }); }; diff --git a/test/utils/datatstructures/SortedList/remove.test.ts b/test/utils/datatstructures/SortedList/remove.test.ts index a0a1374c..0ec92293 100644 --- a/test/utils/datatstructures/SortedList/remove.test.ts +++ b/test/utils/datatstructures/SortedList/remove.test.ts @@ -1,26 +1,54 @@ import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; +import {deploySortedList} from "./deployer.test"; export const run = async () => { describe("Remove", async function () { + const front = 1; + const element = 2; + const back = 3; + afterEach(async function () { await hardhat_reset(); }); it("[SUCCESS] remove front", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(back); + expect(await sortedlist.contains(front)).to.equal(true); + await sortedlist.remove(front); + expect(await sortedlist.contains(front)).to.equal(false); + expect(await sortedlist.front()).to.equal(back); }); it("[SUCCESS] remove back", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(back); + expect(await sortedlist.contains(back)).to.equal(true); + await sortedlist.remove(back); + expect(await sortedlist.contains(back)).to.equal(false); + expect(await sortedlist.back()).to.equal(front); }); it("[SUCCESS] remove between front and back", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(element) + await sortedlist.insert(back); + expect(await sortedlist.contains(element)).to.equal(true); + await sortedlist.remove(element); + expect(await sortedlist.contains(element)).to.equal(false); + expect(await sortedlist.front()).to.equal(front); + expect(await sortedlist.back()).to.equal(back); }); it("[FAILED] remove the non-exist element", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.isEmpty()).to.equal(true); + await sortedlist.remove(4); + expect(await sortedlist.isEmpty()).to.equal(true); }); }); }; diff --git a/test/utils/datatstructures/SortedList/shrink.test.ts b/test/utils/datatstructures/SortedList/shrink.test.ts index 1e4b5257..16a7ade2 100644 --- a/test/utils/datatstructures/SortedList/shrink.test.ts +++ b/test/utils/datatstructures/SortedList/shrink.test.ts @@ -1,22 +1,70 @@ import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; +import {deploySortedList} from "./deployer.test"; export const run = async () => { describe("Shrink", async function () { + const front = 1; + const element = 2; + const elementSecond = 3; + const elementThird = 4; + const back = 5; + afterEach(async function () { await hardhat_reset(); }); it("[SUCCESS] shrink", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(element); + await sortedlist.insert(back); + expect(await sortedlist.front()).to.equal(front); + expect(await sortedlist.contains(element)).to.equal(true); + expect(await sortedlist.back()).to.equal(back); + await sortedlist.shrink(element); + expect(await sortedlist.front()).to.equal(element); + expect(await sortedlist.contains(front)).to.equal(false); + }); + + it("[SUCCESS] shrink then insert back", async function () { + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(element); + await sortedlist.insert(elementSecond); + await sortedlist.insert(elementThird); + await sortedlist.insert(back); + expect(await sortedlist.front()).to.equal(front); + expect(await sortedlist.contains(element)).to.equal(true); + expect(await sortedlist.back()).to.equal(back); + await sortedlist.shrink(elementThird); + expect(await sortedlist.front()).to.equal(elementThird); + expect(await sortedlist.contains(element)).to.equal(true); + await sortedlist.insertLazy(element); + expect(await sortedlist.front()).to.equal(element); }); it("[FAILED] shrink the non-exist element", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + expect(await sortedlist.isEmpty()).to.equal(true); + await sortedlist.shrink(element); + await sortedlist.insertLazy(element); + expect(await sortedlist.isEmpty()).to.equal(true); }); it("[FAILED] shrink the ignore element", async function () { - // @TODO + const {sortedlist} = await deploySortedList(""); + await sortedlist.insert(front); + await sortedlist.insert(element) + await sortedlist.insert(back); + expect(await sortedlist.front()).to.equal(front); + expect(await sortedlist.contains(element)).to.equal(true); + expect(await sortedlist.back()).to.equal(back); + await sortedlist.shrink(element); + expect(await sortedlist.front()).to.equal(element); + expect(await sortedlist.contains(front)).to.equal(false); + await sortedlist.shrink(front); + expect(await sortedlist.contains(front)).to.equal(false); }); }); }; diff --git a/test/utils/index.test.ts b/test/utils/index.test.ts index cf9178cd..114a1895 100644 --- a/test/utils/index.test.ts +++ b/test/utils/index.test.ts @@ -7,7 +7,7 @@ import * as SortedList from "./datatstructures/SortedList/index.test"; export const run = async () => { describe("utils", async function () { BLSW.run(); - // SortedList.run(); + SortedList.run(); // XortedList.run(); }); }; From f8c2147763aba6af2f57e2a57cc0b3f1be99a985 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 13 Jan 2025 14:02:48 +0700 Subject: [PATCH 04/53] more clean up Signed-off-by: MASDXI --- contracts/tokens/ERC20/ERC20EXPBase.sol | 8 +- contracts/utils/datastructures/LSCDLL.sol | 370 ------------------ hardhat.config.ts | 2 +- test/constant.test.ts | 25 +- test/index.test.ts | 2 +- .../ERC7818Backlist/addToBlacklist.test.ts | 2 +- .../LazyShrink.test.ts | 154 -------- .../LazyShrink.test.ts | 158 -------- .../index.test.ts => algorithms/TLSW/.keep} | 0 .../SortedList/deployer.test.ts | 6 +- .../datatstructures/SortedList/index.test.ts | 2 +- test/utils/datatstructures/XortedList/.keep | 0 test/utils/index.test.ts | 4 +- 13 files changed, 17 insertions(+), 716 deletions(-) delete mode 100644 contracts/utils/datastructures/LSCDLL.sol delete mode 100644 test/utils/LightWeightSortedCircularDoublyLinkedList/LazyShrink.test.ts delete mode 100644 test/utils/SortedCircularDoublyLinkedList/LazyShrink.test.ts rename test/utils/{datatstructures/index.test.ts => algorithms/TLSW/.keep} (100%) create mode 100644 test/utils/datatstructures/XortedList/.keep diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index 49c83694..3b480201 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -4,14 +4,14 @@ pragma solidity >=0.8.0 <0.9.0; /// @title ERC20EXP Base /// @author Kiwari Labs -import {SortedList as SCDLL} from "../../utils/datastructures/SortedList.sol"; +import {SortedList} from "../../utils/datastructures/SortedList.sol"; import {Context} from "@openzeppelin/contracts/utils/Context.sol"; import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC7818} from "./interfaces/IERC7818.sol"; abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC7818 { - using SCDLL for SCDLL.List; + using SortedList for SortedList.List; string private _name; string private _symbol; @@ -19,7 +19,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 struct Epoch { uint256 totalBalance; mapping(uint256 => uint256) balances; - SCDLL.List list; + SortedList.List list; } mapping(uint256 => mapping(address => Epoch)) private _balances; @@ -80,7 +80,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 /// @param duration The maximum allowed difference between pointer and the key. /// @return element The index of the first valid block balance. function _findValidBalance(address account, uint256 epoch, uint256 pointer, uint256 duration) private view returns (uint256 element) { - SCDLL.List storage list = _balances[epoch][account].list; + SortedList.List storage list = _balances[epoch][account].list; if (!list.isEmpty()) { element = list.front(); unchecked { diff --git a/contracts/utils/datastructures/LSCDLL.sol b/contracts/utils/datastructures/LSCDLL.sol deleted file mode 100644 index 48b1bd8d..00000000 --- a/contracts/utils/datastructures/LSCDLL.sol +++ /dev/null @@ -1,370 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity >=0.8.0 <0.9.0; - -/// @title A lightweight version of a Sorted Circular Doubly Linked List. -/// @author Kiwari Labs -/// @notice This version reduces gas usage by removing embedded byte data from nodes and incurs less overhead compared to the original version. - -library SCDLL { - /// Sorted Circular Doubly Linked List - struct List { - uint256 _size; - mapping(uint256 node => mapping(bool direction => uint256 value)) _nodes; - } - - /// Constants for managing a doubly linked list. - uint8 private constant ONE_BIT = 1; /// A constant representing a single bit (1). - uint8 private constant SENTINEL = 0; /// A sentinel value used to indicate the end or start of the list. - bool private constant PREV = false; /// Direction constant for the previous node. - bool private constant NEXT = true; /// Direction constant for the next node. - - /// @notice Partitions the linked list in the specified direction. - /// @dev This function creates an array `part` of size `listSize`, containing the indices of nodes - /// in the linked list, traversed in the specified `direction` (NEXT or PREV). - /// @param self The linked list state. - /// @param listSize The size of the list to partition. - /// @param direction The direction of traversal: NEXT for forward, PREV for backward. - /// @return part An array containing the indices of the nodes in the partition. - function _partition(List storage self, uint256 listSize, bool direction) private view returns (uint256[] memory part) { - unchecked { - part = new uint256[](listSize); - uint256 index; - for (uint256 i = SENTINEL; i < listSize; i++) { - part[i] = self._nodes[index][direction]; - index = part[i]; - } - } - } - - /// @notice Retrieves the path of node indices in the specified direction, starting from the given index. - /// @dev Constructs an array `part` containing the indices of nodes in the linked list, - /// starting from `index` and following the specified `direction` (NEXT or PREV) until the end is reached. - /// @param self The linked list state. - /// @param index The starting index of the node. - /// @param direction The direction of traversal: NEXT for forward, PREV for backward. - /// @return part An array of node indices from the starting index to the head (for NEXT) or the tail (for PREV). - function _path(List storage self, uint256 index, bool direction) private view returns (uint256[] memory part) { - uint256 tmpSize = self._size; - part = new uint[](tmpSize); - uint256 counter; - unchecked { - while (index != SENTINEL && counter < tmpSize) { - part[counter] = index; - counter++; - index = self._nodes[index][direction]; - } - } - assembly { - /// @notice Resize the array to the actual count of elements using inline assembly. - mstore(part, counter) /// Set the array length to the actual count. - } - } - - /// @notice Traverses the linked list in the specified direction and returns a list of node indices. - /// @dev Constructs an array `list` containing the indices of nodes in the linked list, - /// starting from either the head or the tail, based on the `direction` parameter. - /// @param self The linked list state. - /// @param direction The traversal direction: true for forward (starting from the head), false for backward (starting from the tail). - /// @return list An array of node indices ordered according to the specified direction. - function _traversal(List storage self, bool direction) private view returns (uint256[] memory list) { - uint256 tmpSize = self._size; - if (tmpSize > SENTINEL) { - uint256 index; - list = new uint256[](tmpSize); - list[SENTINEL] = self._nodes[index][!direction]; - unchecked { - for (uint256 i = tmpSize - 1; i > SENTINEL; i--) { - list[i] = self._nodes[index][direction]; - index = list[i]; - } - } - } - } - - /// @notice Checks if a node exists in the linked list. - /// @dev Check if a node exists in the linked list at the specified index. - /// @param self The linked list state. - /// @param index The index of the node to check. - /// @return result True if the node exists, false otherwise. - function exist(List storage self, uint256 index) internal view returns (bool result) { - uint256 tmpPrev = self._nodes[index][PREV]; - uint256 tmpNext = self._nodes[SENTINEL][NEXT]; - assembly { - result := or(eq(tmpNext, index), gt(tmpPrev, 0)) - } - } - - /// @notice Retrieves the index of the next node in the list. - /// @dev Accesses the `_nodes` mapping in the `List` structure to fetch the index of the next node. - /// @param self The linked list state. - /// @param index The index of the current node. - /// @return The index of the next node. - function next(List storage self, uint256 index) internal view returns (uint256) { - return self._nodes[index][NEXT]; - } - - /// @notice Get the index of the previous node in the list. - /// @dev Accesses the `_nodes` mapping in the `List` structure to get the index of the previous node. - /// @param self The linked list state. - /// @param index The index of the current node. - /// @return The index of the previous node. - function previous(List storage self, uint256 index) internal view returns (uint256) { - return self._nodes[index][PREV]; - } - - /// @notice Inserts data into the linked list at the specified index. - /// @dev This function inserts the provided data into the linked list at the given index. - /// @param self The linked list state. - /// @param index The index at which to insert the data. - function insert(List storage self, uint256 index) internal { - if (!exist(self, index)) { - uint256 tmpTail = self._nodes[SENTINEL][PREV]; - uint256 tmpHead = self._nodes[SENTINEL][NEXT]; - uint256 tmpSize = self._size; - if (tmpSize == SENTINEL) { - self._nodes[SENTINEL][NEXT] = index; - self._nodes[SENTINEL][PREV] = index; - self._nodes[index][PREV] = SENTINEL; - self._nodes[index][NEXT] = SENTINEL; - } else if (index < tmpHead) { - self._nodes[SENTINEL][NEXT] = index; - self._nodes[tmpHead][PREV] = index; - self._nodes[index][PREV] = SENTINEL; - self._nodes[index][NEXT] = tmpHead; - } else if (index > tmpTail) { - self._nodes[SENTINEL][PREV] = index; - self._nodes[tmpTail][NEXT] = index; - self._nodes[index][PREV] = tmpTail; - self._nodes[index][NEXT] = SENTINEL; - } else { - uint256 tmpCurr; - unchecked { - if (index - tmpHead <= tmpTail - index) { - tmpCurr = tmpHead; - while (index > tmpCurr) { - tmpCurr = self._nodes[tmpCurr][NEXT]; - } - } else { - tmpCurr = tmpTail; - while (index < tmpCurr) { - tmpCurr = self._nodes[tmpCurr][PREV]; - } - } - } - uint256 tmpPrev = self._nodes[tmpCurr][PREV]; - self._nodes[tmpPrev][NEXT] = index; - self._nodes[tmpCurr][PREV] = index; - self._nodes[index][PREV] = tmpPrev; - self._nodes[index][NEXT] = tmpCurr; - } - assembly { - sstore(self.slot, add(tmpSize, 1)) - } - } - } - - /// @notice Removes a node from the linked list at the specified index. - /// @dev This function removes the node from the linked list at the given index. - /// @param self The linked list state. - /// @param index The index of the node to remove. - function remove(List storage self, uint256 index) internal { - if (exist(self, index)) { - uint256 tmpPrev = self._nodes[index][PREV]; - uint256 tmpNext = self._nodes[index][NEXT]; - self._nodes[index][NEXT] = SENTINEL; - self._nodes[index][PREV] = SENTINEL; - self._nodes[tmpPrev][NEXT] = tmpNext; - self._nodes[tmpNext][PREV] = tmpPrev; - assembly { - sstore(self.slot, sub(sload(self.slot), 1)) - } - } - } - - /// @notice Shrinks the list by removing all nodes before the specified index. - /// @dev Updates the head of the list to the specified index, removing all nodes before it. - /// @param self The linked list state. - /// @param index The index from which to shrink the list. All nodes before this index will be removed. - function shrink(List storage self, uint256 index) internal { - if (exist(self, index)) { - uint256 tmpCurr = self._nodes[SENTINEL][NEXT]; - uint256 tmpSize = self._size; - while (tmpCurr != index) { - uint256 tmpNext = self._nodes[tmpCurr][NEXT]; - self._nodes[tmpCurr][NEXT] = SENTINEL; - self._nodes[tmpCurr][PREV] = SENTINEL; - tmpCurr = tmpNext; - unchecked { - tmpSize--; - } - } - assembly { - sstore(self.slot, tmpSize) - } - self._nodes[SENTINEL][NEXT] = index; - self._nodes[index][PREV] = SENTINEL; - } - } - - /// @notice Shrinks the list by setting a new head without removing previous nodes. - /// @dev Updates the head pointer to the specified `index` without traversing or cleaning up previous nodes. - /// @param self The linked list state. - /// @param index The index to set as the new head of the list. - function lazyShrink(List storage self, uint256 index) internal { - if (exist(self, index)) { - self._nodes[SENTINEL][NEXT] = index; /// forced link sentinel to new head - self._nodes[index][PREV] = SENTINEL; /// forced link previous of index to sentinel - - uint256 counter; - while (index != SENTINEL) { - unchecked { - counter++; - } - index = self._nodes[index][NEXT]; - } - self._size = counter; - } - } - - /// @notice Retrieves the index of the head node in the linked list. - /// @dev Returns the index of the head node in the linked list. - /// @param self The linked list state. - /// @return The index of the head node. - function head(List storage self) internal view returns (uint256) { - return self._nodes[SENTINEL][NEXT]; - } - - /// @notice Retrieves the index of the middle node in the list. - /// @dev Returns the index of the middle node in the linked list. - /// @param self The linked list state. - /// @return mid The index of the middle node. - function middle(List storage self) internal view returns (uint256 mid) { - assembly { - mid := sload(self.slot) - if iszero(mid) { - return(0x00, 0x20) - } - } - uint256[] memory tmpList = firstPartition(self); - mid = tmpList[tmpList.length - 1]; - } - - /// @notice Retrieves the index of the tail node in the linked list. - /// @dev Returns the index of the tail node in the linked list. - /// @param self The linked list state. - /// @return The index of the tail node. - function tail(List storage self) internal view returns (uint256) { - return self._nodes[SENTINEL][PREV]; - } - - /// @notice Retrieves the size of the linked list. - /// @dev Returns the size of the linked list. - /// @param self The linked list state. - /// @return The size of the linked list. - function size(List storage self) internal view returns (uint256) { - return self._size; - } - - /// @notice Retrieves information about a node in the list. - /// @dev Returns information about a node in the list at the specified index. - /// @param self The linked list state. - /// @param index The index of the node. - /// @return prev The index of the previous node. - /// @return next The index of the next node. - function node(List storage self, uint256 index) internal view returns (uint256, uint256) { - return (self._nodes[index][PREV], self._nodes[index][NEXT]); - } - - /// @notice Retrieves the indices of nodes in ascending order. - /// @dev Returns an array containing the indices of nodes in ascending order. - /// @param self The linked list state. - /// @return An array of node indices in ascending order. - function ascending(List storage self) internal view returns (uint256[] memory) { - return _traversal(self, PREV); - } - - /// @notice Retrieves the indices of nodes in descending order. - /// @dev Returns an array containing the indices of nodes in descending order. - /// @param self The linked list state. - /// @return An array of node indices in descending order. - function descending(List storage self) internal view returns (uint256[] memory) { - return _traversal(self, NEXT); - } - - /// @notice Retrieves the indices of nodes in the first partition of the linked list. - /// @dev Returns an array containing the indices of nodes in the first partition of the linked list. - /// @param self The linked list state. - /// @return part An array of node indices in the first partition. - function firstPartition(List storage self) internal view returns (uint256[] memory part) { - uint256 tmpSize = self._size; - if (tmpSize > SENTINEL) { - unchecked { - tmpSize = tmpSize == 1 ? tmpSize : tmpSize >> ONE_BIT; - } - part = _partition(self, tmpSize, NEXT); - } - } - - /// @notice Retrieves the indices of nodes in the second partition of the linked list. - /// @dev Returns an array containing the indices of nodes in the second partition of the linked list. - /// @param self The linked list state. - /// @return part An array of node indices in the second partition. - function secondPartition(List storage self) internal view returns (uint256[] memory part) { - uint256 tmpSize = self._size; - if (tmpSize > SENTINEL) { - unchecked { - if (tmpSize & ONE_BIT == SENTINEL) { - tmpSize = tmpSize >> ONE_BIT; - } else { - tmpSize = (tmpSize + 1) >> ONE_BIT; - } - part = _partition(self, tmpSize, PREV); - } - } - } - - /// @notice Retrieves the path of indices from a specified node to the head of the linked list. - /// @dev Returns an array containing the indices of nodes from a specified node to the head of the linked list. - /// @param self The linked list state. - /// @param index The starting index. - /// @return part An array of node indices from the starting node to the head. - function pathToHead(List storage self, uint256 index) internal view returns (uint256[] memory part) { - if (exist(self, index)) { - part = _path(self, index, PREV); - } - } - - /// @notice Retrieves the path of indices from a specified node to the tail of the linked list. - /// @dev Returns an array containing the indices of nodes from a specified node to the tail of the linked list. - /// @param self The linked list state. - /// @param index The starting index. - /// @return part An array of node indices from the starting node to the tail. - function pathToTail(List storage self, uint256 index) internal view returns (uint256[] memory part) { - if (exist(self, index)) { - part = _path(self, index, NEXT); - } - } - - /// @notice Retrieves the indices starting from a specified node and wrapping around to the beginning if necessary. - /// @dev Returns an array of node indices starting from a specified node and wrapping around to the beginning if necessary. - /// @param self The linked list state. - /// @param start The starting index. - /// @return part An array of node indices. - function partition(List storage self, uint256 start) internal view returns (uint256[] memory part) { - if (exist(self, start)) { - uint256 tmpSize = self._size; - part = new uint[](tmpSize); - uint256 counter; - unchecked { - while (counter < tmpSize) { - part[counter] = start; /// Add the current index to the partition. - counter++; - start = self._nodes[start][NEXT]; /// Move to the next node. - if (start == SENTINEL) { - start = self._nodes[start][NEXT]; /// Move to the next node. - } - } - } - } - } -} diff --git a/hardhat.config.ts b/hardhat.config.ts index 72f1212b..6a963828 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -69,7 +69,7 @@ const config: HardhatUserConfig = { outputDir: "./docs", }, mocha: { - slow: 20, + slow: 50, }, }; diff --git a/test/constant.test.ts b/test/constant.test.ts index 53df7e49..582fff77 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -157,30 +157,18 @@ export const BLSWLibrary = { events: {}, }; -export const SortedCircularDoublyLinkedListLibrary = { +export const SortedListLibrary = { name: "MockSortedList", errors: {}, events: {}, }; -export const LightWeightSortedCircularDoublyLinkedListLibrary = { +export const XortedListLibrary = { name: "MockXort128", errors: {}, events: {}, }; -export const Xort128 = { - name: "MockLightWeightSortedCircularDoublyLinkedListLibraryV2", - errors: {}, - events: {}, -}; - -export const Comparator = { - name: "MockComparator", - errors: {}, - events: {}, -}; - export const contracts = { abstracts: { SlidingWindow, @@ -192,12 +180,7 @@ export const contracts = { }, utils: { SlidingWindowLibrary, - SortedCircularDoublyLinkedListLibrary, - // LightWeightSlidingWindowLibrary, - // LightWeightSortedCircularDoublyLinkedListLibrary, - // PU128SCDLL, - comparators: { - Comparator, - }, + SortedListLibrary, + XortedListLibrary }, }; diff --git a/test/index.test.ts b/test/index.test.ts index 2401b6f8..59a654ab 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -5,6 +5,6 @@ import {EventEmitter} from "events"; describe("Scenario", async function () { EventEmitter.setMaxListeners(1000); // abstracts.run(); - // tokens.run(); + tokens.run(); utils.run(); }); diff --git a/test/tokens/ERC20/extensions/ERC7818Backlist/addToBlacklist.test.ts b/test/tokens/ERC20/extensions/ERC7818Backlist/addToBlacklist.test.ts index d8584b12..f7931016 100644 --- a/test/tokens/ERC20/extensions/ERC7818Backlist/addToBlacklist.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Backlist/addToBlacklist.test.ts @@ -18,7 +18,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { it("[FAILED] account blacklisted", async function () { const {erc7818Backlist, alice} = await deployERC7818BacklistSelector({epochType}); - await expect(erc7818Backlist.addToBlacklist(alice.address)); + await erc7818Backlist.addToBlacklist(alice.address); await expect(erc7818Backlist.addToBlacklist(alice.address)).to.be.revertedWithCustomError( erc7818Backlist, ERC7818Backlist.errors.AccountBlacklisted, diff --git a/test/utils/LightWeightSortedCircularDoublyLinkedList/LazyShrink.test.ts b/test/utils/LightWeightSortedCircularDoublyLinkedList/LazyShrink.test.ts deleted file mode 100644 index f9b3d6f2..00000000 --- a/test/utils/LightWeightSortedCircularDoublyLinkedList/LazyShrink.test.ts +++ /dev/null @@ -1,154 +0,0 @@ -import {expect} from "chai"; -import {deployLightWeightDoublyListLibrary} from "../../utils.test"; - -export const run = async () => { - describe("LazyShrink", async function () { - it("[HAPPY] lazy shrink correctly of one element", async function () { - const {doublyList} = await deployLightWeightDoublyListLibrary({ - autoList: true, - len: 1, - }); - - // [1:head] => [1, {sentinel:head}] - // [1] => [] - - expect(await doublyList.size()).to.equal(1); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(1); - - await doublyList.lazyShrink(0); - - expect(await doublyList.size()).to.equal(0); - expect(await doublyList.head()).to.equal(0); - expect(await doublyList.tail()).to.equal(0); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(0); - - list = await doublyList.descending(); - expect(list.length).to.equal(0); - - // Since no data has been cleaned, the data should still be in the node. - const node = await doublyList.node(1); - expect(node.prev).to.equal(0); - expect(node.next).to.equal(0); - }); - - it("[HAPPY] lazy shrink correctly of two element", async function () { - const {doublyList} = await deployLightWeightDoublyListLibrary({ - autoList: true, - len: 2, - }); - - // [1:head, 2] => [1, {2:head}] - // [1,2] => [2] - - expect(await doublyList.size()).to.equal(2); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(2); - - await doublyList.lazyShrink(2); - - expect(await doublyList.size()).to.equal(1); - expect(await doublyList.head()).to.equal(2); - expect(await doublyList.tail()).to.equal(2); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(1); - expect(list[0]).to.equal(2); - - list = await doublyList.descending(); - expect(list.length).to.equal(1); - expect(list[0]).to.equal(2); - - // Since no data has been cleaned, the data should still be in the node. - const node = await doublyList.node(1); - expect(node.prev).to.equal(0); - expect(node.next).to.equal(2); - }); - - it("[HAPPY] lazy shrink correctly of even linked list", async function () { - const {doublyList} = await deployLightWeightDoublyListLibrary({ - autoList: true, - len: 10, - }); - - // [1:head, 2, 3, 4, 5, 6, 7, 8, 9, 10] => [1, 2, 3, 4, {5:head, 6, 7, 8, 9, 10}] - // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] => [5, 6, 7, 8, 9, 10] - - expect(await doublyList.size()).to.equal(10); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(10); - - await doublyList.lazyShrink(5); - - expect(await doublyList.size()).to.equal(6); - expect(await doublyList.head()).to.equal(5); - expect(await doublyList.tail()).to.equal(10); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(6); - for (let i = 0; i < 6; i++) { - expect(list[i]).to.equal(5 + i); - } - - list = await doublyList.descending(); - expect(list.length).to.equal(6); - for (let i = 0; i < 6; i++) { - expect(list[i]).to.equal(10 - i); - } - - // Since no data has been cleaned, the data should still be in the node. - for (let i = 0; i < 4; i++) { - const node = await doublyList.node(i + 1); - expect(node.prev).to.equal(i); - expect(node.next).to.equal(i + 2); - } - }); - - it("[HAPPY] lazy shrink correctly of odd linked list", async function () { - const {doublyList} = await deployLightWeightDoublyListLibrary({ - autoList: true, - len: 9, - }); - - // [1:head, 2, 3, 4, 5, 6, 7, 8, 9] => [1, 2, 3, 4, {5:head, 6, 7, 8, 9}] - // [1, 2, 3, 4, 5, 6, 7, 8, 9] => [5, 6, 7, 8, 9] - - expect(await doublyList.size()).to.equal(9); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(9); - - await doublyList.lazyShrink(5); - - expect(await doublyList.size()).to.equal(5); - expect(await doublyList.head()).to.equal(5); - expect(await doublyList.tail()).to.equal(9); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(5); - for (let i = 0; i < 5; i++) { - expect(list[i]).to.equal(5 + i); - } - - list = await doublyList.descending(); - expect(list.length).to.equal(5); - for (let i = 0; i < 5; i++) { - expect(list[i]).to.equal(9 - i); - } - - // Since no data has been cleaned, the data should still be in the node. - for (let i = 0; i < 4; i++) { - const node = await doublyList.node(i + 1); - expect(node.prev).to.equal(i); - expect(node.next).to.equal(i + 2); - } - }); - - it("[UNHAPPY] lazy shrink correctly of empty linked list", async function () { - const {doublyList} = await deployLightWeightDoublyListLibrary({}); - await doublyList.lazyShrink(1); - expect(await doublyList.size()).to.equal(0); - }); - }); -}; diff --git a/test/utils/SortedCircularDoublyLinkedList/LazyShrink.test.ts b/test/utils/SortedCircularDoublyLinkedList/LazyShrink.test.ts deleted file mode 100644 index 940808e1..00000000 --- a/test/utils/SortedCircularDoublyLinkedList/LazyShrink.test.ts +++ /dev/null @@ -1,158 +0,0 @@ -import {expect} from "chai"; -import {deployDoublyListLibrary, padIndexToData} from "../../utils.test"; - -export const run = async () => { - describe("LazyShrink", async function () { - it("[HAPPY] lazy shrink correctly of one element", async function () { - const {doublyList} = await deployDoublyListLibrary({ - autoList: true, - len: 1, - }); - - // [1:head] => [1, {sentinel:head}] - // [1] => [] - - expect(await doublyList.size()).to.equal(1); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(1); - - await doublyList.lazyShrink(0); - - expect(await doublyList.size()).to.equal(0); - expect(await doublyList.head()).to.equal(0); - expect(await doublyList.tail()).to.equal(0); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(0); - - list = await doublyList.descending(); - expect(list.length).to.equal(0); - - // Since no data has been cleaned, the data should still be in the node. - const node = await doublyList.node(1); - expect(node.prev).to.equal(0); - expect(node.next).to.equal(0); - expect(node.data).to.equal(padIndexToData(1)); - }); - - it("[HAPPY] lazy shrink correctly of two element", async function () { - const {doublyList} = await deployDoublyListLibrary({ - autoList: true, - len: 2, - }); - - // [1:head, 2] => [1, {2:head}] - // [1,2] => [2] - - expect(await doublyList.size()).to.equal(2); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(2); - - await doublyList.lazyShrink(2); - - expect(await doublyList.size()).to.equal(1); - expect(await doublyList.head()).to.equal(2); - expect(await doublyList.tail()).to.equal(2); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(1); - expect(list[0]).to.equal(2); - - list = await doublyList.descending(); - expect(list.length).to.equal(1); - expect(list[0]).to.equal(2); - - // Since no data has been cleaned, the data should still be in the node. - const node = await doublyList.node(1); - expect(node.prev).to.equal(0); - expect(node.next).to.equal(2); - expect(node.data).to.equal(padIndexToData(1)); - }); - - it("[HAPPY] lazy shrink correctly of even linked list", async function () { - const {doublyList} = await deployDoublyListLibrary({ - autoList: true, - len: 10, - }); - - // [1:head, 2, 3, 4, 5, 6, 7, 8, 9, 10] => [1, 2, 3, 4, {5:head, 6, 7, 8, 9, 10}] - // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] => [5, 6, 7, 8, 9, 10] - - expect(await doublyList.size()).to.equal(10); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(10); - - await doublyList.lazyShrink(5); - - expect(await doublyList.size()).to.equal(6); - expect(await doublyList.head()).to.equal(5); - expect(await doublyList.tail()).to.equal(10); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(6); - for (let i = 0; i < 6; i++) { - expect(list[i]).to.equal(5 + i); - } - - list = await doublyList.descending(); - expect(list.length).to.equal(6); - for (let i = 0; i < 6; i++) { - expect(list[i]).to.equal(10 - i); - } - - // Since no data has been cleaned, the data should still be in the node. - for (let i = 0; i < 4; i++) { - const node = await doublyList.node(i + 1); - expect(node.prev).to.equal(i); - expect(node.next).to.equal(i + 2); - expect(node.data).to.equal(padIndexToData(i + 1)); - } - }); - - it("[HAPPY] lazy shrink correctly of odd linked list", async function () { - const {doublyList} = await deployDoublyListLibrary({ - autoList: true, - len: 9, - }); - - // [1:head, 2, 3, 4, 5, 6, 7, 8, 9] => [1, 2, 3, 4, {5:head, 6, 7, 8, 9}] - // [1, 2, 3, 4, 5, 6, 7, 8, 9] => [5, 6, 7, 8, 9] - - expect(await doublyList.size()).to.equal(9); - expect(await doublyList.head()).to.equal(1); - expect(await doublyList.tail()).to.equal(9); - - await doublyList.lazyShrink(5); - - expect(await doublyList.size()).to.equal(5); - expect(await doublyList.head()).to.equal(5); - expect(await doublyList.tail()).to.equal(9); - - let list = await doublyList.ascending(); - expect(list.length).to.equal(5); - for (let i = 0; i < 5; i++) { - expect(list[i]).to.equal(5 + i); - } - - list = await doublyList.descending(); - expect(list.length).to.equal(5); - for (let i = 0; i < 5; i++) { - expect(list[i]).to.equal(9 - i); - } - - // Since no data has been cleaned, the data should still be in the node. - for (let i = 0; i < 4; i++) { - const node = await doublyList.node(i + 1); - expect(node.prev).to.equal(i); - expect(node.next).to.equal(i + 2); - expect(node.data).to.equal(padIndexToData(i + 1)); - } - }); - - it("[UNHAPPY] lazy shrink correctly of empty linked list", async function () { - const {doublyList} = await deployDoublyListLibrary({}); - await doublyList.lazyShrink(1); - expect(await doublyList.size()).to.equal(0); - }); - }); -}; diff --git a/test/utils/datatstructures/index.test.ts b/test/utils/algorithms/TLSW/.keep similarity index 100% rename from test/utils/datatstructures/index.test.ts rename to test/utils/algorithms/TLSW/.keep diff --git a/test/utils/datatstructures/SortedList/deployer.test.ts b/test/utils/datatstructures/SortedList/deployer.test.ts index ae32def1..ab34332e 100644 --- a/test/utils/datatstructures/SortedList/deployer.test.ts +++ b/test/utils/datatstructures/SortedList/deployer.test.ts @@ -1,13 +1,13 @@ import {ethers} from "hardhat"; -import {SortedCircularDoublyLinkedListLibrary} from "../../../constant.test"; +import {SortedListLibrary} from "../../../constant.test"; export const deploySortedList = async function (contract: string) { // @TODO selector - if (contract === SortedCircularDoublyLinkedListLibrary.name) { + if (contract === SortedListLibrary.name) { // contract = Xort128.name; } else { // default - contract = SortedCircularDoublyLinkedListLibrary.name; + contract = SortedListLibrary.name; } const [deployer, alice, bob, charlie] = await ethers.getSigners(); const SORTED_LIST = await ethers.getContractFactory(contract, deployer); diff --git a/test/utils/datatstructures/SortedList/index.test.ts b/test/utils/datatstructures/SortedList/index.test.ts index 722ae4fd..e0122e7a 100644 --- a/test/utils/datatstructures/SortedList/index.test.ts +++ b/test/utils/datatstructures/SortedList/index.test.ts @@ -4,7 +4,7 @@ import * as Remove from "./remove.test"; import * as Shrink from "./shrink.test"; export const run = async () => { - describe("Sorted Circular Linked List (SCDLL)", async function () { + describe("SortedList", async function () { General.run(); Insert.run(); Remove.run(); diff --git a/test/utils/datatstructures/XortedList/.keep b/test/utils/datatstructures/XortedList/.keep new file mode 100644 index 00000000..e69de29b diff --git a/test/utils/index.test.ts b/test/utils/index.test.ts index 114a1895..4749825c 100644 --- a/test/utils/index.test.ts +++ b/test/utils/index.test.ts @@ -1,12 +1,12 @@ import * as BLSW from "./algorithms/BLSW/index.test"; +// import * as TLSW from "./algorithms/BLSW/index.test"; import * as SortedList from "./datatstructures/SortedList/index.test"; // import * as XortedList from "./datatstructures/XortedList/index.test"; -// import * as LightWeightSlidingWindow from "./LightWeightSlidingWindow/index.test"; -// import * as SlidingWindow from "./SlidingWindow/index.test"; export const run = async () => { describe("utils", async function () { BLSW.run(); + // TLSW.run(); SortedList.run(); // XortedList.run(); }); From 2c16f4da99978903d6821bbec64f0995f37e262c Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 13 Jan 2025 14:18:38 +0700 Subject: [PATCH 05/53] refactor: remove unused function in ERC721EXP Signed-off-by: MASDXI --- contracts/tokens/ERC721/ERC721EXPBase.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index b040017c..59e9db7c 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -52,15 +52,6 @@ abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { _updateTimestamp(tokenId, start, end); } - // @TODO override update _validation before super._update() - function _update(address to, uint256 tokenId, address auth) internal virtual override returns (address) { - if (_validation(tokenId)) { - return super._update(to, tokenId, auth); - } else { - revert ERC5007TransferredInvalidToken(); - } - } - /// @inheritdoc IERC7858 function startTime(uint256 tokenId) public view virtual override returns (uint256) { return _tokensTimestamp[tokenId].startBlock; From 719d3a34c93a7fea19c3938dc35686bc96c1153e Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 13 Jan 2025 16:55:56 +0700 Subject: [PATCH 06/53] refactor: remove unused file Signed-off-by: MASDXI --- .../ERC721/extensions/ERC721Whitelist.sol | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 contracts/tokens/ERC721/extensions/ERC721Whitelist.sol diff --git a/contracts/tokens/ERC721/extensions/ERC721Whitelist.sol b/contracts/tokens/ERC721/extensions/ERC721Whitelist.sol deleted file mode 100644 index 51a94198..00000000 --- a/contracts/tokens/ERC721/extensions/ERC721Whitelist.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -pragma solidity >=0.8.0 <0.9.0; - -/// @title ERC721EXP Whitelist extension contract -/// @author Kiwari Labs - -import "../ERC721EXPBase.sol"; - -abstract contract ERC721EXPWhitelist is ERC721EXPBase { - /// @notice Emitted when an address is added to the whitelist - /// @param caller Operate by the address - /// @param account The address that was whitelist - event Whitelisted(address indexed caller, address indexed account); - - /// @notice Emitted when an address is removed from the whitelist - /// @param caller Operate by the address - /// @param account The address that was removed from the whitelist - event Unwhitelisted(address indexed caller, address indexed account); - - /// @notice Custom error definitions - error InvalidWhitelistAddress(); - error NotExistInWhitelist(); - error ExistInWhitelist(); - - function _update(address to, uint256 tokenId, address auth) internal override returns (address) { - // override ERC721Base - // if (_whitelist(auth) || _validation(tokenId)) { - // super._update(to, tokenId, auth); - // } else { - // revert - // } - } -} From 4074523eb14840847907e62bc343b84ab4ea2f14 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 14 Jan 2025 21:44:59 +0700 Subject: [PATCH 07/53] feat: update ERC721EXP contracts Signed-off-by: MASDXI --- contracts/tokens/ERC721/ERC721B.sol | 24 +++++++ contracts/tokens/ERC721/ERC721EXPBase.sol | 71 ++++++++----------- contracts/tokens/ERC721/ERC721T.sol | 23 ++++++ .../tokens/ERC721/extensions/IERC7858.sol | 34 +++++---- 4 files changed, 97 insertions(+), 55 deletions(-) create mode 100644 contracts/tokens/ERC721/ERC721B.sol create mode 100644 contracts/tokens/ERC721/ERC721T.sol diff --git a/contracts/tokens/ERC721/ERC721B.sol b/contracts/tokens/ERC721/ERC721B.sol new file mode 100644 index 00000000..701d952a --- /dev/null +++ b/contracts/tokens/ERC721/ERC721B.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +/// @title ERC721EXP using Block-Height-Based +/// @dev ERC721EXP Base contract each token have individual expiration date. +/// @author Kiwari Labs + +import {ERC721EXPBase} from "./ERC721EXPBase.sol"; + +abstract contract ERC721B is ERC721EXPBase { + + function _blockNumberProvider() internal view virtual returns (uint256) { + return block.number; + } + + function _expiryType() internal pure virtual override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.BLOCKS_BASED; + } + + + function _pointerProvider() internal view virtual override returns (uint256) { + return _blockNumberProvider(); + } +} diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 59e9db7c..9604dee4 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -10,70 +10,59 @@ import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { - struct Timestamp { - uint256 startBlock; - uint256 endBlock; + struct AssetStamp { + uint256 start; + uint256 end; } - mapping(uint256 => Timestamp) private _tokensTimestamp; + mapping(uint256 => AssetStamp) private _tokensTimestamp; - /// @notice internal checking before transfer function _validation(uint256 tokenId) internal view returns (bool) { - address owner = _ownerOf(tokenId); - if (owner == address(0)) { - return false; - } - Timestamp memory timestamp = _tokensTimestamp[tokenId]; - uint256 current = block.number; - if (timestamp.startBlock == 0 && timestamp.endBlock == 0) { - // if start and end is 0,0 mean token non-expirable and return true - return true; - } else if (current > timestamp.startBlock || current < timestamp.endBlock) { - // between start and end valid and return true - return true; - } else { - // other case return false + if (_ownerOf(tokenId) == address(0)) return false; + AssetStamp memory timestamp = _tokensTimestamp[tokenId]; + uint256 current = _pointerProvider(); + if (current < timestamp.start || current >= timestamp.end) { return false; } + // if start and end is {0, 0} mean token non-expirable and return true. + return true; } - function _updateTimestamp(uint256 tokenId, uint64 start, uint64 end) internal { + function _updateStamp(uint256 tokenId, uint64 start, uint64 end) internal { if (start >= end) { - // revert ERC5007InvalidTime() + // @TODO revert ERC5007InvalidTime() } - _tokensTimestamp[tokenId].startBlock = start; - _tokensTimestamp[tokenId].endBlock = end; - - // emit tokenTimeSet(tokenId, start, end); + _tokensTimestamp[tokenId].start = start; + _tokensTimestamp[tokenId].end = end; + // @TODO emit tokenTimeSet(tokenId, start, end); } - function _mintWithTime(address to, uint256 tokenId, uint64 start, uint64 end) internal { + function _mintWithStamp(address to, uint256 tokenId, uint64 start, uint64 end) internal { _mint(to, tokenId); - _updateTimestamp(tokenId, start, end); + _updateStamp(tokenId, start, end); + } + + // @TODO function support interface + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Enumerable) returns (bool) { + return interfaceId == type(IERC7858).interfaceId || super.supportsInterface(interfaceId); } /// @inheritdoc IERC7858 function startTime(uint256 tokenId) public view virtual override returns (uint256) { - return _tokensTimestamp[tokenId].startBlock; + return _tokensTimestamp[tokenId].start; } /// @inheritdoc IERC7858 function endTime(uint256 tokenId) public view virtual override returns (uint256) { - return _tokensTimestamp[tokenId].endBlock; - } - - // @TODO function support interface - function supportsInterface(bytes4 interfaceId) public view virtual override(IERC165, ERC721Enumerable) returns (bool) { - return interfaceId == type(IERC7858).interfaceId || super.supportsInterface(interfaceId); + return _tokensTimestamp[tokenId].end; } - // @TODO other useful function like isTokenValid + /// @inheritdoc IERC7858 function isTokenValid(uint256 tokenId) public view returns (bool) { - address owner = _ownerOf(tokenId); - if (owner != address(0)) { - return _validation(tokenId); - } else { - return false; - } + return _validation(tokenId); } + + function _expiryType() internal pure virtual returns (EXPIRY_TYPE) {} + + function _pointerProvider() internal view virtual returns (uint256) {} } diff --git a/contracts/tokens/ERC721/ERC721T.sol b/contracts/tokens/ERC721/ERC721T.sol new file mode 100644 index 00000000..16a3940e --- /dev/null +++ b/contracts/tokens/ERC721/ERC721T.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +/// @title ERC721EXP using Timestamp-Based-Based +/// @dev ERC721EXP Base contract each token have individual expiration date. +/// @author Kiwari Labs + +import {ERC721EXPBase} from "./ERC721EXPBase.sol"; + +abstract contract ERC721T is ERC721EXPBase { + + function _blockTimestampProvider() internal view virtual returns (uint256) { + return block.timestamp; + } + + function _expiryType() internal pure virtual override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.TIME_BASED; + } + + function _pointerProvider() internal view virtual override returns (uint256) { + return _blockTimestampProvider(); + } +} diff --git a/contracts/tokens/ERC721/extensions/IERC7858.sol b/contracts/tokens/ERC721/extensions/IERC7858.sol index 6a03cae9..324b2b79 100644 --- a/contracts/tokens/ERC721/extensions/IERC7858.sol +++ b/contracts/tokens/ERC721/extensions/IERC7858.sol @@ -1,23 +1,29 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC-5007 modified to support block-based +/// @title ERC-7858 modified to support block-based -import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +interface IERC7858 { + enum EXPIRY_TYPE { + BLOCKS_BASED, // block.number + TIME_BASED // block.timestamp + } + + /** + * @dev Returns the type of the expiry. + * @return EXPIRY_TYPE Enum value indicating the unit of an expiry. + */ + function expiryType() external view returns (EXPIRY_TYPE); -interface IERC7858 is IERC721 { + /** + * @dev Checks whether a specific token is expired. + * @param Id The identifier representing the `tokenId` (ERC721). + * @return bool True if the token is expired, false otherwise. + */ + function isTokenValid(uint256 Id) external view returns (bool); - error ERC5007TransferredInvalidToken(); - - /// @param tokenId The id of the token. - /// @dev Retrieve the start time of the NFT as a block number. - /// @return uint256 Returns the start time in block number. + // inherit from ERC-5007 return depends on the type `block.timestamp` or `block.number` + // {ERC-5007} return in uint64 MAY not suitable for `block.number` based. function startTime(uint256 tokenId) external view returns (uint256); - - /// @param tokenId The id of the token. - /// @dev Retrieve the end time of the NFT as a block number. - /// @return uint256 Returns the end time in block number. function endTime(uint256 tokenId) external view returns (uint256); - - function isTokenValid(uint256 tokenId) external view returns (bool); } From 6c25951001010ac8dccf84c04914bedd840805f4 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 14 Jan 2025 21:49:05 +0700 Subject: [PATCH 08/53] prettier code Signed-off-by: MASDXI --- contracts/tokens/ERC721/ERC721B.sol | 2 -- contracts/tokens/ERC721/ERC721EXPBase.sol | 2 +- contracts/tokens/ERC721/ERC721T.sol | 1 - contracts/tokens/ERC721/extensions/IERC7858.sol | 2 +- test/constant.test.ts | 2 +- test/utils/datatstructures/SortedList/remove.test.ts | 2 +- test/utils/datatstructures/SortedList/shrink.test.ts | 2 +- 7 files changed, 5 insertions(+), 8 deletions(-) diff --git a/contracts/tokens/ERC721/ERC721B.sol b/contracts/tokens/ERC721/ERC721B.sol index 701d952a..e7a70ed3 100644 --- a/contracts/tokens/ERC721/ERC721B.sol +++ b/contracts/tokens/ERC721/ERC721B.sol @@ -8,7 +8,6 @@ pragma solidity >=0.8.0 <0.9.0; import {ERC721EXPBase} from "./ERC721EXPBase.sol"; abstract contract ERC721B is ERC721EXPBase { - function _blockNumberProvider() internal view virtual returns (uint256) { return block.number; } @@ -16,7 +15,6 @@ abstract contract ERC721B is ERC721EXPBase { function _expiryType() internal pure virtual override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; } - function _pointerProvider() internal view virtual override returns (uint256) { return _blockNumberProvider(); diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 9604dee4..1fb6c956 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -25,7 +25,7 @@ abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { return false; } // if start and end is {0, 0} mean token non-expirable and return true. - return true; + return true; } function _updateStamp(uint256 tokenId, uint64 start, uint64 end) internal { diff --git a/contracts/tokens/ERC721/ERC721T.sol b/contracts/tokens/ERC721/ERC721T.sol index 16a3940e..8a068e60 100644 --- a/contracts/tokens/ERC721/ERC721T.sol +++ b/contracts/tokens/ERC721/ERC721T.sol @@ -8,7 +8,6 @@ pragma solidity >=0.8.0 <0.9.0; import {ERC721EXPBase} from "./ERC721EXPBase.sol"; abstract contract ERC721T is ERC721EXPBase { - function _blockTimestampProvider() internal view virtual returns (uint256) { return block.timestamp; } diff --git a/contracts/tokens/ERC721/extensions/IERC7858.sol b/contracts/tokens/ERC721/extensions/IERC7858.sol index 324b2b79..2d62d4eb 100644 --- a/contracts/tokens/ERC721/extensions/IERC7858.sol +++ b/contracts/tokens/ERC721/extensions/IERC7858.sol @@ -8,7 +8,7 @@ interface IERC7858 { BLOCKS_BASED, // block.number TIME_BASED // block.timestamp } - + /** * @dev Returns the type of the expiry. * @return EXPIRY_TYPE Enum value indicating the unit of an expiry. diff --git a/test/constant.test.ts b/test/constant.test.ts index a6d87443..8281011d 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -218,6 +218,6 @@ export const contracts = { utils: { SlidingWindowLibrary, SortedListLibrary, - XortedListLibrary + XortedListLibrary, }, }; diff --git a/test/utils/datatstructures/SortedList/remove.test.ts b/test/utils/datatstructures/SortedList/remove.test.ts index 0ec92293..fd55eec0 100644 --- a/test/utils/datatstructures/SortedList/remove.test.ts +++ b/test/utils/datatstructures/SortedList/remove.test.ts @@ -35,7 +35,7 @@ export const run = async () => { it("[SUCCESS] remove between front and back", async function () { const {sortedlist} = await deploySortedList(""); await sortedlist.insert(front); - await sortedlist.insert(element) + await sortedlist.insert(element); await sortedlist.insert(back); expect(await sortedlist.contains(element)).to.equal(true); await sortedlist.remove(element); diff --git a/test/utils/datatstructures/SortedList/shrink.test.ts b/test/utils/datatstructures/SortedList/shrink.test.ts index 16a7ade2..a9209d40 100644 --- a/test/utils/datatstructures/SortedList/shrink.test.ts +++ b/test/utils/datatstructures/SortedList/shrink.test.ts @@ -55,7 +55,7 @@ export const run = async () => { it("[FAILED] shrink the ignore element", async function () { const {sortedlist} = await deploySortedList(""); await sortedlist.insert(front); - await sortedlist.insert(element) + await sortedlist.insert(element); await sortedlist.insert(back); expect(await sortedlist.front()).to.equal(front); expect(await sortedlist.contains(element)).to.equal(true); From 29302affabfe7575380ef6a9d1131874a8b99896 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 14 Jan 2025 21:50:29 +0700 Subject: [PATCH 09/53] chore: change path Signed-off-by: MASDXI --- contracts/tokens/ERC721/ERC721EXPBase.sol | 2 +- contracts/tokens/ERC721/extensions/.keep | 0 contracts/tokens/ERC721/{extensions => interfaces}/IERC7858.sol | 0 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 contracts/tokens/ERC721/extensions/.keep rename contracts/tokens/ERC721/{extensions => interfaces}/IERC7858.sol (100%) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 1fb6c956..c642198f 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -5,7 +5,7 @@ pragma solidity >=0.8.0 <0.9.0; /// @dev ERC721EXP Base contract each token have individual expiration date. /// @author Kiwari Labs -import {IERC7858} from "./extensions/IERC7858.sol"; +import {IERC7858} from "./interfaces/IERC7858.sol"; import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; diff --git a/contracts/tokens/ERC721/extensions/.keep b/contracts/tokens/ERC721/extensions/.keep new file mode 100644 index 00000000..e69de29b diff --git a/contracts/tokens/ERC721/extensions/IERC7858.sol b/contracts/tokens/ERC721/interfaces/IERC7858.sol similarity index 100% rename from contracts/tokens/ERC721/extensions/IERC7858.sol rename to contracts/tokens/ERC721/interfaces/IERC7858.sol From 61b9c755f94365ddd30b7c874dc2d3de3dba8769 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 15 Jan 2025 10:26:38 +0700 Subject: [PATCH 10/53] chore: adding interfaces ERC-7858Epoch Signed-off-by: MASDXI --- .../ERC721/interfaces/IERC7858Epoch.sol | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol diff --git a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol new file mode 100644 index 00000000..29cbc626 --- /dev/null +++ b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +/// @title ERC-7858 Epoch + +import {IERC7858} from "./IERC7858.sol"; + +interface IERC7858Epoch is IERC7858 { + /** + * @dev Retrieves the balance of a specific `epoch` owned by an account. + * @param epoch The `epoch for which the balance is checked. + * @param account The address of the account. + * @return uint256 The balance of the specified `epoch`. + * @notice "MUST" return 0 if the specified `epoch` is expired. + */ + function balanceOfAtEpoch(uint256 epoch, address account) external view returns (uint256); + + /** + * @dev Retrieves the current epoch of the contract. + * @return uint256 The current epoch of the token contract, + * often used for determining active/expired states. + */ + function currentEpoch() external view returns (uint256); + + /** + * @dev Retrieves the duration of a single epoch. + * @return uint256 The duration of a single epoch. + * @notice The unit of the epoch length is determined by the `validityPeriodType` function. + */ + function epochLength() external view returns (uint256); + + /** + * @dev Returns the type of the epoch. + * @return EPOCH_TYPE Enum value indicating the unit of an epoch. + */ + function epochType() external view returns (EPOCH_TYPE); + + /** + * @dev Retrieves the validity duration of each token. + * @return uint256 The validity duration of each token in `epoch` unit. + */ + function validityDuration() external view returns (uint256); + + /** + * @dev Checks whether a specific `epoch` is expired. + * @param epoch The `epoch` to check. + * @return bool True if the token is expired, false otherwise. + * @notice Implementing contracts "MUST" define and document the logic for determining expiration, + * typically by comparing the latest epoch with the given `epoch` value, + * based on the `EPOCH_TYPE` measurement (e.g., block count or time duration). + */ + function isEpochExpired(uint256 epoch) external view returns (bool); +} \ No newline at end of file From 5e36db4400d91f7a6cb39a0368801479e115436d Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 15 Jan 2025 12:57:20 +0700 Subject: [PATCH 11/53] refactor: adding ERC721Epoch abstract Signed-off-by: MASDXI --- contracts/tokens/ERC721/ERC721B.sol | 2 ++ contracts/tokens/ERC721/ERC721EXPBase.sol | 23 ++++++++++++++----- contracts/tokens/ERC721/ERC721T.sol | 2 ++ contracts/tokens/ERC721/extensions/.keep | 0 .../tokens/ERC721/extensions/ERC721BLSW.sol | 12 ++++++++++ .../tokens/ERC721/extensions/ERC721TLSW.sol | 12 ++++++++++ .../ERC721/interfaces/IERC7858Epoch.sol | 10 ++++---- 7 files changed, 50 insertions(+), 11 deletions(-) delete mode 100644 contracts/tokens/ERC721/extensions/.keep create mode 100644 contracts/tokens/ERC721/extensions/ERC721BLSW.sol create mode 100644 contracts/tokens/ERC721/extensions/ERC721TLSW.sol diff --git a/contracts/tokens/ERC721/ERC721B.sol b/contracts/tokens/ERC721/ERC721B.sol index e7a70ed3..c25f1b23 100644 --- a/contracts/tokens/ERC721/ERC721B.sol +++ b/contracts/tokens/ERC721/ERC721B.sol @@ -8,6 +8,8 @@ pragma solidity >=0.8.0 <0.9.0; import {ERC721EXPBase} from "./ERC721EXPBase.sol"; abstract contract ERC721B is ERC721EXPBase { + constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} + function _blockNumberProvider() internal view virtual returns (uint256) { return block.number; } diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index c642198f..5e90a410 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -6,10 +6,11 @@ pragma solidity >=0.8.0 <0.9.0; /// @author Kiwari Labs import {IERC7858} from "./interfaces/IERC7858.sol"; +import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { +abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { struct AssetStamp { uint256 start; uint256 end; @@ -17,6 +18,21 @@ abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { mapping(uint256 => AssetStamp) private _tokensTimestamp; + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} + + function _update(address to, uint256 tokenId, address auth) internal override(ERC721, ERC721Enumerable) returns (address) { + return super._update(to, tokenId, auth); + } + + function _increaseBalance(address account, uint128 value) internal override(ERC721, ERC721Enumerable) { + super._increaseBalance(account, value); + } + + // @TODO function support interface + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { + return interfaceId == type(IERC7858).interfaceId || super.supportsInterface(interfaceId); + } + function _validation(uint256 tokenId) internal view returns (bool) { if (_ownerOf(tokenId) == address(0)) return false; AssetStamp memory timestamp = _tokensTimestamp[tokenId]; @@ -42,11 +58,6 @@ abstract contract ERC721EXPBase is ERC721Enumerable, IERC7858 { _updateStamp(tokenId, start, end); } - // @TODO function support interface - function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Enumerable) returns (bool) { - return interfaceId == type(IERC7858).interfaceId || super.supportsInterface(interfaceId); - } - /// @inheritdoc IERC7858 function startTime(uint256 tokenId) public view virtual override returns (uint256) { return _tokensTimestamp[tokenId].start; diff --git a/contracts/tokens/ERC721/ERC721T.sol b/contracts/tokens/ERC721/ERC721T.sol index 8a068e60..1f6e8c17 100644 --- a/contracts/tokens/ERC721/ERC721T.sol +++ b/contracts/tokens/ERC721/ERC721T.sol @@ -8,6 +8,8 @@ pragma solidity >=0.8.0 <0.9.0; import {ERC721EXPBase} from "./ERC721EXPBase.sol"; abstract contract ERC721T is ERC721EXPBase { + constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} + function _blockTimestampProvider() internal view virtual returns (uint256) { return block.timestamp; } diff --git a/contracts/tokens/ERC721/extensions/.keep b/contracts/tokens/ERC721/extensions/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol b/contracts/tokens/ERC721/extensions/ERC721BLSW.sol new file mode 100644 index 00000000..fc90367a --- /dev/null +++ b/contracts/tokens/ERC721/extensions/ERC721BLSW.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +/// @title ERC721EXP using Block-Height-Based Lazy Sliding Window (BLSW) Algorithm. +/// @author Kiwari Labs + + +import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; + +abstract contract ERC721BLSW is IERC7858Epoch { + // @TODO +} \ No newline at end of file diff --git a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol b/contracts/tokens/ERC721/extensions/ERC721TLSW.sol new file mode 100644 index 00000000..6bb51b75 --- /dev/null +++ b/contracts/tokens/ERC721/extensions/ERC721TLSW.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +/// @title ERC721EXP using Timestamp-Based Lazy Sliding Window (TLSW) Algorithm. +/// @author Kiwari Labs + + +import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; + +abstract contract ERC721TLSW is IERC7858Epoch { + // @TODO +} \ No newline at end of file diff --git a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol index 29cbc626..f4df6a0a 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol @@ -28,19 +28,19 @@ interface IERC7858Epoch is IERC7858 { * @notice The unit of the epoch length is determined by the `validityPeriodType` function. */ function epochLength() external view returns (uint256); - + /** * @dev Returns the type of the epoch. - * @return EPOCH_TYPE Enum value indicating the unit of an epoch. + * @return EXPIRY_TYPE Enum value indicating the unit of an epoch. */ - function epochType() external view returns (EPOCH_TYPE); + function epochType() external view returns (EXPIRY_TYPE); /** * @dev Retrieves the validity duration of each token. * @return uint256 The validity duration of each token in `epoch` unit. */ function validityDuration() external view returns (uint256); - + /** * @dev Checks whether a specific `epoch` is expired. * @param epoch The `epoch` to check. @@ -50,4 +50,4 @@ interface IERC7858Epoch is IERC7858 { * based on the `EPOCH_TYPE` measurement (e.g., block count or time duration). */ function isEpochExpired(uint256 epoch) external view returns (bool); -} \ No newline at end of file +} From 42cb5d2b95bfd1b9a40d81db91d8bcfeed7fa420 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Thu, 16 Jan 2025 14:24:04 +0700 Subject: [PATCH 12/53] refactor: ERC721Epoch Signed-off-by: MASDXI --- .../tokens/ERC721/extensions/ERC721BLSW.sol | 43 +++- .../ERC721/extensions/ERC721EpochBase.sol | 224 ++++++++++++++++++ .../tokens/ERC721/extensions/ERC721TLSW.sol | 43 +++- 3 files changed, 302 insertions(+), 8 deletions(-) create mode 100644 contracts/tokens/ERC721/extensions/ERC721EpochBase.sol diff --git a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol b/contracts/tokens/ERC721/extensions/ERC721BLSW.sol index fc90367a..8e665b9b 100644 --- a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721BLSW.sol @@ -4,9 +4,44 @@ pragma solidity >=0.8.0 <0.9.0; /// @title ERC721EXP using Block-Height-Based Lazy Sliding Window (BLSW) Algorithm. /// @author Kiwari Labs +import {AbstractBLSW as BLSW} from "../../../abstracts/AbstractBLSW.sol"; +import {ERC721EpochBase} from "./ERC721EpochBase.sol"; -import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; +abstract contract ERC721BLSW is ERC721EpochBase, BLSW { + constructor( + string memory name_, + string memory symbol_, + uint256 initialBlockNumber_, + uint40 blocksPerEpoch_, + uint8 windowSize_, + bool development_ + ) ERC721EpochBase(name_, symbol_) BLSW(initialBlockNumber_, blocksPerEpoch_, windowSize_, development_) {} -abstract contract ERC721BLSW is IERC7858Epoch { - // @TODO -} \ No newline at end of file + function _epochType() internal pure virtual override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.BLOCKS_BASED; + } + + function _getEpoch(uint256 pointer) internal view virtual override returns (uint256) { + return _epoch(pointer); + } + + function _getWindowRage(uint256 pointer) internal view virtual override returns (uint256 fromEpoch, uint256 toEpoch) { + return _windowRage(pointer); + } + + function _getWindowSize() internal view virtual override returns (uint8) { + return _windowSize(); + } + + function _getPointersInEpoch() internal view virtual override returns (uint40) { + return _blocksInEpoch(); + } + + function _getPointersInWindow() internal view virtual override returns (uint40) { + return _blocksInWindow(); + } + + function _pointerProvider() internal view virtual override returns (uint256) { + return _blockNumberProvider(); + } +} diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol new file mode 100644 index 00000000..9f543980 --- /dev/null +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -0,0 +1,224 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity >=0.8.0 <0.9.0; + +/// @title ERC721Epoch base +/// @author Kiwari Labs + +import {SortedList} from "../../../utils/datastructures/SortedList.sol"; +import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; +import {Context} from "@openzeppelin/contracts/utils/Context.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {IERC165, ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import {ERC721Utils} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Utils.sol"; +import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; +import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; +import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; + +// @TODO following https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/ERC721.sol + +abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Metadata, IERC7858Epoch { + using SortedList for SortedList.List; + using Strings for uint256; + + string private _name; + string private _symbol; + + struct Epoch { + uint256 totalBalance; + mapping(uint256 => uint256[]) tokens; // it's possible to contains more than one tokenId. + SortedList.List list; + } + + mapping(uint256 tokenId => address) private _owners; + mapping(uint256 => mapping(address => Epoch)) private _balances; + mapping(uint256 tokenId => address) private _tokenApprovals; + mapping(address owner => mapping(address operator => bool)) private _operatorApprovals; + + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } + + /** + * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist + * + * IMPORTANT: Any overrides to this function that add ownership of tokens not tracked by the + * core ERC-721 logic MUST be matched with the use of {_increaseBalance} to keep balances + * consistent with ownership. The invariant to preserve is that for any address `a` the value returned by + * `balanceOf(a)` must be equal to the number of tokens such that `_ownerOf(tokenId)` is `a`. + */ + function _ownerOf(uint256 tokenId) internal view virtual returns (address) { + return _owners[tokenId]; + } + + function _computeBalanceOverEpochRange(uint256 fromEpoch, uint256 toEpoch, address account) private view returns (uint256 balance) { + unchecked { + for (; fromEpoch <= toEpoch; fromEpoch++) { + balance += _balances[fromEpoch][account].totalBalance; + } + } + } + + function _computeBalanceAtEpoch( + uint256 epoch, + address account, + uint256 pointer, + uint256 duration + ) private view returns (uint256 balance) { + (uint256 element, uint256 value) = _findValidBalance(account, epoch, pointer, duration); + Epoch storage _account = _balances[epoch][account]; + unchecked { + balance = value; + while (element > 0) { + balance += _account.tokens[element].length; + element = _account.list.next(element); + } + } + return balance; + } + + function _findValidBalance( + address account, + uint256 epoch, + uint256 pointer, + uint256 duration + ) internal view returns (uint256 element, uint256 value) { + SortedList.List storage list = _balances[epoch][account].list; + if (!list.isEmpty()) { + element = list.front(); + unchecked { + while (pointer - element >= duration) { + element = list.next(element); + } + } + value = _balances[epoch][account].tokens[element].length; + } + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { + return + interfaceId == type(IERC721).interfaceId || + interfaceId == type(IERC721Metadata).interfaceId || + interfaceId == type(IERC7858Epoch).interfaceId || + super.supportsInterface(interfaceId); + } + + function balanceOf(address owner) public view virtual returns (uint256) { + if (owner == address(0)) { + revert ERC721InvalidOwner(address(0)); + } + // uint256 pointer = _pointerProvider(); + // (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); + // uint256 balance = _computeBalanceAtEpoch(fromEpoch, account, pointer, _getPointersInWindow()); + // if (fromEpoch == toEpoch) { + // return balance; + // } else { + // fromEpoch += 1; + // } + // balance += _computeBalanceOverEpochRange(fromEpoch, toEpoch, account); + // return balance; + } + + /** + * @dev See {IERC721-ownerOf}. + */ + function ownerOf(uint256 tokenId) public view virtual returns (address) { + return _requireOwned(tokenId); + } + + /** + * @dev See {IERC721Metadata-name}. + */ + function name() public view virtual returns (string memory) { + return _name; + } + + /** + * @dev See {IERC721Metadata-symbol}. + */ + function symbol() public view virtual returns (string memory) { + return _symbol; + } + + /** + * @dev See {IERC721Metadata-tokenURI}. + */ + function tokenURI(uint256 tokenId) public view virtual returns (string memory) { + _requireOwned(tokenId); + + string memory baseURI = _baseURI(); + return bytes(baseURI).length > 0 ? string.concat(baseURI, tokenId.toString()) : ""; + } + + /** + * @dev Reverts if the `tokenId` doesn't have a current owner (it hasn't been minted, or it has been burned). + * Returns the owner. + * + * Overrides to ownership logic should be done to {_ownerOf}. + */ + function _requireOwned(uint256 tokenId) internal view returns (address) { + address owner = _ownerOf(tokenId); + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + return owner; + } + + function _expired(uint256 epoch) internal view returns (bool) { + unchecked { + (uint256 fromEpoch, ) = _getWindowRage(_pointerProvider()); + if (epoch < fromEpoch) { + return true; + } + return false; + } + } + + /** + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overridden in child contracts. + */ + function _baseURI() internal view virtual returns (string memory) { + return ""; + } + + /// @dev See {IERC7858Epoch-currentEpoch}. + function currentEpoch() public view virtual returns (uint256) { + return _getEpoch(_pointerProvider()); + } + + /// @dev See {IERC7858Epoch-epochLength}. + function epochLength() public view virtual returns (uint256) { + return _getPointersInEpoch(); + } + + /// @dev See {IERC7858Epoch-epochType}. + function epochType() public pure returns (EXPIRY_TYPE) { + return _epochType(); + } + + /// @dev See {IERC7858Epoch-validityDuration}. + function validityDuration() public view virtual returns (uint256) { + return _getWindowSize(); + } + + /// @dev See {IERC7858Epoch-isEpochExpired}. + function isEpochExpired(uint256 id) public view virtual returns (bool) { + return _expired(id); + } + + function _epochType() internal pure virtual returns (EXPIRY_TYPE) {} + + function _getEpoch(uint256 pointer) internal view virtual returns (uint256) {} + + function _getWindowRage(uint256 pointer) internal view virtual returns (uint256 fromEpoch, uint256 toEpoch) {} + + function _getWindowSize() internal view virtual returns (uint8) {} + + function _getPointersInEpoch() internal view virtual returns (uint40) {} + + function _getPointersInWindow() internal view virtual returns (uint40) {} + + function _pointerProvider() internal view virtual returns (uint256) {} +} diff --git a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol b/contracts/tokens/ERC721/extensions/ERC721TLSW.sol index 6bb51b75..c1e6715f 100644 --- a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721TLSW.sol @@ -4,9 +4,44 @@ pragma solidity >=0.8.0 <0.9.0; /// @title ERC721EXP using Timestamp-Based Lazy Sliding Window (TLSW) Algorithm. /// @author Kiwari Labs +import {AbstractTLSW as TLSW} from "../../../abstracts/AbstractTLSW.sol"; +import {ERC721EpochBase} from "./ERC721EpochBase.sol"; -import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; +abstract contract ERC721TLSW is ERC721EpochBase, TLSW { + constructor( + string memory name_, + string memory symbol_, + uint256 initialBlockTimestamp_, + uint40 secondsPerEpoch_, + uint8 windowSize_, + bool development_ + ) ERC721EpochBase(name_, symbol_) TLSW(initialBlockTimestamp_, secondsPerEpoch_, windowSize_, development_) {} -abstract contract ERC721TLSW is IERC7858Epoch { - // @TODO -} \ No newline at end of file + function _epochType() internal pure virtual override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.TIME_BASED; + } + + function _getEpoch(uint256 pointer) internal view virtual override returns (uint256) { + return _epoch(pointer); + } + + function _getWindowRage(uint256 pointer) internal view virtual override returns (uint256 fromEpoch, uint256 toEpoch) { + return _windowRage(pointer); + } + + function _getWindowSize() internal view virtual override returns (uint8) { + return _windowSize(); + } + + function _getPointersInEpoch() internal view virtual override returns (uint40) { + return _secondsInEpoch(); + } + + function _getPointersInWindow() internal view virtual override returns (uint40) { + return _secondsInWindow(); + } + + function _pointerProvider() internal view virtual override returns (uint256) { + return _blockTimestampProvider(); + } +} From dfa828233765a80f2c5e79a0d153f84208cd8767 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Thu, 16 Jan 2025 14:44:25 +0700 Subject: [PATCH 13/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20ERC20NearestEx?= =?UTF-8?q?piry=20func=20name=20and=20ERC721Epoch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tokens/ERC20/extensions/ERC7818NearestExpiryQuery.sol | 2 +- contracts/tokens/ERC721/ERC721B.sol | 8 +++++--- contracts/tokens/ERC721/ERC721EXPBase.sol | 8 +++++--- contracts/tokens/ERC721/ERC721T.sol | 8 +++++--- contracts/tokens/ERC721/extensions/ERC721BLSW.sol | 6 ++++-- contracts/tokens/ERC721/extensions/ERC721EpochBase.sol | 6 ++++-- contracts/tokens/ERC721/extensions/ERC721TLSW.sol | 6 ++++-- .../ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts | 4 ++-- 8 files changed, 30 insertions(+), 18 deletions(-) diff --git a/contracts/tokens/ERC20/extensions/ERC7818NearestExpiryQuery.sol b/contracts/tokens/ERC20/extensions/ERC7818NearestExpiryQuery.sol index 30084e1f..325dfb73 100644 --- a/contracts/tokens/ERC20/extensions/ERC7818NearestExpiryQuery.sol +++ b/contracts/tokens/ERC20/extensions/ERC7818NearestExpiryQuery.sol @@ -14,7 +14,7 @@ abstract contract ERC7818NearestExpiryQuery is ERC20EXPBase { * @return value The balance value associated with the nearest expiry. * @return estimateExpiry The estimated expiry timestamp or block number for the account's balance. */ - function nearestExpiryOf(address account) public view returns (uint256 value, uint256 estimateExpiry) { + function getNearestExpiryOf(address account) public view returns (uint256 value, uint256 estimateExpiry) { uint256 pointer = _pointerProvider(); (uint256 fromEpoch, ) = _getWindowRage(pointer); uint256 duration = _getPointersInWindow(); diff --git a/contracts/tokens/ERC721/ERC721B.sol b/contracts/tokens/ERC721/ERC721B.sol index c25f1b23..3fc7a0a2 100644 --- a/contracts/tokens/ERC721/ERC721B.sol +++ b/contracts/tokens/ERC721/ERC721B.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC721EXP using Block-Height-Based -/// @dev ERC721EXP Base contract each token have individual expiration date. -/// @author Kiwari Labs +/** + * @title ERC721EXP using Block-Height-Based + * @dev ERC721EXP Base contract each token have individual expiration date. + * @author Kiwari Labs + */ import {ERC721EXPBase} from "./ERC721EXPBase.sol"; diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 5e90a410..2c686d2b 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC721EXP Base -/// @dev ERC721EXP Base contract each token have individual expiration date. -/// @author Kiwari Labs +/** + * @title ERC721EXP Base + * @dev ERC721EXP Base contract each token have individual expiration date. + * @author Kiwari Labs + */ import {IERC7858} from "./interfaces/IERC7858.sol"; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; diff --git a/contracts/tokens/ERC721/ERC721T.sol b/contracts/tokens/ERC721/ERC721T.sol index 1f6e8c17..5cbac72d 100644 --- a/contracts/tokens/ERC721/ERC721T.sol +++ b/contracts/tokens/ERC721/ERC721T.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC721EXP using Timestamp-Based-Based -/// @dev ERC721EXP Base contract each token have individual expiration date. -/// @author Kiwari Labs +/** + * @title ERC721EXP using Timestamp-Based-Based + * @dev ERC721EXP Base contract each token have individual expiration date. + * @author Kiwari Labs + */ import {ERC721EXPBase} from "./ERC721EXPBase.sol"; diff --git a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol b/contracts/tokens/ERC721/extensions/ERC721BLSW.sol index 8e665b9b..ae943006 100644 --- a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721BLSW.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC721EXP using Block-Height-Based Lazy Sliding Window (BLSW) Algorithm. -/// @author Kiwari Labs +/** + * @title ERC721EXP using Block-Height-Based Lazy Sliding Window (BLSW) Algorithm. + * @author Kiwari Labs + */ import {AbstractBLSW as BLSW} from "../../../abstracts/AbstractBLSW.sol"; import {ERC721EpochBase} from "./ERC721EpochBase.sol"; diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 9f543980..9fc4051f 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC721Epoch base -/// @author Kiwari Labs +/** + * @title ERC721Epoch base + * @author Kiwari Labs + */ import {SortedList} from "../../../utils/datastructures/SortedList.sol"; import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; diff --git a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol b/contracts/tokens/ERC721/extensions/ERC721TLSW.sol index c1e6715f..a2a9e791 100644 --- a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721TLSW.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity >=0.8.0 <0.9.0; -/// @title ERC721EXP using Timestamp-Based Lazy Sliding Window (TLSW) Algorithm. -/// @author Kiwari Labs +/** + * @title ERC721EXP using Timestamp-Based Lazy Sliding Window (TLSW) Algorithm. + * @author Kiwari Labs + */ import {AbstractTLSW as TLSW} from "../../../abstracts/AbstractTLSW.sol"; import {ERC721EpochBase} from "./ERC721EpochBase.sol"; diff --git a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts index 9b7c4f8c..2225a2c9 100644 --- a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts @@ -18,14 +18,14 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { (await erc7818NearestExpiryQuery.epochLength()) * (await erc7818NearestExpiryQuery.validityDuration()), ); const latestPointer = await hardhat_latestPointer(epochType); - const [balance, expiry] = await erc7818NearestExpiryQuery.nearestExpiryOf(alice.address); + const [balance, expiry] = await erc7818NearestExpiryQuery.getNearestExpiryOf(alice.address); expect(balance).to.equal(amount); expect(expiry).to.equal(latestPointer + pointerInWindow); }); it("[SUCCESS] nearest expiry query empty", async function () { const {erc7818NearestExpiryQuery, alice} = await deployERC7818NearestExpiryQuerySelector({epochType}); - const [balance, expiry] = await erc7818NearestExpiryQuery.nearestExpiryOf(alice.address); + const [balance, expiry] = await erc7818NearestExpiryQuery.getNearestExpiryOf(alice.address); expect(balance).to.equal(0); expect(expiry).to.equal(0); }); From d113b756c3f66d11447950b6dab3d060c98e0beb Mon Sep 17 00:00:00 2001 From: MASDXI Date: Fri, 17 Jan 2025 18:07:28 +0700 Subject: [PATCH 14/53] =?UTF-8?q?chore:=20=F0=9F=A4=96=20generate=20copyri?= =?UTF-8?q?ght/license=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mocks/data/asc_273_number.ts | 5 +++++ mocks/data/shuffle_273_number.ts | 5 +++++ scripts/demo/1_demo.ts | 5 +++++ test/constant.test.ts | 5 +++++ test/index.test.ts | 5 +++++ test/tokens/ERC20/ERC7818Behavior/burn.test.ts | 5 +++++ test/tokens/ERC20/ERC7818Behavior/index.test.ts | 5 +++++ test/tokens/ERC20/ERC7818Behavior/interface.test.ts | 5 +++++ test/tokens/ERC20/ERC7818Behavior/transfer.test.ts | 5 +++++ test/tokens/ERC20/ERC7818Behavior/transferAtEpoch.test.ts | 5 +++++ .../tokens/ERC20/ERC7818Behavior/transferFromAtEpoch.test.ts | 5 +++++ test/tokens/ERC20/base/approve.test.ts | 5 +++++ test/tokens/ERC20/base/burn.test.ts | 5 +++++ test/tokens/ERC20/base/deployer.test.ts | 5 +++++ test/tokens/ERC20/base/index.test.ts | 5 +++++ test/tokens/ERC20/base/mint.test.ts | 5 +++++ test/tokens/ERC20/base/transfer.test.ts | 5 +++++ test/tokens/ERC20/base/transferFrom.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Blacklist/addToBlacklist.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Blacklist/burn.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Blacklist/deployer.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Blacklist/index.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Blacklist/mint.test.ts | 5 +++++ .../extensions/ERC7818Blacklist/removeFromBlacklist.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Blacklist/transfer.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/burn.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/deployer.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/freeze.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/index.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/mint.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/transfer.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Frozen/unfreeze.test.ts | 5 +++++ .../ERC20/extensions/ERC7818MintQuota/addMinter.test.ts | 5 +++++ .../ERC20/extensions/ERC7818MintQuota/decreaseQuota.test.ts | 5 +++++ .../ERC20/extensions/ERC7818MintQuota/deployer.test.ts | 5 +++++ .../ERC20/extensions/ERC7818MintQuota/increaseQuota.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818MintQuota/index.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818MintQuota/mint.test.ts | 5 +++++ .../ERC20/extensions/ERC7818MintQuota/removeMinter.test.ts | 5 +++++ .../ERC20/extensions/ERC7818MintQuota/setQuota.test.ts | 5 +++++ .../extensions/ERC7818NearestExpiryQuery/deployer.test.ts | 5 +++++ .../ERC20/extensions/ERC7818NearestExpiryQuery/index.test.ts | 5 +++++ .../ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Whitelist/addToWhitelist.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Whitelist/burn.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Whitelist/deployer.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Whitelist/index.test.ts | 5 +++++ test/tokens/ERC20/extensions/ERC7818Whitelist/mint.test.ts | 5 +++++ .../extensions/ERC7818Whitelist/removeFromWhitelist.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Whitelist/transfer.test.ts | 5 +++++ .../ERC20/extensions/ERC7818Whitelist/transferFrom.test.ts | 5 +++++ test/tokens/ERC20/extensions/index.test.ts | 5 +++++ test/tokens/ERC20/index.test.ts | 5 +++++ test/tokens/ERC721/index.test.ts | 5 +++++ test/tokens/index.test.ts | 5 +++++ test/utils.test.ts | 5 +++++ test/utils/algorithms/BLSW/deployer.test.ts | 5 +++++ test/utils/algorithms/BLSW/epoch.test.ts | 5 +++++ test/utils/algorithms/BLSW/general.test.ts | 5 +++++ test/utils/algorithms/BLSW/index.test.ts | 5 +++++ test/utils/algorithms/BLSW/windowRange.test.ts | 5 +++++ test/utils/datatstructures/SortedList/deployer.test.ts | 5 +++++ test/utils/datatstructures/SortedList/general.test.ts | 5 +++++ test/utils/datatstructures/SortedList/index.test.ts | 5 +++++ test/utils/datatstructures/SortedList/insert.test.ts | 5 +++++ test/utils/datatstructures/SortedList/remove.test.ts | 5 +++++ test/utils/datatstructures/SortedList/shrink.test.ts | 5 +++++ test/utils/index.test.ts | 5 +++++ 68 files changed, 340 insertions(+) diff --git a/mocks/data/asc_273_number.ts b/mocks/data/asc_273_number.ts index 027ea2fa..0e2cf6fd 100644 --- a/mocks/data/asc_273_number.ts +++ b/mocks/data/asc_273_number.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + export const data = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, diff --git a/mocks/data/shuffle_273_number.ts b/mocks/data/shuffle_273_number.ts index 15ecc5d4..f4434dff 100644 --- a/mocks/data/shuffle_273_number.ts +++ b/mocks/data/shuffle_273_number.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + export const data = [ 133, 119, 238, 14, 147, 10, 6, 62, 158, 154, 27, 273, 73, 22, 267, 165, 71, 60, 107, 207, 39, 65, 61, 41, 21, 99, 246, 198, 196, 12, 236, 183, 96, 233, 197, 18, 270, 20, 53, 244, 118, 224, 217, 159, 259, 63, 46, 126, 28, 95, 139, 254, 79, 145, 89, 64, 156, 148, 109, 157, 19, diff --git a/scripts/demo/1_demo.ts b/scripts/demo/1_demo.ts index 91da047f..3a02809c 100644 --- a/scripts/demo/1_demo.ts +++ b/scripts/demo/1_demo.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {mine} from "@nomicfoundation/hardhat-network-helpers"; import {parseEther} from "ethers/lib/utils"; import {network, ethers} from "hardhat"; diff --git a/test/constant.test.ts b/test/constant.test.ts index f0f963b7..658be2e2 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {MaxUint256, ZeroAddress} from "ethers"; export const constants = { diff --git a/test/index.test.ts b/test/index.test.ts index 59a654ab..3036ff94 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import * as tokens from "./tokens/index.test"; import * as utils from "./utils/index.test"; import {EventEmitter} from "events"; diff --git a/test/tokens/ERC20/ERC7818Behavior/burn.test.ts b/test/tokens/ERC20/ERC7818Behavior/burn.test.ts index 1c8113ed..5aab8561 100644 --- a/test/tokens/ERC20/ERC7818Behavior/burn.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/burn.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC20Selector} from "../base/deployer.test"; import {ERC20, constants} from "../../../constant.test"; diff --git a/test/tokens/ERC20/ERC7818Behavior/index.test.ts b/test/tokens/ERC20/ERC7818Behavior/index.test.ts index 030886a0..17e9e9d9 100644 --- a/test/tokens/ERC20/ERC7818Behavior/index.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../constant.test"; import * as Interface from "./interface.test"; import * as Burn from "./burn.test"; diff --git a/test/tokens/ERC20/ERC7818Behavior/interface.test.ts b/test/tokens/ERC20/ERC7818Behavior/interface.test.ts index 61fa0cba..e0dce510 100644 --- a/test/tokens/ERC20/ERC7818Behavior/interface.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/interface.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_increasePointerTo, hardhat_latestPointer, hardhat_reset} from "../../../utils.test"; import {deployERC20Selector} from "../base/deployer.test"; diff --git a/test/tokens/ERC20/ERC7818Behavior/transfer.test.ts b/test/tokens/ERC20/ERC7818Behavior/transfer.test.ts index 06f20e0a..e39a0c10 100644 --- a/test/tokens/ERC20/ERC7818Behavior/transfer.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/transfer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC20Selector} from "../base/deployer.test"; import {ERC20, constants} from "../../../constant.test"; diff --git a/test/tokens/ERC20/ERC7818Behavior/transferAtEpoch.test.ts b/test/tokens/ERC20/ERC7818Behavior/transferAtEpoch.test.ts index 806f8a91..199c47a0 100644 --- a/test/tokens/ERC20/ERC7818Behavior/transferAtEpoch.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/transferAtEpoch.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC20Selector} from "../base/deployer.test"; import {ERC20, ERC7818, constants} from "../../../constant.test"; diff --git a/test/tokens/ERC20/ERC7818Behavior/transferFromAtEpoch.test.ts b/test/tokens/ERC20/ERC7818Behavior/transferFromAtEpoch.test.ts index c9e4c31c..a5870975 100644 --- a/test/tokens/ERC20/ERC7818Behavior/transferFromAtEpoch.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/transferFromAtEpoch.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC20Selector} from "../base/deployer.test"; import {constants, ERC20, ERC7818} from "../../../constant.test"; diff --git a/test/tokens/ERC20/base/approve.test.ts b/test/tokens/ERC20/base/approve.test.ts index 3c4f9eaa..46af47d9 100644 --- a/test/tokens/ERC20/base/approve.test.ts +++ b/test/tokens/ERC20/base/approve.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {ERC20, constants} from "../../../constant.test"; import {deployERC20Selector} from "./deployer.test"; diff --git a/test/tokens/ERC20/base/burn.test.ts b/test/tokens/ERC20/base/burn.test.ts index 0d2eb021..646363df 100644 --- a/test/tokens/ERC20/base/burn.test.ts +++ b/test/tokens/ERC20/base/burn.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; import {deployERC20Selector} from "./deployer.test"; diff --git a/test/tokens/ERC20/base/deployer.test.ts b/test/tokens/ERC20/base/deployer.test.ts index fe56357f..d5c092c8 100644 --- a/test/tokens/ERC20/base/deployer.test.ts +++ b/test/tokens/ERC20/base/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {constants, ERC20, ERC20BLSW, ERC20TLSW} from "../../../constant.test"; import {MockERC20BLSW, MockERC20TLSW} from "../../../../typechain-types/mocks/contracts/tokens/ERC20"; diff --git a/test/tokens/ERC20/base/index.test.ts b/test/tokens/ERC20/base/index.test.ts index 7ca438da..fab3d7b1 100644 --- a/test/tokens/ERC20/base/index.test.ts +++ b/test/tokens/ERC20/base/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../constant.test"; import * as Mint from "./mint.test"; import * as Burn from "./burn.test"; diff --git a/test/tokens/ERC20/base/mint.test.ts b/test/tokens/ERC20/base/mint.test.ts index 29c28c20..4a7fcfa0 100644 --- a/test/tokens/ERC20/base/mint.test.ts +++ b/test/tokens/ERC20/base/mint.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_reset, hardhat_latestPointer} from "../../../utils.test"; import {deployERC20Selector} from "./deployer.test"; diff --git a/test/tokens/ERC20/base/transfer.test.ts b/test/tokens/ERC20/base/transfer.test.ts index 1a511acf..21d0ed74 100644 --- a/test/tokens/ERC20/base/transfer.test.ts +++ b/test/tokens/ERC20/base/transfer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC20Selector} from "./deployer.test"; import {ERC20, constants} from "../../../constant.test"; diff --git a/test/tokens/ERC20/base/transferFrom.test.ts b/test/tokens/ERC20/base/transferFrom.test.ts index 73334afe..17161486 100644 --- a/test/tokens/ERC20/base/transferFrom.test.ts +++ b/test/tokens/ERC20/base/transferFrom.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC20Selector} from "./deployer.test"; import {ERC20, constants} from "../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/addToBlacklist.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/addToBlacklist.test.ts index 809f3d1c..c1114c2a 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/addToBlacklist.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/addToBlacklist.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818BlacklistSelector} from "./deployer.test"; import {constants, ERC7818Blacklist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/burn.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/burn.test.ts index 9f6d1a82..957a69fb 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/burn.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/burn.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818BlacklistSelector} from "./deployer.test"; import {constants, ERC7818Blacklist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/deployer.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/deployer.test.ts index 85b14590..a509a5a4 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/deployer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {constants, ERC7818BlacklistBLSW, ERC20, ERC7818BlacklistTLSW} from "../../../../constant.test"; import {MockERC7818BlacklistBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC20/extensions/BLSW"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/index.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/index.test.ts index 94479544..00df9364 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/index.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../../constant.test"; import * as AddToBlacklist from "./addToBlacklist.test"; import * as RemoveFromBlacklist from "./removeFromBlacklist.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/mint.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/mint.test.ts index 2c0cf8f9..cb3123f4 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/mint.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/mint.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818BlacklistSelector} from "./deployer.test"; import {constants, ERC7818Blacklist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/removeFromBlacklist.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/removeFromBlacklist.test.ts index c23828ef..9be94c96 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/removeFromBlacklist.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/removeFromBlacklist.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818BlacklistSelector} from "./deployer.test"; import {constants, ERC7818Blacklist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Blacklist/transfer.test.ts b/test/tokens/ERC20/extensions/ERC7818Blacklist/transfer.test.ts index bc9da369..11465412 100644 --- a/test/tokens/ERC20/extensions/ERC7818Blacklist/transfer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Blacklist/transfer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818BlacklistSelector} from "./deployer.test"; import {constants, ERC7818Blacklist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/burn.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/burn.test.ts index 0fea983e..78bc7604 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/burn.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/burn.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818FrozenSelector} from "./deployer.test"; import {constants, ERC7818Frozen} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/deployer.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/deployer.test.ts index b1ba78c3..c35349f7 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/deployer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {constants, ERC7818FrozenBLSW, ERC20, ERC7818FrozenTLSW} from "../../../../constant.test"; import {MockERC7818FrozenBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC20/extensions/BLSW"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/freeze.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/freeze.test.ts index 80618728..99058df6 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/freeze.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/freeze.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818FrozenSelector} from "./deployer.test"; import {constants, ERC7818Frozen} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/index.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/index.test.ts index 61d76851..9257e3e6 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/index.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../../constant.test"; import * as Freeze from "./freeze.test"; import * as Unfreeze from "./unfreeze.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/mint.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/mint.test.ts index 90d674ff..487e4383 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/mint.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/mint.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818FrozenSelector} from "./deployer.test"; import {constants, ERC7818Frozen} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/transfer.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/transfer.test.ts index 7cf770f0..275ff8f6 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/transfer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/transfer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818FrozenSelector} from "./deployer.test"; import {constants, ERC7818Frozen} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Frozen/unfreeze.test.ts b/test/tokens/ERC20/extensions/ERC7818Frozen/unfreeze.test.ts index 9630ff4b..d476fd81 100644 --- a/test/tokens/ERC20/extensions/ERC7818Frozen/unfreeze.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Frozen/unfreeze.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818FrozenSelector} from "./deployer.test"; import {constants, ERC7818Frozen} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/addMinter.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/addMinter.test.ts index a3ece6a3..d54a21f3 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/addMinter.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/addMinter.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, ERC7818MintQuota} from "../../../../constant.test"; import {deployERC7818MintQuotaSelector} from "./deployer.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/decreaseQuota.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/decreaseQuota.test.ts index 737b1da1..e283d0a6 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/decreaseQuota.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/decreaseQuota.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, ERC7818MintQuota} from "../../../../constant.test"; import {deployERC7818MintQuotaSelector} from "./deployer.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/deployer.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/deployer.test.ts index 2b982254..f38cc47c 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/deployer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {ERC7818MintQuotaBLSW, constants, ERC20, ERC7818MintQuotaTLSW} from "../../../../constant.test"; import {MockERC7818MintQuotaBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC20/extensions/BLSW"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/increaseQuota.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/increaseQuota.test.ts index d0782818..b39f492b 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/increaseQuota.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/increaseQuota.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, ERC7818MintQuota} from "../../../../constant.test"; import {deployERC7818MintQuotaSelector} from "./deployer.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/index.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/index.test.ts index e4c2e534..3fcf62b4 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/index.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../../constant.test"; import * as AddMinter from "./addMinter.test"; import * as RemoveMinter from "./removeMinter.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/mint.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/mint.test.ts index 1b9b3de5..642c075d 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/mint.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/mint.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, ERC7818MintQuota} from "../../../../constant.test"; import {deployERC7818MintQuotaSelector} from "./deployer.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/removeMinter.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/removeMinter.test.ts index b4762b65..9ebea54e 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/removeMinter.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/removeMinter.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, ERC7818MintQuota} from "../../../../constant.test"; import {deployERC7818MintQuotaSelector} from "./deployer.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818MintQuota/setQuota.test.ts b/test/tokens/ERC20/extensions/ERC7818MintQuota/setQuota.test.ts index c8ba77a1..f1e6a255 100644 --- a/test/tokens/ERC20/extensions/ERC7818MintQuota/setQuota.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818MintQuota/setQuota.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, ERC7818MintQuota} from "../../../../constant.test"; import {deployERC7818MintQuotaSelector} from "./deployer.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/deployer.test.ts b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/deployer.test.ts index 1e8dea6a..de4ca5da 100644 --- a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/deployer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {constants, ERC7818NearestExpiryQueryBLSW, ERC20, ERC7818NearestExpiryQueryTLSW} from "../../../../constant.test"; import {MockERC7818NearestExpiryQueryBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC20/extensions/BLSW"; diff --git a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/index.test.ts b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/index.test.ts index 8f87d622..c45b7482 100644 --- a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/index.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../../constant.test"; import * as NearestExpiryQuery from "./nearestExpiryQuery.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts index 2225a2c9..16e8daaa 100644 --- a/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818NearestExpiryQuery/nearestExpiryQuery.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818NearestExpiryQuerySelector} from "./deployer.test"; import {constants} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/addToWhitelist.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/addToWhitelist.test.ts index e7d76141..2e6cb760 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/addToWhitelist.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/addToWhitelist.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818WhitelistSelector} from "./deployer.test"; import {constants, ERC7818Whitelist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/burn.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/burn.test.ts index ba48e412..d48c8778 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/burn.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/burn.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818WhitelistSelector} from "./deployer.test"; import {ERC7818Whitelist, ERC20, constants} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/deployer.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/deployer.test.ts index d97620f1..e787875d 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/deployer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {ERC20, constants, ERC7818WhitelistBLSW, ERC7818WhitelistTLSW} from "../../../../constant.test"; import {MockERC7818WhitelistBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC20/extensions/BLSW"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/index.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/index.test.ts index 6aca0c9f..904591d4 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/index.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import * as AddToWhitelist from "./addToWhitelist.test"; import * as RemoveFromWhitelist from "./removeFromWhitelist.test"; import * as Mint from "./mint.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/mint.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/mint.test.ts index 3610f596..46913788 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/mint.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/mint.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818WhitelistSelector} from "./deployer.test"; import {ERC7818Whitelist, ERC20, constants} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/removeFromWhitelist.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/removeFromWhitelist.test.ts index 7dd1ad38..40b36284 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/removeFromWhitelist.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/removeFromWhitelist.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818WhitelistSelector} from "./deployer.test"; import {constants, ERC7818Whitelist} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/transfer.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/transfer.test.ts index 33bc642d..dda8e839 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/transfer.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/transfer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818WhitelistSelector} from "./deployer.test"; import {ERC7818Whitelist, ERC20, constants} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/ERC7818Whitelist/transferFrom.test.ts b/test/tokens/ERC20/extensions/ERC7818Whitelist/transferFrom.test.ts index 468ce0c6..bba425ea 100644 --- a/test/tokens/ERC20/extensions/ERC7818Whitelist/transferFrom.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Whitelist/transferFrom.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployERC7818WhitelistSelector} from "./deployer.test"; import {ERC7818Whitelist, ERC20, constants} from "../../../../constant.test"; diff --git a/test/tokens/ERC20/extensions/index.test.ts b/test/tokens/ERC20/extensions/index.test.ts index d4db0edc..6059445a 100644 --- a/test/tokens/ERC20/extensions/index.test.ts +++ b/test/tokens/ERC20/extensions/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../../constant.test"; import * as ERC7818Blacklist from "./ERC7818Blacklist/index.test"; import * as ERC7818MintQuota from "./ERC7818MintQuota/index.test"; diff --git a/test/tokens/ERC20/index.test.ts b/test/tokens/ERC20/index.test.ts index 64a84e79..ce075747 100644 --- a/test/tokens/ERC20/index.test.ts +++ b/test/tokens/ERC20/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {constants} from "../../constant.test"; import * as ERC20Base from "./base/index.test"; import * as ERC7818Behavior from "./ERC7818Behavior/index.test"; diff --git a/test/tokens/ERC721/index.test.ts b/test/tokens/ERC721/index.test.ts index 4e8aa938..f9b4dee1 100644 --- a/test/tokens/ERC721/index.test.ts +++ b/test/tokens/ERC721/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + export const run = async () => { describe("ERC721EXPBase", async function () { // @TODO diff --git a/test/tokens/index.test.ts b/test/tokens/index.test.ts index 873a24dd..50030b4a 100644 --- a/test/tokens/index.test.ts +++ b/test/tokens/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import * as ERC20 from "./ERC20/index.test"; import * as ERC721 from "./ERC721/index.test"; diff --git a/test/utils.test.ts b/test/utils.test.ts index b3ebdc97..008a99f8 100644 --- a/test/utils.test.ts +++ b/test/utils.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {AddressLike, toBeHex} from "ethers"; import {network, ethers} from "hardhat"; import {mine, time} from "@nomicfoundation/hardhat-network-helpers"; diff --git a/test/utils/algorithms/BLSW/deployer.test.ts b/test/utils/algorithms/BLSW/deployer.test.ts index 78fc51c4..cc77c0ad 100644 --- a/test/utils/algorithms/BLSW/deployer.test.ts +++ b/test/utils/algorithms/BLSW/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {BLSWLibrary, constants} from "../../../constant.test"; import {MockBLSW} from "../../../../typechain-types/mocks/contracts/utils"; diff --git a/test/utils/algorithms/BLSW/epoch.test.ts b/test/utils/algorithms/BLSW/epoch.test.ts index 89180a59..47c963fc 100644 --- a/test/utils/algorithms/BLSW/epoch.test.ts +++ b/test/utils/algorithms/BLSW/epoch.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployBLSW} from "./deployer.test"; import {hardhat_mine, hardhat_reset} from "../../../utils.test"; diff --git a/test/utils/algorithms/BLSW/general.test.ts b/test/utils/algorithms/BLSW/general.test.ts index d2b394e2..77ffae33 100644 --- a/test/utils/algorithms/BLSW/general.test.ts +++ b/test/utils/algorithms/BLSW/general.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {constants, SlidingWindow} from "../../../constant.test"; import {calculateSlidingWindowState, deployBLSW} from "./deployer.test"; diff --git a/test/utils/algorithms/BLSW/index.test.ts b/test/utils/algorithms/BLSW/index.test.ts index af3f32db..1a512492 100644 --- a/test/utils/algorithms/BLSW/index.test.ts +++ b/test/utils/algorithms/BLSW/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import * as General from "./general.test"; import * as Epoch from "./epoch.test"; import * as WindowRange from "./windowRange.test"; diff --git a/test/utils/algorithms/BLSW/windowRange.test.ts b/test/utils/algorithms/BLSW/windowRange.test.ts index bf0d1388..accb04e5 100644 --- a/test/utils/algorithms/BLSW/windowRange.test.ts +++ b/test/utils/algorithms/BLSW/windowRange.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {deployBLSW} from "./deployer.test"; import {hardhat_latestBlock, hardhat_mine, hardhat_reset} from "../../../utils.test"; diff --git a/test/utils/datatstructures/SortedList/deployer.test.ts b/test/utils/datatstructures/SortedList/deployer.test.ts index ab34332e..c96da659 100644 --- a/test/utils/datatstructures/SortedList/deployer.test.ts +++ b/test/utils/datatstructures/SortedList/deployer.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {ethers} from "hardhat"; import {SortedListLibrary} from "../../../constant.test"; diff --git a/test/utils/datatstructures/SortedList/general.test.ts b/test/utils/datatstructures/SortedList/general.test.ts index 9d2a42f6..f2a3fdac 100644 --- a/test/utils/datatstructures/SortedList/general.test.ts +++ b/test/utils/datatstructures/SortedList/general.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; import {deploySortedList} from "./deployer.test"; diff --git a/test/utils/datatstructures/SortedList/index.test.ts b/test/utils/datatstructures/SortedList/index.test.ts index e0122e7a..821efd38 100644 --- a/test/utils/datatstructures/SortedList/index.test.ts +++ b/test/utils/datatstructures/SortedList/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import * as General from "./general.test"; import * as Insert from "./insert.test"; import * as Remove from "./remove.test"; diff --git a/test/utils/datatstructures/SortedList/insert.test.ts b/test/utils/datatstructures/SortedList/insert.test.ts index 788ce461..3c979c6e 100644 --- a/test/utils/datatstructures/SortedList/insert.test.ts +++ b/test/utils/datatstructures/SortedList/insert.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; import {deploySortedList} from "./deployer.test"; diff --git a/test/utils/datatstructures/SortedList/remove.test.ts b/test/utils/datatstructures/SortedList/remove.test.ts index fd55eec0..e0409c05 100644 --- a/test/utils/datatstructures/SortedList/remove.test.ts +++ b/test/utils/datatstructures/SortedList/remove.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; import {deploySortedList} from "./deployer.test"; diff --git a/test/utils/datatstructures/SortedList/shrink.test.ts b/test/utils/datatstructures/SortedList/shrink.test.ts index a9209d40..abe539c6 100644 --- a/test/utils/datatstructures/SortedList/shrink.test.ts +++ b/test/utils/datatstructures/SortedList/shrink.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import {expect} from "chai"; import {hardhat_reset} from "../../../utils.test"; import {deploySortedList} from "./deployer.test"; diff --git a/test/utils/index.test.ts b/test/utils/index.test.ts index 4749825c..34423d88 100644 --- a/test/utils/index.test.ts +++ b/test/utils/index.test.ts @@ -1,3 +1,8 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + import * as BLSW from "./algorithms/BLSW/index.test"; // import * as TLSW from "./algorithms/BLSW/index.test"; import * as SortedList from "./datatstructures/SortedList/index.test"; From 5a709196226b15d2ab8d10652d32377109d699cb Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 18 Feb 2025 10:45:53 +0700 Subject: [PATCH 15/53] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20adding=20fun?= =?UTF-8?q?ding=20section=20to=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 8b39b2e0..0f865221 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,10 @@ Check out the contribution [guide](CONTRIBUTING.md) For support or any inquiries, feel free to reach out to us at [github-issue](https://github.com/Kiwari-Labs/kiwari-labs-contracts/issues) or kiwarilabs@protonmail.com +## Funding + +If you value our work and would like to support Kiwari Labs, please visit our [Open Collective](https://opencollective.com/kiwari-labs) page to make a donation. Thank you! + ## License This repository is released under the [Apache-2.0](LICENSE). From 431003e6f886349880550a047f761275a39dcfcb Mon Sep 17 00:00:00 2001 From: MASDXI Date: Thu, 20 Feb 2025 10:41:49 +0700 Subject: [PATCH 16/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20missing=20args=20in?= =?UTF-8?q?=20insert?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC20/ERC20EXPBase.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index f0b7fdf9..78f64824 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -315,7 +315,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.totalBalance += balance; _recipient.balances[element] += balance; } - _recipient.list.insert(element); + _recipient.list.insert(element, false); element = _spender.list.next(element); _spender.list.remove(_spender.list.previous(element)); } else { @@ -325,7 +325,7 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 _recipient.totalBalance += pendingValue; _recipient.balances[element] += pendingValue; } - _recipient.list.insert(element); + _recipient.list.insert(element, false); pendingValue = 0; } } From fe08f3aa1ea54484ab764e21e3a7dfa4361fc986 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Thu, 20 Feb 2025 16:56:56 +0700 Subject: [PATCH 17/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20adding=20logic?= =?UTF-8?q?=20to=20ERC721EXPEpochBase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC721EXPBase.sol | 1 + .../ERC721/extensions/ERC721EpochBase.sol | 195 ++++++++++++++++-- 2 files changed, 184 insertions(+), 12 deletions(-) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 2c686d2b..4e5aee8c 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -52,6 +52,7 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { } _tokensTimestamp[tokenId].start = start; _tokensTimestamp[tokenId].end = end; + // @TODO emit tokenTimeSet(tokenId, start, end); } diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 9fc4051f..9ccaac6b 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -18,7 +18,7 @@ import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.s // @TODO following https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/ERC721.sol -abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Metadata, IERC7858Epoch { +abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IERC721Metadata, IERC7858Epoch { using SortedList for SortedList.List; using Strings for uint256; @@ -27,10 +27,12 @@ abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Meta struct Epoch { uint256 totalBalance; - mapping(uint256 => uint256[]) tokens; // it's possible to contains more than one tokenId. + mapping(uint256 pointer => uint256[]) tokens; // it's possible to contains more than one tokenId. + mapping(uint256 pointer => mapping(uint256 tokenId => uint256)) tokenIndex; SortedList.List list; } + mapping(uint256 tokenId => uint256) private _tokenPointers; mapping(uint256 tokenId => address) private _owners; mapping(uint256 => mapping(address => Epoch)) private _balances; mapping(uint256 tokenId => address) private _tokenApprovals; @@ -53,6 +55,10 @@ abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Meta return _owners[tokenId]; } + function _getApproved(uint256 tokenId) internal view virtual returns (address) { + return _tokenApprovals[tokenId]; + } + function _computeBalanceOverEpochRange(uint256 fromEpoch, uint256 toEpoch, address account) private view returns (uint256 balance) { unchecked { for (; fromEpoch <= toEpoch; fromEpoch++) { @@ -109,16 +115,16 @@ abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Meta if (owner == address(0)) { revert ERC721InvalidOwner(address(0)); } - // uint256 pointer = _pointerProvider(); - // (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); - // uint256 balance = _computeBalanceAtEpoch(fromEpoch, account, pointer, _getPointersInWindow()); - // if (fromEpoch == toEpoch) { - // return balance; - // } else { - // fromEpoch += 1; - // } - // balance += _computeBalanceOverEpochRange(fromEpoch, toEpoch, account); - // return balance; + uint256 pointer = _pointerProvider(); + (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); + uint256 balance = _computeBalanceAtEpoch(fromEpoch, owner, pointer, _getPointersInWindow()); + if (fromEpoch == toEpoch) { + return balance; + } else { + fromEpoch += 1; + } + balance += _computeBalanceOverEpochRange(fromEpoch, toEpoch, owner); + return balance; } /** @@ -166,6 +172,36 @@ abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Meta return owner; } + /** + * @dev See {IERC721-approve}. + */ + function approve(address to, uint256 tokenId) public virtual { + _approve(to, tokenId, _msgSender()); + } + + /** + * @dev See {IERC721-getApproved}. + */ + function getApproved(uint256 tokenId) public view virtual returns (address) { + _requireOwned(tokenId); + + return _getApproved(tokenId); + } + + /** + * @dev See {IERC721-setApprovalForAll}. + */ + function setApprovalForAll(address operator, bool approved) public virtual { + _setApprovalForAll(_msgSender(), operator, approved); + } + + /** + * @dev See {IERC721-isApprovedForAll}. + */ + function isApprovedForAll(address owner, address operator) public view virtual returns (bool) { + return _operatorApprovals[owner][operator]; + } + function _expired(uint256 epoch) internal view returns (bool) { unchecked { (uint256 fromEpoch, ) = _getWindowRage(_pointerProvider()); @@ -185,6 +221,141 @@ abstract contract ERC721EpochBase is ERC165, IERC721, IERC721Errors, IERC721Meta return ""; } + function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { + uint256 pointer = _pointerProvider(); + uint256 epoch = _getEpoch(pointer); + address from = _ownerOf(tokenId); + uint256 tokenPointer = _tokenPointers[tokenId]; + if (tokenPointer == 0) { + tokenPointer = pointer; + _tokenPointers[tokenId] = pointer; + } + + // Perform (optional) operator check + if (auth != address(0)) { + // _checkAuthorized(from, auth, tokenId); + } + + Epoch storage _sender = _balances[epoch][from]; + Epoch storage _recipient = _balances[epoch][to]; + + // Execute the update + if (from != address(0)) { + // Clear approval. No need to re-authorize or emit the Approval event + // _approve(address(0), tokenId, address(0), false); + + unchecked { + _sender.totalBalance -= 1; + _sender.tokenIndex[tokenPointer][_sender.tokens[tokenPointer].length - 1] = _sender.tokenIndex[tokenPointer][tokenId]; + _sender.tokens[tokenPointer].pop(); + delete _sender.tokenIndex[tokenPointer][tokenId]; + _sender.list.remove(tokenPointer); + } + } + + if (to != address(0)) { + unchecked { + _recipient.totalBalance += 1; + _recipient.tokens[tokenPointer].push(tokenId); + _recipient.tokenIndex[tokenPointer][tokenId] = _recipient.tokens[tokenPointer].length - 1; + _recipient.list.insert(tokenPointer, false); + } + } + + if (to == address(0)) { + delete _tokenPointers[tokenId]; + } + + _owners[tokenId] = to; + + emit Transfer(from, to, tokenId); + + return from; + } + + function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) { + return + spender != address(0) && + (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender); + } + + + function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual { + if (!_isAuthorized(owner, spender, tokenId)) { + if (owner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } else { + revert ERC721InsufficientApproval(spender, tokenId); + } + } + } + + function _mint(address account, uint256 tokenId) internal { + if (account == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + address previousOwner = _update(account, tokenId, address(0)); + if (previousOwner != address(0)) { + revert ERC721InvalidSender(address(0)); + } + } + + function _safeMint(address account, uint256 tokenId) internal { + _safeMint(account, tokenId, ""); + } + + function _safeMint(address account, uint256 tokenId, bytes memory data) internal virtual { + _mint(account, tokenId); + ERC721Utils.checkOnERC721Received(_msgSender(), address(0), account, tokenId, data); + } + + function _burn(uint256 tokenId) internal { + address previousOwner = _update(address(0), tokenId, address(0)); + if (previousOwner == address(0)) { + revert ERC721NonexistentToken(tokenId); + } + } + + /** + * @dev Approve `to` to operate on `tokenId` + * + * The `auth` argument is optional. If the value passed is non 0, then this function will check that `auth` is + * either the owner of the token, or approved to operate on all tokens held by this owner. + * + * Emits an {Approval} event. + * + * Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument. + */ + function _approve(address to, uint256 tokenId, address auth) internal { + _approve(to, tokenId, auth, true); + } + + function _approve(address to, uint256 tokenId, address auth, bool emitEvent) internal virtual { + // Avoid reading the owner unless necessary + if (emitEvent || auth != address(0)) { + address owner = _requireOwned(tokenId); + + // We do not use _isAuthorized because single-token approvals should not be able to call approve + if (auth != address(0) && owner != auth && !isApprovedForAll(owner, auth)) { + revert ERC721InvalidApprover(auth); + } + + if (emitEvent) { + emit Approval(owner, to, tokenId); + } + } + + _tokenApprovals[tokenId] = to; + } + + function _setApprovalForAll(address owner, address operator, bool approved) internal virtual { + if (operator == address(0)) { + revert ERC721InvalidOperator(operator); + } + _operatorApprovals[owner][operator] = approved; + emit ApprovalForAll(owner, operator, approved); + } + /// @dev See {IERC7858Epoch-currentEpoch}. function currentEpoch() public view virtual returns (uint256) { return _getEpoch(_pointerProvider()); From 13d5eac83861c7c9456261084738efed74ae6aa4 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Fri, 21 Feb 2025 09:58:02 +0700 Subject: [PATCH 18/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20adding=20logic?= =?UTF-8?q?=20to=20ERC721EpochBase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adding logic to the functio that inheritance form the IERC7858 --- contracts/tokens/ERC721/ERC721EXPBase.sol | 1 - .../ERC721/extensions/ERC721EpochBase.sol | 36 +++++++++++++------ .../ERC721/interfaces/IERC7858Epoch.sol | 7 ++++ 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 4e5aee8c..2c686d2b 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -52,7 +52,6 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { } _tokensTimestamp[tokenId].start = start; _tokensTimestamp[tokenId].end = end; - // @TODO emit tokenTimeSet(tokenId, start, end); } diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 9ccaac6b..08a8a614 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -172,7 +172,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE return owner; } - /** + /** * @dev See {IERC721-approve}. */ function approve(address to, uint256 tokenId) public virtual { @@ -222,6 +222,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { + // @TODO adding refresh list to remove expired token from list. uint256 pointer = _pointerProvider(); uint256 epoch = _getEpoch(pointer); address from = _ownerOf(tokenId); @@ -233,7 +234,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE // Perform (optional) operator check if (auth != address(0)) { - // _checkAuthorized(from, auth, tokenId); + _checkAuthorized(from, auth, tokenId); } Epoch storage _sender = _balances[epoch][from]; @@ -242,7 +243,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE // Execute the update if (from != address(0)) { // Clear approval. No need to re-authorize or emit the Approval event - // _approve(address(0), tokenId, address(0), false); + _approve(address(0), tokenId, address(0), false); unchecked { _sender.totalBalance -= 1; @@ -262,10 +263,6 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } } - if (to == address(0)) { - delete _tokenPointers[tokenId]; - } - _owners[tokenId] = to; emit Transfer(from, to, tokenId); @@ -274,12 +271,9 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } function _isAuthorized(address owner, address spender, uint256 tokenId) internal view virtual returns (bool) { - return - spender != address(0) && - (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender); + return spender != address(0) && (owner == spender || isApprovedForAll(owner, spender) || _getApproved(tokenId) == spender); } - function _checkAuthorized(address owner, address spender, uint256 tokenId) internal view virtual { if (!_isAuthorized(owner, spender, tokenId)) { if (owner == address(0)) { @@ -356,6 +350,16 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE emit ApprovalForAll(owner, operator, approved); } + /// @dev See {IERC7858-startTime}. + function startTime(uint256 tokenId) external view returns (uint256) { + return _tokenPointers[tokenId]; + } + + /// @dev See {IERC7858-endTime}. + function endTime(uint256 tokenId) external view returns (uint256) { + return _tokenPointers[tokenId] + _getPointersInWindow(); + } + /// @dev See {IERC7858Epoch-currentEpoch}. function currentEpoch() public view virtual returns (uint256) { return _getEpoch(_pointerProvider()); @@ -381,6 +385,16 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE return _expired(id); } + /// @dev See {IERC7858Epoch-isTokenValid}. + function isTokenValid(uint256 tokenId) external view returns (bool) { + uint256 pointer = _tokenPointers[tokenId]; + if (pointer != 0) { + if (_pointerProvider() - pointer < _getPointersInWindow()) { + return true; + } + } + } + function _epochType() internal pure virtual returns (EXPIRY_TYPE) {} function _getEpoch(uint256 pointer) internal view virtual returns (uint256) {} diff --git a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol index f4df6a0a..d7fc330f 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol @@ -50,4 +50,11 @@ interface IERC7858Epoch is IERC7858 { * based on the `EPOCH_TYPE` measurement (e.g., block count or time duration). */ function isEpochExpired(uint256 epoch) external view returns (bool); + + /** + * @dev Checks whether a specific token is expired. + * @param tokenId The identifier representing the `tokenId` (ERC721). + * @return bool True if the token is expired, false otherwise. + */ + function isTokenValid(uint256 tokenId) external view returns (bool); } From 7ed8b476ed759931d723f6c0dd3e8ae79c7ea776 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 22 Feb 2025 19:20:21 +0700 Subject: [PATCH 19/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20validBalanceOf?= =?UTF-8?q?=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC721EpochBase.sol | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 08a8a614..d053393a 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -34,7 +34,8 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE mapping(uint256 tokenId => uint256) private _tokenPointers; mapping(uint256 tokenId => address) private _owners; - mapping(uint256 => mapping(address => Epoch)) private _balances; + mapping(uint256 => mapping(address => Epoch)) private _epochBalances; + mapping(address => uint256) _balances; mapping(uint256 tokenId => address) private _tokenApprovals; mapping(address owner => mapping(address operator => bool)) private _operatorApprovals; @@ -62,7 +63,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE function _computeBalanceOverEpochRange(uint256 fromEpoch, uint256 toEpoch, address account) private view returns (uint256 balance) { unchecked { for (; fromEpoch <= toEpoch; fromEpoch++) { - balance += _balances[fromEpoch][account].totalBalance; + balance += _epochBalances[fromEpoch][account].totalBalance; } } } @@ -74,7 +75,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE uint256 duration ) private view returns (uint256 balance) { (uint256 element, uint256 value) = _findValidBalance(account, epoch, pointer, duration); - Epoch storage _account = _balances[epoch][account]; + Epoch storage _account = _epochBalances[epoch][account]; unchecked { balance = value; while (element > 0) { @@ -91,7 +92,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE uint256 pointer, uint256 duration ) internal view returns (uint256 element, uint256 value) { - SortedList.List storage list = _balances[epoch][account].list; + SortedList.List storage list = _epochBalances[epoch][account].list; if (!list.isEmpty()) { element = list.front(); unchecked { @@ -99,7 +100,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE element = list.next(element); } } - value = _balances[epoch][account].tokens[element].length; + value = _epochBalances[epoch][account].tokens[element].length; } } @@ -112,6 +113,13 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } function balanceOf(address owner) public view virtual returns (uint256) { + if (owner == address(0)) { + revert ERC721InvalidOwner(address(0)); + } + return _balances[owner]; + } + + function validBalanceOf(address owner) public view virtual returns (uint256) { if (owner == address(0)) { revert ERC721InvalidOwner(address(0)); } @@ -222,7 +230,6 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { - // @TODO adding refresh list to remove expired token from list. uint256 pointer = _pointerProvider(); uint256 epoch = _getEpoch(pointer); address from = _ownerOf(tokenId); @@ -237,8 +244,8 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE _checkAuthorized(from, auth, tokenId); } - Epoch storage _sender = _balances[epoch][from]; - Epoch storage _recipient = _balances[epoch][to]; + Epoch storage _sender = _epochBalances[epoch][from]; + Epoch storage _recipient = _epochBalances[epoch][to]; // Execute the update if (from != address(0)) { @@ -246,16 +253,18 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE _approve(address(0), tokenId, address(0), false); unchecked { + _balances[from] -= 1; _sender.totalBalance -= 1; _sender.tokenIndex[tokenPointer][_sender.tokens[tokenPointer].length - 1] = _sender.tokenIndex[tokenPointer][tokenId]; _sender.tokens[tokenPointer].pop(); - delete _sender.tokenIndex[tokenPointer][tokenId]; _sender.list.remove(tokenPointer); + delete _sender.tokenIndex[tokenPointer][tokenId]; } } if (to != address(0)) { unchecked { + _balances[from] += 1; _recipient.totalBalance += 1; _recipient.tokens[tokenPointer].push(tokenId); _recipient.tokenIndex[tokenPointer][tokenId] = _recipient.tokens[tokenPointer].length - 1; From 3db857ec078dbf3f85c0813d0abe52cd13ed3676 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 22 Feb 2025 22:42:32 +0700 Subject: [PATCH 20/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20unuse?= =?UTF-8?q?d=20line?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/utils/datastructures/SortedList.sol | 5 ----- 1 file changed, 5 deletions(-) diff --git a/contracts/utils/datastructures/SortedList.sol b/contracts/utils/datastructures/SortedList.sol index 4573cc4f..69a08ab4 100644 --- a/contracts/utils/datastructures/SortedList.sol +++ b/contracts/utils/datastructures/SortedList.sol @@ -101,11 +101,6 @@ library SortedList { self._nodes[element][PREVIOUS] = SENTINEL; self._nodes[tmpPREVIOUS][NEXT] = tmpNext; self._nodes[tmpNext][PREVIOUS] = tmpPREVIOUS; - - assembly { - let slot := self.slot - sstore(slot, sub(sload(slot), 0x01)) - } } } From 5fb373e628f8bdda6ea6b8a572b68403c75dba3e Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 12:10:53 +0700 Subject: [PATCH 21/53] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20getInitialBl?= =?UTF-8?q?ockNumber?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/abstracts/AbstractBLSW.sol | 8 ++++++++ contracts/utils/algorithms/BLSW.sol | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/contracts/abstracts/AbstractBLSW.sol b/contracts/abstracts/AbstractBLSW.sol index fd2bd7db..f9289637 100644 --- a/contracts/abstracts/AbstractBLSW.sol +++ b/contracts/abstracts/AbstractBLSW.sol @@ -59,6 +59,14 @@ abstract contract AbstractBLSW { return _window.windowSize(); } + /** + * @notice Returns the initial block number of the sliding window. + * @return The initial block number. + */ + function _getInitialBlockNumber() internal view returns (uint256) { + return _window.getInitialBlockNumber(); + } + /// @notice Returns the number of blocks in each epoch. /// @return The number of blocks in an epoch. function _blocksInEpoch() internal view returns (uint40) { diff --git a/contracts/utils/algorithms/BLSW.sol b/contracts/utils/algorithms/BLSW.sol index a73af138..b2eb0d48 100644 --- a/contracts/utils/algorithms/BLSW.sol +++ b/contracts/utils/algorithms/BLSW.sol @@ -72,6 +72,15 @@ library BLSW { } } + /** + * @notice Returns the initial block number of the sliding window. + * @param self The sliding window structure. + * @return The initial block number. + */ + function getInitialBlockNumber(Window storage self) internal view returns (uint256) { + return self.initialBlockNumber; + } + /** * @notice Returns the number of blocks in each epoch for a given window. * @param self The sliding window structure. From 72ca6132522cac48af695a6ade1333e6509eea17 Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 12:11:12 +0700 Subject: [PATCH 22/53] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20getInitialTi?= =?UTF-8?q?mestamp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/abstracts/AbstractTLSW.sol | 8 ++++++++ contracts/utils/algorithms/TLSW.sol | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/contracts/abstracts/AbstractTLSW.sol b/contracts/abstracts/AbstractTLSW.sol index 15c0dc84..5c528eae 100644 --- a/contracts/abstracts/AbstractTLSW.sol +++ b/contracts/abstracts/AbstractTLSW.sol @@ -59,6 +59,14 @@ abstract contract AbstractTLSW { return _window.windowSize(); } + /** + * @notice Returns the initial timestamp of the sliding window. + * @return The initial timestamp. + */ + function _getInitialTimestamp() internal view returns (uint256) { + return _window.getInitialTimestamp(); + } + /// @notice Returns the number of seconds in an epoch. /// @return The number of seconds in an epoch. function _secondsInEpoch() internal view returns (uint40) { diff --git a/contracts/utils/algorithms/TLSW.sol b/contracts/utils/algorithms/TLSW.sol index 26ef427e..60da599e 100644 --- a/contracts/utils/algorithms/TLSW.sol +++ b/contracts/utils/algorithms/TLSW.sol @@ -72,6 +72,15 @@ library TLSW { } } + /** + * @notice Returns the initial timestamp of the sliding window. + * @param self The sliding window structure. + * @return The initial timestamp. + */ + function getInitialTimestamp(Window storage self) internal view returns (uint256) { + return self.initialTimestamp; + } + /** * @notice Returns the number of seconds in each epoch for a given window. * @param self The sliding window structure. From fe1faa8e25f22ce2570b5e780d63ce8c85eb9bcc Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 12:11:43 +0700 Subject: [PATCH 23/53] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20getInitialPo?= =?UTF-8?q?inter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC20/ERC20BLSW.sol | 4 ++++ contracts/tokens/ERC20/ERC20EXPBase.sol | 10 ++++++++++ contracts/tokens/ERC20/ERC20TLSW.sol | 4 ++++ .../ERC20/extensions/BLSW/MockERC7818BlacklistBLSW.sol | 4 ++++ .../ERC20/extensions/BLSW/MockERC7818ExceptionBLSW.sol | 4 ++++ .../ERC20/extensions/BLSW/MockERC7818FrozenBLSW.sol | 4 ++++ .../ERC20/extensions/BLSW/MockERC7818MintQuotaBLSW.sol | 4 ++++ .../BLSW/MockERC7818NearestExpiryQueryBLSW.sol | 4 ++++ .../ERC20/extensions/BLSW/MockERC7818PermitBLSW.sol | 4 ++++ .../ERC20/extensions/TLSW/MockERC7818BlacklistTLSW.sol | 4 ++++ .../ERC20/extensions/TLSW/MockERC7818ExceptionTLSW.sol | 4 ++++ .../ERC20/extensions/TLSW/MockERC7818FrozenTLSW.sol | 4 ++++ .../ERC20/extensions/TLSW/MockERC7818MintQuotaTLSW.sol | 4 ++++ .../TLSW/MockERC7818NearestExpiryQueryTLSW.sol | 4 ++++ .../ERC20/extensions/TLSW/MockERC7818PermitTLSW.sol | 4 ++++ 15 files changed, 66 insertions(+) diff --git a/contracts/tokens/ERC20/ERC20BLSW.sol b/contracts/tokens/ERC20/ERC20BLSW.sol index 96c45c8c..ec491e82 100644 --- a/contracts/tokens/ERC20/ERC20BLSW.sol +++ b/contracts/tokens/ERC20/ERC20BLSW.sol @@ -29,6 +29,10 @@ abstract contract ERC20BLSW is IERC7818, ERC20EXPBase, BLSW { bool development_ ) ERC20EXPBase(name_, symbol_) BLSW(initialBlockNumber_, blocksPerEpoch_, windowSize_, development_) {} + function _getInitialPointer() internal view virtual override returns (uint256) { + return _getInitialBlockNumber(); + } + function _epochType() internal pure virtual override returns (EPOCH_TYPE) { return EPOCH_TYPE.BLOCKS_BASED; } diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index 78f64824..8d6daeea 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -523,6 +523,14 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 return balance; } + /** + * @notice Returns the initial pointer of the token. + * @return The initial pointer value. + */ + function getInitialPointer() public view virtual returns (uint256) { + return _getInitialPointer(); + } + /** * @dev See {IERC20-allowance}. */ @@ -633,6 +641,8 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 return true; } + function _getInitialPointer() internal view virtual returns (uint256) {} + function _epochType() internal pure virtual returns (EPOCH_TYPE) {} function _getEpoch(uint256 pointer) internal view virtual returns (uint256) {} diff --git a/contracts/tokens/ERC20/ERC20TLSW.sol b/contracts/tokens/ERC20/ERC20TLSW.sol index 28a55834..db6a33d4 100644 --- a/contracts/tokens/ERC20/ERC20TLSW.sol +++ b/contracts/tokens/ERC20/ERC20TLSW.sol @@ -29,6 +29,10 @@ abstract contract ERC20TLSW is IERC7818, ERC20EXPBase, TLSW { bool development_ ) ERC20EXPBase(name_, symbol_) TLSW(initialBlockTimestamp_, secondsPerEpoch_, windowSize_, development_) {} + function _getInitialPointer() internal view virtual override returns (uint256) { + return _getInitialTimestamp(); + } + function _epochType() internal pure virtual override returns (EPOCH_TYPE) { return EPOCH_TYPE.TIME_BASED; } diff --git a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818BlacklistBLSW.sol b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818BlacklistBLSW.sol index d9f767b0..07d2c5c2 100644 --- a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818BlacklistBLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818BlacklistBLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818BlacklistBLSW is ERC20BLSW, ERC7818Blacklist { uint8 windowSize_ ) ERC20BLSW(_name, _symbol, block.number, blocksPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20BLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20BLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818ExceptionBLSW.sol b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818ExceptionBLSW.sol index 302313ba..1c397cba 100644 --- a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818ExceptionBLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818ExceptionBLSW.sol @@ -15,6 +15,10 @@ contract MockERC7818ExceptionBLSW is ERC20BLSW, ERC7818Exception { uint8 windowSize_ ) ERC20BLSW(_name, _symbol, block.number, blocksPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20BLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20BLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818FrozenBLSW.sol b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818FrozenBLSW.sol index 53206bb8..b007b947 100644 --- a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818FrozenBLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818FrozenBLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818FrozenBLSW is ERC20BLSW, ERC7818Frozen { uint8 windowSize_ ) ERC20BLSW(_name, _symbol, block.number, blocksPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20BLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20BLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818MintQuotaBLSW.sol b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818MintQuotaBLSW.sol index b3c5d39c..d232c5de 100644 --- a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818MintQuotaBLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818MintQuotaBLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818MintQuotaBLSW is ERC20BLSW, ERC7818MintQuota { uint8 windowSize_ ) ERC20BLSW(_name, _symbol, block.number, blocksPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20BLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20BLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818NearestExpiryQueryBLSW.sol b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818NearestExpiryQueryBLSW.sol index 3f852492..c378907c 100644 --- a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818NearestExpiryQueryBLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818NearestExpiryQueryBLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818NearestExpiryQueryBLSW is ERC20BLSW, ERC7818NearestExpiryQue uint8 windowSize_ ) ERC20BLSW(_name, _symbol, block.number, blocksPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20BLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20BLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818PermitBLSW.sol b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818PermitBLSW.sol index 372f41bf..b6228636 100644 --- a/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818PermitBLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/BLSW/MockERC7818PermitBLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818PermitBLSW is ERC20BLSW, ERC7818Permit { uint8 windowSize_ ) ERC20BLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) ERC7818Permit(_name) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20BLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20BLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818BlacklistTLSW.sol b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818BlacklistTLSW.sol index 554392e6..c1eca28c 100644 --- a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818BlacklistTLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818BlacklistTLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818BlacklistTLSW is ERC20TLSW, ERC7818Blacklist { uint8 windowSize_ ) ERC20TLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20TLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20TLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818ExceptionTLSW.sol b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818ExceptionTLSW.sol index b1cf3616..a1b7f973 100644 --- a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818ExceptionTLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818ExceptionTLSW.sol @@ -15,6 +15,10 @@ contract MockERC7818ExceptionTLSW is ERC20TLSW, ERC7818Exception { uint8 windowSize_ ) ERC20TLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20TLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20TLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818FrozenTLSW.sol b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818FrozenTLSW.sol index b55785e0..7e0e5966 100644 --- a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818FrozenTLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818FrozenTLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818FrozenTLSW is ERC20TLSW, ERC7818Frozen { uint8 windowSize_ ) ERC20TLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20TLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20TLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818MintQuotaTLSW.sol b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818MintQuotaTLSW.sol index 72bb1915..97b4d1ec 100644 --- a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818MintQuotaTLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818MintQuotaTLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818MintQuotaTLSW is ERC20TLSW, ERC7818MintQuota { uint8 windowSize_ ) ERC20TLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20TLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20TLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818NearestExpiryQueryTLSW.sol b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818NearestExpiryQueryTLSW.sol index 9979c7b5..c885ffd7 100644 --- a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818NearestExpiryQueryTLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818NearestExpiryQueryTLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818NearestExpiryQueryTLSW is ERC20TLSW, ERC7818NearestExpiryQue uint8 windowSize_ ) ERC20TLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20TLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20TLSW) returns (EPOCH_TYPE) { return super._epochType(); } diff --git a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818PermitTLSW.sol b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818PermitTLSW.sol index f5761c7c..f52c9ebd 100644 --- a/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818PermitTLSW.sol +++ b/mocks/contracts/tokens/ERC20/extensions/TLSW/MockERC7818PermitTLSW.sol @@ -13,6 +13,10 @@ contract MockERC7818PermitTLSW is ERC20TLSW, ERC7818Permit { uint8 windowSize_ ) ERC20TLSW(_name, _symbol, block.timestamp, secondsPerEpoch_, windowSize_, false) ERC7818Permit(_name) {} + function _getInitialPointer() internal view virtual override(ERC20EXPBase, ERC20TLSW) returns (uint256) { + return super._getInitialPointer(); + } + function _epochType() internal pure virtual override(ERC20EXPBase, ERC20TLSW) returns (EPOCH_TYPE) { return super._epochType(); } From 497cccd4a5f52adb1163348890e5f040cfe4e50e Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 19:32:23 +0700 Subject: [PATCH 24/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20block?= =?UTF-8?q?LengthCache=20to=20pointerLengthCache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC20/ERC20EXPBase.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index 8d6daeea..357087c2 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -140,9 +140,9 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 } _recipient.list.insert(pointer, false); } else { - uint256 blockLengthCache = _getPointersInWindow(); + uint256 pointerLengthCache = _getPointersInWindow(); (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); - _refreshBalanceAtEpoch(from, fromEpoch, pointer, blockLengthCache); + _refreshBalanceAtEpoch(from, fromEpoch, pointer, pointerLengthCache); uint256 balance = _computeBalanceOverEpochRange(fromEpoch, toEpoch, from); if (balance < value) { revert ERC20InsufficientBalance(from, balance, value); From 7f294becb2797692b94888255e21b133bd71d2fc Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 20:15:48 +0700 Subject: [PATCH 25/53] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20Nat?= =?UTF-8?q?Spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC20/ERC20EXPBase.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index 357087c2..ee582bad 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -218,16 +218,17 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 } /** - * @notice Finds the index of the first valid block balance in a sorted list of block numbers. - * A block balance index is considered valid if the difference between the current pointer - * and the block number at the index (key) is less than the duration. - * @dev This function is used to determine the first valid block balance index within a sorted circular doubly linked list. - * It iterates through the list starting from the head and stops when it finds a valid index or reaches the end of the list. - * @param account The account address. + * @notice Finds the index of the first valid balance in a sorted list based on a given pointer. + * @dev Determines the first valid balance index in a sorted circular doubly linked list. + * A balance index is considered valid if the difference between the current pointer + * and the index (key) is less than the specified duration. + * Iterates through the list starting from the front until it finds a valid index or reaches the end. + * @param account The address of the account. * @param epoch The epoch number. - * @param pointer The current block number. - * @param duration The maximum allowed difference between pointer and the key. - * @return element The index of the first valid block balance. + * @param pointer The reference point used for validation. + * @param duration The maximum allowed difference between the pointer and the key. + * @return element The index of the first valid balance. + * @return value The balance associated with the valid index. */ function _findValidBalance( address account, From b3822f9d1f439f7f81618b11a70cd5e11cf84aa8 Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 20:17:43 +0700 Subject: [PATCH 26/53] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20Nat?= =?UTF-8?q?Spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC20/ERC20EXPBase.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index ee582bad..e4d9a6e2 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -455,10 +455,10 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 } /** - * @notice Retrieves the total balance stored at a specific block. - * @dev This function returns the balance of the given block from the internal `_worldStateBalances` mapping. - * @param pointer The block number for which the balance is being queried. - * @return balance The total balance stored at the given block number. + * @notice Retrieves the total balance stored at a specific pointer. + * @dev This function returns the balance of the given pointer from the internal `_worldStateBalances` mapping. + * @param pointer The reference point for which the balance is being queried. + * @return balance The total balance stored at the given pointer. */ function getWorldStateBalance(uint256 pointer) external view virtual returns (uint256) { return _worldStateBalances[pointer]; From 0c67c6d5e5be4c09f774929179a61693c0fb1e32 Mon Sep 17 00:00:00 2001 From: ADISAKBOONMARK Date: Sun, 23 Feb 2025 21:07:20 +0700 Subject: [PATCH 27/53] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20Nat?= =?UTF-8?q?Spec?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/utils/datastructures/SortedList.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/utils/datastructures/SortedList.sol b/contracts/utils/datastructures/SortedList.sol index 69a08ab4..9d1e1cb5 100644 --- a/contracts/utils/datastructures/SortedList.sol +++ b/contracts/utils/datastructures/SortedList.sol @@ -46,6 +46,7 @@ library SortedList { * @dev This function inserts data into the linked list at the specified element. * @param self The linked list. * @param index The element at which to insert the data. + * @param check A flag to validate the index before insertion. */ function insert(List storage self, uint256 index, bool check) internal { if (check) { From 6d92a87e9923faad3eb0032a288819d083ff685c Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 24 Feb 2025 13:44:55 +0700 Subject: [PATCH 28/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=5Fupdate=20handlin?= =?UTF-8?q?g=20transfer=20expired=20tokenId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit allowing _update function to transfer expired tokenId --- contracts/tokens/ERC721/extensions/ERC721EpochBase.sol | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index d053393a..e4924831 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -230,14 +230,17 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { - uint256 pointer = _pointerProvider(); - uint256 epoch = _getEpoch(pointer); - address from = _ownerOf(tokenId); + uint256 pointer = _pointerProvider(); // current block or timestamp uint256 tokenPointer = _tokenPointers[tokenId]; + address from = _ownerOf(tokenId); + // if the tokenId is not exist before minting it if (tokenPointer == 0) { tokenPointer = pointer; _tokenPointers[tokenId] = pointer; + } else { + pointer = tokenPointer; } + uint256 epoch = _getEpoch(pointer); // Perform (optional) operator check if (auth != address(0)) { From 4e9e12b0a8b1514002d4a6712233cbb1645d7159 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 24 Feb 2025 14:02:32 +0700 Subject: [PATCH 29/53] =?UTF-8?q?docs:=20=E2=9C=8F=EF=B8=8F=20update=20ERC?= =?UTF-8?q?-7858=20usage=20pattern?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0f865221..c0f6f273 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ pragma solidity ^0.8.0; import {ERC20EXPBase} from "@kiwarilabs/contracts/tokens/ERC20/ERC20EXPBase.sol"; import {ERC20BLSW} from "@kiwarilabs/contracts/tokens/ERC20/ERC20BLSW.sol"; -contract ExpirableERC20 is ERC20EXPBase, ERC20BLSW { +contract ExpirableERC20 is ERC20BLSW { constructor( string memory _name, string memory _symbol, @@ -70,10 +70,41 @@ contract ExpirableERC20 is ERC20EXPBase, ERC20BLSW { } ``` -ERC-7858 +### ERC-7858 > [!IMPORTANT] This ERC still underdevelopment. +#### Individual Expiration + +``` Solidity +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import {ERC721B} from "@kiwarilabs/contracts/tokens/ERC721/ERC721B.sol"; +import {ERC721EXPBase} from "@kiwarilabs/contracts/tokens/ERC721/ERC721EXPBase.sol"; + +// Expirable ERC721 with individual expiration +contract ExpirableERC721 is ERC721B { + + constructor ERC721B() {} +} +``` +#### Epoch Expiration + +``` Solidity +// SPDX-License-Identifier: Apache-2.0 +pragma solidity ^0.8.0; + +import {ERC721BLSW} from "@kiwarilabs/contracts/tokens/ERC721/extensions/ERC721BLSW.sol"; +import {ERC721EXPEpochBase} from "@kiwarilabs/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol"; + +// Expirable ERC721 with epoch expiration +contract ExpirableERC721 is ERC721BLSW { + + constructor ERC721BLSW() {} +} +``` + ## Contribute Check out the contribution [guide](CONTRIBUTING.md) From 914d534837ff00dc5b99991fc8c647ca120f5109 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 25 Feb 2025 15:53:11 +0700 Subject: [PATCH 30/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20funct?= =?UTF-8?q?ion=20name=20in=20ERC-7858=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++++++++++++++----- .../ERC721/{ERC721B.sol => ERC721BLSW.sol} | 2 +- contracts/tokens/ERC721/ERC721EXPBase.sol | 3 +-- .../ERC721/{ERC721T.sol => ERC721TLSW.sol} | 2 +- .../{ERC721BLSW.sol => ERC721EpochBLSW.sol} | 2 +- .../ERC721/extensions/ERC721EpochBase.sol | 2 +- .../{ERC721TLSW.sol => ERC721EpochTLSW.sol} | 2 +- .../tokens/ERC721/interfaces/IERC7858.sol | 4 +-- .../ERC721/interfaces/IERC7858Epoch.sol | 20 +++++++------- 9 files changed, 39 insertions(+), 25 deletions(-) rename contracts/tokens/ERC721/{ERC721B.sol => ERC721BLSW.sol} (93%) rename contracts/tokens/ERC721/{ERC721T.sol => ERC721TLSW.sol} (94%) rename contracts/tokens/ERC721/extensions/{ERC721BLSW.sol => ERC721EpochBLSW.sol} (96%) rename contracts/tokens/ERC721/extensions/{ERC721TLSW.sol => ERC721EpochTLSW.sol} (96%) diff --git a/README.md b/README.md index c0f6f273..c17a1c95 100644 --- a/README.md +++ b/README.md @@ -72,23 +72,23 @@ contract ExpirableERC20 is ERC20BLSW { ### ERC-7858 -> [!IMPORTANT] This ERC still underdevelopment. - #### Individual Expiration ``` Solidity // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -import {ERC721B} from "@kiwarilabs/contracts/tokens/ERC721/ERC721B.sol"; +import {ERC721BLSW} from "@kiwarilabs/contracts/tokens/ERC721/ERC721B.sol"; import {ERC721EXPBase} from "@kiwarilabs/contracts/tokens/ERC721/ERC721EXPBase.sol"; // Expirable ERC721 with individual expiration -contract ExpirableERC721 is ERC721B { +contract ExpirableERC721 is ERC721BLSW { + + constructor (string memory name_, string memory symbol_) ERC721BLSW(name_, symbol) {} - constructor ERC721B() {} } ``` + #### Epoch Expiration ``` Solidity @@ -101,8 +101,23 @@ import {ERC721EXPEpochBase} from "@kiwarilabs/contracts/tokens/ERC721/extensions // Expirable ERC721 with epoch expiration contract ExpirableERC721 is ERC721BLSW { - constructor ERC721BLSW() {} + constructor ( + string memory name_, + string memory symbol_, + uint256 initialBlockNumber_, + uint40 blocksPerEpoch_, + uint8 windowSize_, + bool development_) + ERC721BLSW( + name_, + symbol_, + initialBlockNumber_, + blocksPerEpoch_, + windowSize_, + development) {} + } + ``` ## Contribute diff --git a/contracts/tokens/ERC721/ERC721B.sol b/contracts/tokens/ERC721/ERC721BLSW.sol similarity index 93% rename from contracts/tokens/ERC721/ERC721B.sol rename to contracts/tokens/ERC721/ERC721BLSW.sol index 3fc7a0a2..03b55a63 100644 --- a/contracts/tokens/ERC721/ERC721B.sol +++ b/contracts/tokens/ERC721/ERC721BLSW.sol @@ -9,7 +9,7 @@ pragma solidity >=0.8.0 <0.9.0; import {ERC721EXPBase} from "./ERC721EXPBase.sol"; -abstract contract ERC721B is ERC721EXPBase { +abstract contract ERC721BLSW is ERC721EXPBase { constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} function _blockNumberProvider() internal view virtual returns (uint256) { diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 2c686d2b..36790689 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -30,7 +30,6 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { super._increaseBalance(account, value); } - // @TODO function support interface function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721, ERC721Enumerable) returns (bool) { return interfaceId == type(IERC7858).interfaceId || super.supportsInterface(interfaceId); } @@ -71,7 +70,7 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { } /// @inheritdoc IERC7858 - function isTokenValid(uint256 tokenId) public view returns (bool) { + function isTokenExpired(uint256 tokenId) public view returns (bool) { return _validation(tokenId); } diff --git a/contracts/tokens/ERC721/ERC721T.sol b/contracts/tokens/ERC721/ERC721TLSW.sol similarity index 94% rename from contracts/tokens/ERC721/ERC721T.sol rename to contracts/tokens/ERC721/ERC721TLSW.sol index 5cbac72d..aee64c17 100644 --- a/contracts/tokens/ERC721/ERC721T.sol +++ b/contracts/tokens/ERC721/ERC721TLSW.sol @@ -9,7 +9,7 @@ pragma solidity >=0.8.0 <0.9.0; import {ERC721EXPBase} from "./ERC721EXPBase.sol"; -abstract contract ERC721T is ERC721EXPBase { +abstract contract ERC721TLSW is ERC721EXPBase { constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} function _blockTimestampProvider() internal view virtual returns (uint256) { diff --git a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol similarity index 96% rename from contracts/tokens/ERC721/extensions/ERC721BLSW.sol rename to contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol index ae943006..b901bc15 100644 --- a/contracts/tokens/ERC721/extensions/ERC721BLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol @@ -9,7 +9,7 @@ pragma solidity >=0.8.0 <0.9.0; import {AbstractBLSW as BLSW} from "../../../abstracts/AbstractBLSW.sol"; import {ERC721EpochBase} from "./ERC721EpochBase.sol"; -abstract contract ERC721BLSW is ERC721EpochBase, BLSW { +abstract contract ERC721EpochBLSW is ERC721EpochBase, BLSW { constructor( string memory name_, string memory symbol_, diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index e4924831..354051eb 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -119,7 +119,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE return _balances[owner]; } - function validBalanceOf(address owner) public view virtual returns (uint256) { + function unexpiredBalanceOf(address owner) public view virtual returns (uint256) { if (owner == address(0)) { revert ERC721InvalidOwner(address(0)); } diff --git a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol b/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol similarity index 96% rename from contracts/tokens/ERC721/extensions/ERC721TLSW.sol rename to contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol index a2a9e791..25efc56a 100644 --- a/contracts/tokens/ERC721/extensions/ERC721TLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol @@ -9,7 +9,7 @@ pragma solidity >=0.8.0 <0.9.0; import {AbstractTLSW as TLSW} from "../../../abstracts/AbstractTLSW.sol"; import {ERC721EpochBase} from "./ERC721EpochBase.sol"; -abstract contract ERC721TLSW is ERC721EpochBase, TLSW { +abstract contract ERC721EpochTLSW is ERC721EpochBase, TLSW { constructor( string memory name_, string memory symbol_, diff --git a/contracts/tokens/ERC721/interfaces/IERC7858.sol b/contracts/tokens/ERC721/interfaces/IERC7858.sol index 2d62d4eb..edd7352c 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858.sol @@ -17,10 +17,10 @@ interface IERC7858 { /** * @dev Checks whether a specific token is expired. - * @param Id The identifier representing the `tokenId` (ERC721). + * @param Id The identifier representing the tokenId. * @return bool True if the token is expired, false otherwise. */ - function isTokenValid(uint256 Id) external view returns (bool); + function isTokenExpired(uint256 tokenId) external view returns (bool); // inherit from ERC-5007 return depends on the type `block.timestamp` or `block.number` // {ERC-5007} return in uint64 MAY not suitable for `block.number` based. diff --git a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol index d7fc330f..4631df3e 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol @@ -35,12 +35,6 @@ interface IERC7858Epoch is IERC7858 { */ function epochType() external view returns (EXPIRY_TYPE); - /** - * @dev Retrieves the validity duration of each token. - * @return uint256 The validity duration of each token in `epoch` unit. - */ - function validityDuration() external view returns (uint256); - /** * @dev Checks whether a specific `epoch` is expired. * @param epoch The `epoch` to check. @@ -52,9 +46,15 @@ interface IERC7858Epoch is IERC7858 { function isEpochExpired(uint256 epoch) external view returns (bool); /** - * @dev Checks whether a specific token is expired. - * @param tokenId The identifier representing the `tokenId` (ERC721). - * @return bool True if the token is expired, false otherwise. + * @dev Retrieves the balance of unexpired tokens owned by an account. + * @param account The address of the account. + * @return uint256 The amount of unexpired tokens owned by an account. */ - function isTokenValid(uint256 tokenId) external view returns (bool); + function unexpiredBalanceOf(address account) external view returns (uint256); + + /** + * @dev Retrieves the validity duration of each token. + * @return uint256 The validity duration of each token in `epoch` unit. + */ + function validityDuration() external view returns (uint256); } From 4944ab0629ad851a1f6b86f1556bccad76b61681 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 26 Feb 2025 12:22:45 +0700 Subject: [PATCH 31/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20handle=20burn?= =?UTF-8?q?=20and=20re-mint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tokens/ERC721/extensions/ERC721EpochBase.sol | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 354051eb..94c63236 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -234,11 +234,15 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE uint256 tokenPointer = _tokenPointers[tokenId]; address from = _ownerOf(tokenId); // if the tokenId is not exist before minting it - if (tokenPointer == 0) { - tokenPointer = pointer; - _tokenPointers[tokenId] = pointer; + if (to == address(0)) { + _tokenPointers[tokenId] = 0; } else { - pointer = tokenPointer; + if (tokenPointer == 0) { + tokenPointer = pointer; + _tokenPointers[tokenId] = pointer; + } else { + pointer = tokenPointer; + } } uint256 epoch = _getEpoch(pointer); From 8f6e09697d0b92a3dc195113b10895e6497e6f90 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 26 Feb 2025 14:22:09 +0700 Subject: [PATCH 32/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20handling=20tok?= =?UTF-8?q?en=20timestamp=20check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC721EXPBase.sol | 53 ++++++++++++------- .../ERC721/extensions/ERC721EpochBase.sol | 18 ++++--- .../tokens/ERC721/interfaces/IERC7858.sol | 16 +++++- 3 files changed, 59 insertions(+), 28 deletions(-) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 36790689..acccef4e 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -13,12 +13,12 @@ import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { - struct AssetStamp { + struct AssetTimeStamp { uint256 start; uint256 end; } - mapping(uint256 => AssetStamp) private _tokensTimestamp; + mapping(uint256 => AssetTimeStamp) private _tokensTimeStamp; constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} @@ -35,41 +35,54 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { } function _validation(uint256 tokenId) internal view returns (bool) { - if (_ownerOf(tokenId) == address(0)) return false; - AssetStamp memory timestamp = _tokensTimestamp[tokenId]; + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + AssetTimeStamp memory timestamp = _tokensTimeStamp[tokenId]; uint256 current = _pointerProvider(); - if (current < timestamp.start || current >= timestamp.end) { + // if start and end is {0, 0} mean token non-expirable and return false. + if (timestamp.start == 0 && timestamp.end == 0) { return false; + } else { + return current >= timestamp.end; } - // if start and end is {0, 0} mean token non-expirable and return true. - return true; } - function _updateStamp(uint256 tokenId, uint64 start, uint64 end) internal { - if (start >= end) { - // @TODO revert ERC5007InvalidTime() + function _updateTimeStamp(uint256 tokenId, uint64 start, uint64 end) internal { + if ((start <= end) && (start != 0) && (end != 0)) { + revert ERC7858InvalidTimeStamp(start, end); } - _tokensTimestamp[tokenId].start = start; - _tokensTimestamp[tokenId].end = end; - // @TODO emit tokenTimeSet(tokenId, start, end); + _tokensTimeStamp[tokenId].start = start; + _tokensTimeStamp[tokenId].end = end; + + emit TokenExpiryUpdated(tokenId, start, end); } - function _mintWithStamp(address to, uint256 tokenId, uint64 start, uint64 end) internal { + function _mintWithTimeStamp(address to, uint256 tokenId, uint64 start, uint64 end) internal { _mint(to, tokenId); - _updateStamp(tokenId, start, end); + _updateTimeStamp(tokenId, start, end); + } + + function _burnAndClearTimeStamp(uint256 tokenId) internal { + _burn(tokenId); + delete _tokensTimeStamp[tokenId]; } - /// @inheritdoc IERC7858 + /** + * @dev See {IERC7858-startTime}. + */ function startTime(uint256 tokenId) public view virtual override returns (uint256) { - return _tokensTimestamp[tokenId].start; + return _tokensTimeStamp[tokenId].start; } - /// @inheritdoc IERC7858 + /** + * @dev See {IERC7858-endTime}. + */ function endTime(uint256 tokenId) public view virtual override returns (uint256) { - return _tokensTimestamp[tokenId].end; + return _tokensTimeStamp[tokenId].end; } - /// @inheritdoc IERC7858 + /** + * @dev See {IERC7858-isTokenExpired}. + */ function isTokenExpired(uint256 tokenId) public view returns (bool) { return _validation(tokenId); } diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 94c63236..59d3d553 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -236,13 +236,14 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE // if the tokenId is not exist before minting it if (to == address(0)) { _tokenPointers[tokenId] = 0; + } + if (tokenPointer == 0) { + tokenPointer = pointer; + _tokenPointers[tokenId] = pointer; + + emit TokenExpiryUpdated(tokenId, pointer, pointer + _getPointersInWindow()); } else { - if (tokenPointer == 0) { - tokenPointer = pointer; - _tokenPointers[tokenId] = pointer; - } else { - pointer = tokenPointer; - } + pointer = tokenPointer; } uint256 epoch = _getEpoch(pointer); @@ -373,7 +374,10 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE /// @dev See {IERC7858-endTime}. function endTime(uint256 tokenId) external view returns (uint256) { - return _tokenPointers[tokenId] + _getPointersInWindow(); + uint256 startTimeCache = _tokenPointers[tokenId]; + if (startTimeCache != 0) { + return startTimeCache + _getPointersInWindow(); + } } /// @dev See {IERC7858Epoch-currentEpoch}. diff --git a/contracts/tokens/ERC721/interfaces/IERC7858.sol b/contracts/tokens/ERC721/interfaces/IERC7858.sol index edd7352c..e0106326 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858.sol @@ -9,6 +9,20 @@ interface IERC7858 { TIME_BASED // block.timestamp } + /** + * @dev Emitted when the expiration date of a token is set or updated. + * @param tokenId The identifier of the token ERC721 `tokenId`. + * @param startTime The start time of the token (block number or timestamp based on `expiryType`). + * @param endTime The end time of the token (block number or timestamp based on `expiryType`). + */ + event TokenExpiryUpdated( + uint256 indexed tokenId, + uint256 indexed startTime, + uint256 indexed endTime + ); + + error ERC7858InvalidTimeStamp(uint256 start, uint256 end); + /** * @dev Returns the type of the expiry. * @return EXPIRY_TYPE Enum value indicating the unit of an expiry. @@ -17,7 +31,7 @@ interface IERC7858 { /** * @dev Checks whether a specific token is expired. - * @param Id The identifier representing the tokenId. + * @param tokenId The identifier representing the tokenId. * @return bool True if the token is expired, false otherwise. */ function isTokenExpired(uint256 tokenId) external view returns (bool); From 9ea61e21f9a6510fbdff44cff0eae1d792708a75 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 26 Feb 2025 14:45:38 +0700 Subject: [PATCH 33/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20logic=20error=20com?= =?UTF-8?q?pare=20start=20and=20end?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC721EXPBase.sol | 2 +- contracts/tokens/ERC721/interfaces/IERC7858.sol | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index acccef4e..9fe00347 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -47,7 +47,7 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { } function _updateTimeStamp(uint256 tokenId, uint64 start, uint64 end) internal { - if ((start <= end) && (start != 0) && (end != 0)) { + if (start >= end) { revert ERC7858InvalidTimeStamp(start, end); } _tokensTimeStamp[tokenId].start = start; diff --git a/contracts/tokens/ERC721/interfaces/IERC7858.sol b/contracts/tokens/ERC721/interfaces/IERC7858.sol index e0106326..c3425bf1 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858.sol @@ -15,11 +15,7 @@ interface IERC7858 { * @param startTime The start time of the token (block number or timestamp based on `expiryType`). * @param endTime The end time of the token (block number or timestamp based on `expiryType`). */ - event TokenExpiryUpdated( - uint256 indexed tokenId, - uint256 indexed startTime, - uint256 indexed endTime - ); + event TokenExpiryUpdated(uint256 indexed tokenId, uint256 indexed startTime, uint256 indexed endTime); error ERC7858InvalidTimeStamp(uint256 start, uint256 end); From 998257e0a235a72ab9d794957c73dbce6e89ae34 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 26 Feb 2025 14:53:59 +0700 Subject: [PATCH 34/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20startTime=20and=20e?= =?UTF-8?q?ndTime=20should=20revert=20if=20id=20not=20exist?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC721EXPBase.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 9fe00347..b55e2d3a 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -70,6 +70,7 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { * @dev See {IERC7858-startTime}. */ function startTime(uint256 tokenId) public view virtual override returns (uint256) { + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); return _tokensTimeStamp[tokenId].start; } @@ -77,6 +78,7 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { * @dev See {IERC7858-endTime}. */ function endTime(uint256 tokenId) public view virtual override returns (uint256) { + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); return _tokensTimeStamp[tokenId].end; } From 8cc98b8b8e9fb9435dd4fdb8267d0dd5f51a55f9 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Thu, 27 Feb 2025 14:41:45 +0700 Subject: [PATCH 35/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20template=20test=20?= =?UTF-8?q?ERC7858=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC721EXPBase.sol | 3 +- .../tokens/ERC721/MockERC721BLSW.sol | 28 ++++++++++++ .../tokens/ERC721/MockERC721TLSW.sol | 28 ++++++++++++ test/constant.test.ts | 28 ++++++++++++ .../ERC721/ERC7858Behavior/index.test.ts | 21 +++++++++ test/tokens/ERC721/base/deployer.test.ts | 45 +++++++++++++++++++ .../ERC721/extensions/ERC7858Epoch}/.keep | 0 7 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 mocks/contracts/tokens/ERC721/MockERC721BLSW.sol create mode 100644 mocks/contracts/tokens/ERC721/MockERC721TLSW.sol create mode 100644 test/tokens/ERC721/ERC7858Behavior/index.test.ts create mode 100644 test/tokens/ERC721/base/deployer.test.ts rename {mocks/contracts/tokens/ERC721 => test/tokens/ERC721/extensions/ERC7858Epoch}/.keep (100%) diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index b55e2d3a..164b1a11 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -46,7 +46,8 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { } } - function _updateTimeStamp(uint256 tokenId, uint64 start, uint64 end) internal { + function _updateTimeStamp(uint256 tokenId, uint256 start, uint256 end) internal { + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); if (start >= end) { revert ERC7858InvalidTimeStamp(start, end); } diff --git a/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol b/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol new file mode 100644 index 00000000..80141321 --- /dev/null +++ b/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9.0; + +import {ERC721BLSW} from "../../../../contracts/tokens/ERC721/ERC721BLSW.sol"; + +contract MockERC721BLSW is ERC721BLSW { + constructor(string memory _name, string memory _symbol) ERC721BLSW(_name, _symbol) {} + + function _pointerProvider() internal view override returns (uint256) { + return block.number; + } + + function expiryType() public pure override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.BLOCKS_BASED; + } + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } + + function updateTimeStamp(uint256 tokenId, uint256 start, uint256 end) public { + _updateTimeStamp(tokenId, start, end); + } +} diff --git a/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol b/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol new file mode 100644 index 00000000..2a819576 --- /dev/null +++ b/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9.0; + +import {ERC721TLSW} from "../../../../contracts/tokens/ERC721/ERC721TLSW.sol"; + +contract MockERC721TLSW is ERC721TLSW { + constructor(string memory _name, string memory _symbol) ERC721TLSW(_name, _symbol) {} + + function _pointerProvider() internal view override returns (uint256) { + return block.timestamp; + } + + function expiryType() public pure override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.TIME_BASED; + } + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } + + function updateTimeStamp(uint256 tokenId, uint256 start, uint256 end) public { + _updateTimeStamp(tokenId, start, end); + } +} diff --git a/test/constant.test.ts b/test/constant.test.ts index 7df24061..909fa728 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -44,6 +44,13 @@ export const ERC7818 = { events: {}, }; +export const ERC7858 = { + errors: { + ERC7858InvalidTimeStamp: "ERC7858InvalidTimeStamp", + }, + events: {}, +}; + export const ERC20 = { constructor: { name: "PointToken", @@ -63,6 +70,15 @@ export const ERC20 = { }, }; +export const ERC721 = { + constructor: { + name: "VoucherToken", + symbol: "VOUCHER", + }, + errors: {}, + events: {}, +}; + export const ERC7818Permit = { name: "MockERC7818Permit", events: {}, @@ -210,6 +226,18 @@ export const ERC20TLSW = { events: {}, }; +export const ERC721TLSW = { + name: "MockERC721TLSW", + errors: {}, + events: {}, +}; + +export const ERC721BLSW = { + name: "MockERC721BLSW", + errors: {}, + events: {}, +}; + export const SlidingWindowLibrary = { name: "MockSlidingWindowLibrary", errors: {}, diff --git a/test/tokens/ERC721/ERC7858Behavior/index.test.ts b/test/tokens/ERC721/ERC7858Behavior/index.test.ts new file mode 100644 index 00000000..90f7f8ee --- /dev/null +++ b/test/tokens/ERC721/ERC7858Behavior/index.test.ts @@ -0,0 +1,21 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +// import {constants} from "../../../constant.test"; +// import * as Interface from "./interface.test"; +// import * as Burn from "./burn.test"; +// import * as Transfer from "./transfer.test"; +// import * as TransferAtEpoch from "./transferAtEpoch.test"; +// import * as TransferFromAtEpoch from "./transferFromAtEpoch.test"; + +// export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { +// describe("ERC7858Behavior", async function () { +// Interface.run({epochType}); +// Burn.run({epochType}); +// Transfer.run({epochType}); +// TransferAtEpoch.run({epochType}); +// TransferFromAtEpoch.run({epochType}); +// }); +// }; diff --git a/test/tokens/ERC721/base/deployer.test.ts b/test/tokens/ERC721/base/deployer.test.ts new file mode 100644 index 00000000..9597f928 --- /dev/null +++ b/test/tokens/ERC721/base/deployer.test.ts @@ -0,0 +1,45 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {ethers} from "hardhat"; +import {constants, ERC721, ERC721BLSW, ERC721TLSW} from "../../../constant.test"; +import {MockERC721BLSW, MockERC721TLSW} from "../../../../typechain-types/mocks/contracts/tokens/ERC721"; + +export const deployERC721Selector = async function ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED} = {}) { + if (epochType === constants.EPOCH_TYPE.BLOCKS_BASED) { + return await deployERC721BLSW({}); + } + return await deployERC721TLSW({}); +}; + +export const deployERC721TLSW = async function ({} = {}) { + const [deployer, alice, bob, charlie, dave] = await ethers.getSigners(); + const ERC721TLSWContract = await ethers.getContractFactory(ERC721TLSW.name, deployer); + const erc721exp = (await ERC721TLSWContract.deploy(ERC721.constructor.name, ERC721.constructor.symbol)) as any as MockERC721TLSW; + await erc721exp.waitForDeployment(); + return { + erc721exp, + deployer, + alice, + bob, + charlie, + dave, + }; +}; + +export const deployERC721BLSW = async function ({} = {}) { + const [deployer, alice, bob, charlie, dave] = await ethers.getSigners(); + const ERC20BLSWContract = await ethers.getContractFactory(ERC721BLSW.name, deployer); + const erc721exp = (await ERC20BLSWContract.deploy(ERC721.constructor.name, ERC721.constructor.symbol)) as any as MockERC721BLSW; + await erc721exp.waitForDeployment(); + return { + erc721exp, + deployer, + alice, + bob, + charlie, + dave, + }; +}; diff --git a/mocks/contracts/tokens/ERC721/.keep b/test/tokens/ERC721/extensions/ERC7858Epoch/.keep similarity index 100% rename from mocks/contracts/tokens/ERC721/.keep rename to test/tokens/ERC721/extensions/ERC7858Epoch/.keep From 8531b83b838668da0637ce80465af27410f8f0a7 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Fri, 28 Feb 2025 11:48:40 +0700 Subject: [PATCH 36/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20testing?= =?UTF-8?q?=20for=20ERC7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/constant.test.ts | 17 ++++- .../ERC20/ERC7818Behavior/index.test.ts | 2 +- test/tokens/ERC20/base/index.test.ts | 2 +- test/tokens/ERC20/extensions/index.test.ts | 2 +- test/tokens/ERC20/index.test.ts | 12 ++-- .../ERC721/ERC7858Behavior/index.test.ts | 20 +++--- test/tokens/ERC721/base/approve.test.ts | 21 ++++++ test/tokens/ERC721/base/burn.test.ts | 21 ++++++ test/tokens/ERC721/base/index.test.ts | 21 ++++++ test/tokens/ERC721/base/mint.test.ts | 67 +++++++++++++++++++ test/tokens/ERC721/base/transfer.test.ts | 21 ++++++ test/tokens/ERC721/base/transferFrom.test.ts | 21 ++++++ .../ERC7858Epoch/{.keep => index.test.ts} | 0 test/tokens/ERC721/extensions/index.test.ts | 13 ++++ test/tokens/ERC721/index.test.ts | 19 +++++- 15 files changed, 234 insertions(+), 25 deletions(-) create mode 100644 test/tokens/ERC721/base/approve.test.ts create mode 100644 test/tokens/ERC721/base/burn.test.ts create mode 100644 test/tokens/ERC721/base/index.test.ts create mode 100644 test/tokens/ERC721/base/mint.test.ts create mode 100644 test/tokens/ERC721/base/transfer.test.ts create mode 100644 test/tokens/ERC721/base/transferFrom.test.ts rename test/tokens/ERC721/extensions/ERC7858Epoch/{.keep => index.test.ts} (100%) create mode 100644 test/tokens/ERC721/extensions/index.test.ts diff --git a/test/constant.test.ts b/test/constant.test.ts index 909fa728..c0d7a38d 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -75,8 +75,21 @@ export const ERC721 = { name: "VoucherToken", symbol: "VOUCHER", }, - errors: {}, - events: {}, + errors: { + ERC721InvalidOwner: "ERC721InvalidOwner", + ERC721NonexistentToken: "ERC721NonexistentToken", + ERC721IncorrectOwner: "ERC721IncorrectOwner", + ERC721InvalidSender: "ERC721InvalidSender", + ERC721InvalidReceiver: "ERC721InvalidReceiver", + ERC721InsufficientApproval: "ERC721InsufficientApproval", + ERC721InvalidApprover: "ERC721InvalidApprover", + ERC721InvalidOperator: "ERC721InvalidOperator", + }, + events: { + Transfer: "Transfer", + Approval: "Approval", + ApprovalForAll: "ApprovalForAll", + }, }; export const ERC7818Permit = { diff --git a/test/tokens/ERC20/ERC7818Behavior/index.test.ts b/test/tokens/ERC20/ERC7818Behavior/index.test.ts index 17e9e9d9..ee67e4b2 100644 --- a/test/tokens/ERC20/ERC7818Behavior/index.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/index.test.ts @@ -11,7 +11,7 @@ import * as TransferAtEpoch from "./transferAtEpoch.test"; import * as TransferFromAtEpoch from "./transferFromAtEpoch.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("ERC7818Behavior", async function () { + describe("ERC7818:Behavior", async function () { Interface.run({epochType}); Burn.run({epochType}); Transfer.run({epochType}); diff --git a/test/tokens/ERC20/base/index.test.ts b/test/tokens/ERC20/base/index.test.ts index fab3d7b1..8428afa0 100644 --- a/test/tokens/ERC20/base/index.test.ts +++ b/test/tokens/ERC20/base/index.test.ts @@ -11,7 +11,7 @@ import * as Approval from "./approve.test"; import * as TransferFrom from "./transferFrom.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("base", async function () { + describe("ERC7818:Base", async function () { Mint.run({epochType}); Burn.run({epochType}); Approval.run({epochType}); diff --git a/test/tokens/ERC20/extensions/index.test.ts b/test/tokens/ERC20/extensions/index.test.ts index 4ca50951..56ce1e6c 100644 --- a/test/tokens/ERC20/extensions/index.test.ts +++ b/test/tokens/ERC20/extensions/index.test.ts @@ -12,7 +12,7 @@ import * as ERC7818Frozen from "./ERC7818Frozen/index.test"; import * as ERC7818Permit from "./ERC7818Permit/index.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("extensions", async function () { + describe("ERC7818:Extensions", async function () { ERC7818Blacklist.run({epochType}); ERC7818MintQuota.run({epochType}); ERC7818Exception.run({epochType}); diff --git a/test/tokens/ERC20/index.test.ts b/test/tokens/ERC20/index.test.ts index ce075747..79b19ab6 100644 --- a/test/tokens/ERC20/index.test.ts +++ b/test/tokens/ERC20/index.test.ts @@ -4,22 +4,22 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {constants} from "../../constant.test"; -import * as ERC20Base from "./base/index.test"; -import * as ERC7818Behavior from "./ERC7818Behavior/index.test"; +import * as base from "./base/index.test"; +import * as behavior from "./ERC7818Behavior/index.test"; import * as extensions from "./extensions/index.test"; export const run = async () => { describe("ERC20BLSW", async function () { const epochType = constants.EPOCH_TYPE.BLOCKS_BASED; - ERC20Base.run({epochType}); - ERC7818Behavior.run({epochType}); + base.run({epochType}); + behavior.run({epochType}); extensions.run({epochType}); }); describe("ERC20TLSW", async function () { const epochType = constants.EPOCH_TYPE.TIME_BASED; - ERC20Base.run({epochType}); - ERC7818Behavior.run({epochType}); + base.run({epochType}); + behavior.run({epochType}); extensions.run({epochType}); }); }; diff --git a/test/tokens/ERC721/ERC7858Behavior/index.test.ts b/test/tokens/ERC721/ERC7858Behavior/index.test.ts index 90f7f8ee..e6c45d1e 100644 --- a/test/tokens/ERC721/ERC7858Behavior/index.test.ts +++ b/test/tokens/ERC721/ERC7858Behavior/index.test.ts @@ -3,19 +3,15 @@ // This file is licensed under the Apache License 2.0. // License text available at https://www.apache.org/licenses/LICENSE-2.0 -// import {constants} from "../../../constant.test"; +import {constants} from "../../../constant.test"; // import * as Interface from "./interface.test"; // import * as Burn from "./burn.test"; // import * as Transfer from "./transfer.test"; -// import * as TransferAtEpoch from "./transferAtEpoch.test"; -// import * as TransferFromAtEpoch from "./transferFromAtEpoch.test"; -// export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { -// describe("ERC7858Behavior", async function () { -// Interface.run({epochType}); -// Burn.run({epochType}); -// Transfer.run({epochType}); -// TransferAtEpoch.run({epochType}); -// TransferFromAtEpoch.run({epochType}); -// }); -// }; +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + // describe("ERC7858:Behavior", async function () { + // Interface.run({epochType}); + // Burn.run({epochType}); + // Transfer.run({epochType}); + // }); +}; diff --git a/test/tokens/ERC721/base/approve.test.ts b/test/tokens/ERC721/base/approve.test.ts new file mode 100644 index 00000000..c5d524da --- /dev/null +++ b/test/tokens/ERC721/base/approve.test.ts @@ -0,0 +1,21 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {ERC721, constants} from "../../../constant.test"; +import {deployERC721Selector} from "./deployer.test"; +import {hardhat_impersonate, hardhat_reset, hardhat_setBalance, hardhat_stopImpersonating, ethers} from "../../../utils.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Approval", async function () { + const tokenId = 1; + + afterEach(async function () { + await hardhat_reset(); + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/base/burn.test.ts b/test/tokens/ERC721/base/burn.test.ts new file mode 100644 index 00000000..375d93ce --- /dev/null +++ b/test/tokens/ERC721/base/burn.test.ts @@ -0,0 +1,21 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset} from "../../../utils.test"; +import {deployERC721Selector} from "./deployer.test"; +import {constants, ERC721} from "../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Burn", async function () { + const tokenId = 1; + + afterEach(async function () { + await hardhat_reset(); + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/base/index.test.ts b/test/tokens/ERC721/base/index.test.ts new file mode 100644 index 00000000..267a9a8f --- /dev/null +++ b/test/tokens/ERC721/base/index.test.ts @@ -0,0 +1,21 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {constants} from "../../../constant.test"; +import * as Mint from "./mint.test"; +import * as Burn from "./burn.test"; +import * as Transfer from "./transfer.test"; +import * as Approval from "./approve.test"; +import * as TransferFrom from "./transferFrom.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("ERC7858:Base", async function () { + Mint.run({epochType}); + Burn.run({epochType}); + Approval.run({epochType}); + Transfer.run({epochType}); + TransferFrom.run({epochType}); + }); +}; diff --git a/test/tokens/ERC721/base/mint.test.ts b/test/tokens/ERC721/base/mint.test.ts new file mode 100644 index 00000000..a82927b3 --- /dev/null +++ b/test/tokens/ERC721/base/mint.test.ts @@ -0,0 +1,67 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../utils.test"; +import {deployERC721Selector} from "./deployer.test"; +import {ERC721, ERC7858, constants} from "../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Mint", async function () { + const tokenId = 1; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + it("[SUCCESS] mint unexpirable token", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.startTime(tokenId)).to.equal(startTime); + expect(await erc721exp.endTime(tokenId)).to.equal(endTime); + expect(await erc721exp.isTokenExpired(tokenId)).to.equal(false); + }); + + it("[SUCCESS] mint expirable token", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + if (epochType == constants.EPOCH_TYPE.BLOCKS_BASED) { + startTime = await hardhat_latestBlock(); + } else { + startTime = await hardhat_latest(); + } + endTime = startTime + 1000; + await erc721exp.updateTimeStamp(tokenId, startTime, endTime); + expect(await erc721exp.startTime(tokenId)).to.equal(startTime); + expect(await erc721exp.endTime(tokenId)).to.equal(endTime); + expect(await erc721exp.isTokenExpired(tokenId)).to.equal(false); + }); + + it("[FAILED] mint expirable token with invalid timestamp", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + startTime = 1000; + await expect(erc721exp.updateTimeStamp(tokenId, startTime, endTime)) + .to.be.revertedWithCustomError(erc721exp, ERC7858.errors.ERC7858InvalidTimeStamp) + .withArgs(startTime, endTime); + }); + }); +}; diff --git a/test/tokens/ERC721/base/transfer.test.ts b/test/tokens/ERC721/base/transfer.test.ts new file mode 100644 index 00000000..1d8720f7 --- /dev/null +++ b/test/tokens/ERC721/base/transfer.test.ts @@ -0,0 +1,21 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {deployERC721Selector} from "./deployer.test"; +import {ERC721, constants} from "../../../constant.test"; +import {ethers, hardhat_impersonate, hardhat_reset, hardhat_setBalance, hardhat_stopImpersonating} from "../../../utils.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Transfer", async function () { + const tokenId = 1; + + afterEach(async function () { + await hardhat_reset(); + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/base/transferFrom.test.ts b/test/tokens/ERC721/base/transferFrom.test.ts new file mode 100644 index 00000000..43c6f53f --- /dev/null +++ b/test/tokens/ERC721/base/transferFrom.test.ts @@ -0,0 +1,21 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {deployERC721Selector} from "./deployer.test"; +import {ERC721, constants} from "../../../constant.test"; +import {hardhat_reset} from "../../../utils.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("TransferFrom", async function () { + const tokenId = 1; + + afterEach(async function () { + await hardhat_reset(); + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/.keep b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts similarity index 100% rename from test/tokens/ERC721/extensions/ERC7858Epoch/.keep rename to test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts diff --git a/test/tokens/ERC721/extensions/index.test.ts b/test/tokens/ERC721/extensions/index.test.ts new file mode 100644 index 00000000..c1c8f4fd --- /dev/null +++ b/test/tokens/ERC721/extensions/index.test.ts @@ -0,0 +1,13 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {constants} from "../../../constant.test"; +// import * as ERC7858Epoch from "./ERC7858Epoch/index.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("extensions", async function () { + // ERC7858Epoch.run({epochType}); + }); +}; diff --git a/test/tokens/ERC721/index.test.ts b/test/tokens/ERC721/index.test.ts index f9b4dee1..e330e566 100644 --- a/test/tokens/ERC721/index.test.ts +++ b/test/tokens/ERC721/index.test.ts @@ -3,8 +3,23 @@ // This file is licensed under the Apache License 2.0. // License text available at https://www.apache.org/licenses/LICENSE-2.0 +import {constants} from "../../constant.test"; +import * as base from "./base/index.test"; +import * as behavior from "./ERC7858Behavior/index.test"; +import * as extensions from "./extensions/index.test"; + export const run = async () => { - describe("ERC721EXPBase", async function () { - // @TODO + describe("ERC721BLSW", async function () { + const epochType = constants.EPOCH_TYPE.BLOCKS_BASED; + base.run({epochType}); + behavior.run({epochType}); + extensions.run({epochType}); + }); + + describe("ERC721TLSW", async function () { + const epochType = constants.EPOCH_TYPE.TIME_BASED; + base.run({epochType}); + behavior.run({epochType}); + extensions.run({epochType}); }); }; From f0c1d687b55867fa42bb411498d5f3c3932585df Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 1 Mar 2025 09:14:24 +0700 Subject: [PATCH 37/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20test=20ca?= =?UTF-8?q?se=20for=20ERC7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC721BLSW.sol | 8 +- contracts/tokens/ERC721/ERC721EXPBase.sol | 11 +-- contracts/tokens/ERC721/ERC721TLSW.sol | 8 +- .../tokens/ERC721/MockERC721BLSW.sol | 13 ++- .../tokens/ERC721/MockERC721TLSW.sol | 13 ++- .../ERC721/ERC7858Behavior/index.test.ts | 17 ---- test/tokens/ERC721/base/burn.test.ts | 56 ++++++++++++- test/tokens/ERC721/base/index.test.ts | 4 +- test/tokens/ERC721/base/interface.test.ts | 81 +++++++++++++++++++ .../extensions/ERC7858Epoch/index.test.ts | 4 + test/tokens/ERC721/index.test.ts | 3 - test/tokens/index.test.ts | 2 +- 12 files changed, 161 insertions(+), 59 deletions(-) delete mode 100644 test/tokens/ERC721/ERC7858Behavior/index.test.ts create mode 100644 test/tokens/ERC721/base/interface.test.ts diff --git a/contracts/tokens/ERC721/ERC721BLSW.sol b/contracts/tokens/ERC721/ERC721BLSW.sol index 03b55a63..323c6c6f 100644 --- a/contracts/tokens/ERC721/ERC721BLSW.sol +++ b/contracts/tokens/ERC721/ERC721BLSW.sol @@ -12,15 +12,11 @@ import {ERC721EXPBase} from "./ERC721EXPBase.sol"; abstract contract ERC721BLSW is ERC721EXPBase { constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} - function _blockNumberProvider() internal view virtual returns (uint256) { - return block.number; - } - - function _expiryType() internal pure virtual override returns (EXPIRY_TYPE) { + function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; } function _pointerProvider() internal view virtual override returns (uint256) { - return _blockNumberProvider(); + return block.number; } } diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC721EXPBase.sol index 164b1a11..635cb991 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC721EXPBase.sol @@ -57,13 +57,8 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { emit TokenExpiryUpdated(tokenId, start, end); } - function _mintWithTimeStamp(address to, uint256 tokenId, uint64 start, uint64 end) internal { - _mint(to, tokenId); - _updateTimeStamp(tokenId, start, end); - } - - function _burnAndClearTimeStamp(uint256 tokenId) internal { - _burn(tokenId); + function _clearTimeStamp(uint256 tokenId) internal { + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); delete _tokensTimeStamp[tokenId]; } @@ -90,7 +85,7 @@ abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { return _validation(tokenId); } - function _expiryType() internal pure virtual returns (EXPIRY_TYPE) {} + function expiryType() public view virtual returns (EXPIRY_TYPE) {} function _pointerProvider() internal view virtual returns (uint256) {} } diff --git a/contracts/tokens/ERC721/ERC721TLSW.sol b/contracts/tokens/ERC721/ERC721TLSW.sol index aee64c17..1c25d415 100644 --- a/contracts/tokens/ERC721/ERC721TLSW.sol +++ b/contracts/tokens/ERC721/ERC721TLSW.sol @@ -12,15 +12,11 @@ import {ERC721EXPBase} from "./ERC721EXPBase.sol"; abstract contract ERC721TLSW is ERC721EXPBase { constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} - function _blockTimestampProvider() internal view virtual returns (uint256) { - return block.timestamp; - } - - function _expiryType() internal pure virtual override returns (EXPIRY_TYPE) { + function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.TIME_BASED; } function _pointerProvider() internal view virtual override returns (uint256) { - return _blockTimestampProvider(); + return block.timestamp; } } diff --git a/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol b/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol index 80141321..cc930146 100644 --- a/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol +++ b/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol @@ -6,22 +6,19 @@ import {ERC721BLSW} from "../../../../contracts/tokens/ERC721/ERC721BLSW.sol"; contract MockERC721BLSW is ERC721BLSW { constructor(string memory _name, string memory _symbol) ERC721BLSW(_name, _symbol) {} - function _pointerProvider() internal view override returns (uint256) { - return block.number; - } - - function expiryType() public pure override returns (EXPIRY_TYPE) { - return EXPIRY_TYPE.BLOCKS_BASED; - } - function mint(address to, uint256 tokenId) public { _mint(to, tokenId); } function burn(uint256 tokenId) public { + _clearTimeStamp(tokenId); _burn(tokenId); } + function clearTimeStamp(uint256 tokenId) public { + _clearTimeStamp(tokenId); + } + function updateTimeStamp(uint256 tokenId, uint256 start, uint256 end) public { _updateTimeStamp(tokenId, start, end); } diff --git a/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol b/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol index 2a819576..143a6221 100644 --- a/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol +++ b/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol @@ -6,22 +6,19 @@ import {ERC721TLSW} from "../../../../contracts/tokens/ERC721/ERC721TLSW.sol"; contract MockERC721TLSW is ERC721TLSW { constructor(string memory _name, string memory _symbol) ERC721TLSW(_name, _symbol) {} - function _pointerProvider() internal view override returns (uint256) { - return block.timestamp; - } - - function expiryType() public pure override returns (EXPIRY_TYPE) { - return EXPIRY_TYPE.TIME_BASED; - } - function mint(address to, uint256 tokenId) public { _mint(to, tokenId); } function burn(uint256 tokenId) public { + _clearTimeStamp(tokenId); _burn(tokenId); } + function clearTimeStamp(uint256 tokenId) public { + _clearTimeStamp(tokenId); + } + function updateTimeStamp(uint256 tokenId, uint256 start, uint256 end) public { _updateTimeStamp(tokenId, start, end); } diff --git a/test/tokens/ERC721/ERC7858Behavior/index.test.ts b/test/tokens/ERC721/ERC7858Behavior/index.test.ts deleted file mode 100644 index e6c45d1e..00000000 --- a/test/tokens/ERC721/ERC7858Behavior/index.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. -// Node module: kiwari-labs-contracts -// This file is licensed under the Apache License 2.0. -// License text available at https://www.apache.org/licenses/LICENSE-2.0 - -import {constants} from "../../../constant.test"; -// import * as Interface from "./interface.test"; -// import * as Burn from "./burn.test"; -// import * as Transfer from "./transfer.test"; - -export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - // describe("ERC7858:Behavior", async function () { - // Interface.run({epochType}); - // Burn.run({epochType}); - // Transfer.run({epochType}); - // }); -}; diff --git a/test/tokens/ERC721/base/burn.test.ts b/test/tokens/ERC721/base/burn.test.ts index 375d93ce..a4fc5e41 100644 --- a/test/tokens/ERC721/base/burn.test.ts +++ b/test/tokens/ERC721/base/burn.test.ts @@ -4,18 +4,72 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {expect} from "chai"; -import {hardhat_reset} from "../../../utils.test"; +import {hardhat_latest, hardhat_latestBlock, hardhat_reset} from "../../../utils.test"; import {deployERC721Selector} from "./deployer.test"; import {constants, ERC721} from "../../../constant.test"; +import {ZeroAddress} from "ethers"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Burn", async function () { const tokenId = 1; + let startTime = 0; + let endTime = 0; afterEach(async function () { await hardhat_reset(); + /** */ + startTime = 0; + endTime = 0; }); // @TODO + it("[SUCCESS] burn expirable token", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.startTime(tokenId)).to.equal(startTime); + expect(await erc721exp.endTime(tokenId)).to.equal(endTime); + await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, ZeroAddress, tokenId); + await expect(erc721exp.isTokenExpired(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + }); + + it("[SUCCESS] burn unexpirable token", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.startTime(tokenId)).to.equal(startTime); + expect(await erc721exp.endTime(tokenId)).to.equal(endTime); + await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, ZeroAddress, tokenId); + await expect(erc721exp.isTokenExpired(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + }); + + it("[SUCCESS] burn and re-mint token that has timestamp", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + if (epochType == constants.EPOCH_TYPE.BLOCKS_BASED) { + startTime = await hardhat_latestBlock(); + } else { + startTime = await hardhat_latest(); + } + endTime = startTime + 1000; + await erc721exp.updateTimeStamp(tokenId, startTime, endTime); + expect(await erc721exp.startTime(tokenId)).to.equal(startTime); + expect(await erc721exp.endTime(tokenId)).to.equal(endTime); + await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, ZeroAddress, tokenId); + await expect(erc721exp.isTokenExpired(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.startTime(tokenId)).to.equal(0); + expect(await erc721exp.endTime(tokenId)).to.equal(0); + }); }); }; diff --git a/test/tokens/ERC721/base/index.test.ts b/test/tokens/ERC721/base/index.test.ts index 267a9a8f..4519d4f7 100644 --- a/test/tokens/ERC721/base/index.test.ts +++ b/test/tokens/ERC721/base/index.test.ts @@ -6,8 +6,9 @@ import {constants} from "../../../constant.test"; import * as Mint from "./mint.test"; import * as Burn from "./burn.test"; -import * as Transfer from "./transfer.test"; import * as Approval from "./approve.test"; +import * as Interface from "./interface.test"; +import * as Transfer from "./transfer.test"; import * as TransferFrom from "./transferFrom.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { @@ -15,6 +16,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { Mint.run({epochType}); Burn.run({epochType}); Approval.run({epochType}); + Interface.run({epochType}); Transfer.run({epochType}); TransferFrom.run({epochType}); }); diff --git a/test/tokens/ERC721/base/interface.test.ts b/test/tokens/ERC721/base/interface.test.ts new file mode 100644 index 00000000..8ff31069 --- /dev/null +++ b/test/tokens/ERC721/base/interface.test.ts @@ -0,0 +1,81 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {constants, ERC721} from "../../../constant.test"; +import {hardhat_reset} from "../../../utils.test"; +import {deployERC721Selector} from "./deployer.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + const tokenId = 1; + + describe("Interface", async function () { + afterEach(async function () { + await hardhat_reset(); + }); + + it("[SUCCESS] expiry type", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + expect(await erc721exp.expiryType()).to.equal(epochType); + }); + + it("[SUCCESS] supportsInterface ERC-721", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + expect(await erc721exp.supportsInterface("0x80ac58cd")).to.equal(true); + }); + + + it("[SUCCESS] supportsInterface ERC-7858", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + expect(await erc721exp.supportsInterface("0x3ebdfa31")).to.equal(true); + }); + + it("[SUCCESS] return startTime existence token", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.startTime(tokenId)).to.equal(0); + }); + + it("[SUCCESS] return endTime existence token", async function () { + const {erc721exp, alice} = await deployERC721Selector({epochType}); + await expect(erc721exp.mint(alice.address, tokenId)) + .to.emit(erc721exp, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc721exp.startTime(tokenId)).to.equal(0); + }); + + it("[FAILED] supportsInterface ERC-7858Epoch", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + expect(await erc721exp.supportsInterface("0xaaf87b24")).to.equal(false); + }); + + it("[FAILED] supportsInterface unknown", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + expect(await erc721exp.supportsInterface("0xffffffff")).to.equal(false); + }); + + it("[FAILED] return startTime nonexistence token", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + await expect(erc721exp.startTime(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + }); + + it("[FAILED] return endTime nonexistence token", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + await expect(erc721exp.endTime(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + }); + + it("[FAILED] update timestamp nonexistence token", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + await expect(erc721exp.updateTimeStamp(tokenId, 0, 0)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + }); + + it("[FAILED] clear timestamp nonexistence token", async function () { + const {erc721exp} = await deployERC721Selector({epochType}); + await expect(erc721exp.clearTimeStamp(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); + }); + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts index e69de29b..e2ac8cb1 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts @@ -0,0 +1,4 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 diff --git a/test/tokens/ERC721/index.test.ts b/test/tokens/ERC721/index.test.ts index e330e566..c8040d3e 100644 --- a/test/tokens/ERC721/index.test.ts +++ b/test/tokens/ERC721/index.test.ts @@ -5,21 +5,18 @@ import {constants} from "../../constant.test"; import * as base from "./base/index.test"; -import * as behavior from "./ERC7858Behavior/index.test"; import * as extensions from "./extensions/index.test"; export const run = async () => { describe("ERC721BLSW", async function () { const epochType = constants.EPOCH_TYPE.BLOCKS_BASED; base.run({epochType}); - behavior.run({epochType}); extensions.run({epochType}); }); describe("ERC721TLSW", async function () { const epochType = constants.EPOCH_TYPE.TIME_BASED; base.run({epochType}); - behavior.run({epochType}); extensions.run({epochType}); }); }; diff --git a/test/tokens/index.test.ts b/test/tokens/index.test.ts index 50030b4a..948251f9 100644 --- a/test/tokens/index.test.ts +++ b/test/tokens/index.test.ts @@ -8,7 +8,7 @@ import * as ERC721 from "./ERC721/index.test"; export const run = async () => { describe("tokens", async function () { - ERC20.run(); + // ERC20.run(); ERC721.run(); }); }; From 6ffd7e49410996bcd8c0739f711f7bb70804a5dc Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 1 Mar 2025 09:18:57 +0700 Subject: [PATCH 38/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20use=20expectBalanc?= =?UTF-8?q?e=20var=20instead=20of=20inscope=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- test/tokens/ERC721/base/interface.test.ts | 1 - test/tokens/ERC721/base/mint.test.ts | 7 ++++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/tokens/ERC721/base/interface.test.ts b/test/tokens/ERC721/base/interface.test.ts index 8ff31069..bdc5164e 100644 --- a/test/tokens/ERC721/base/interface.test.ts +++ b/test/tokens/ERC721/base/interface.test.ts @@ -26,7 +26,6 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc721exp.supportsInterface("0x80ac58cd")).to.equal(true); }); - it("[SUCCESS] supportsInterface ERC-7858", async function () { const {erc721exp} = await deployERC721Selector({epochType}); expect(await erc721exp.supportsInterface("0x3ebdfa31")).to.equal(true); diff --git a/test/tokens/ERC721/base/mint.test.ts b/test/tokens/ERC721/base/mint.test.ts index a82927b3..60a5daee 100644 --- a/test/tokens/ERC721/base/mint.test.ts +++ b/test/tokens/ERC721/base/mint.test.ts @@ -11,6 +11,7 @@ import {ERC721, ERC7858, constants} from "../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Mint", async function () { const tokenId = 1; + const expectBalance = 1; let startTime = 0; let endTime = 0; @@ -26,7 +27,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); - expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.balanceOf(alice.address)).to.equal(expectBalance); expect(await erc721exp.startTime(tokenId)).to.equal(startTime); expect(await erc721exp.endTime(tokenId)).to.equal(endTime); expect(await erc721exp.isTokenExpired(tokenId)).to.equal(false); @@ -38,7 +39,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); - expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.balanceOf(alice.address)).to.equal(expectBalance); if (epochType == constants.EPOCH_TYPE.BLOCKS_BASED) { startTime = await hardhat_latestBlock(); } else { @@ -57,7 +58,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); - expect(await erc721exp.balanceOf(alice.address)).to.equal(1); + expect(await erc721exp.balanceOf(alice.address)).to.equal(expectBalance); startTime = 1000; await expect(erc721exp.updateTimeStamp(tokenId, startTime, endTime)) .to.be.revertedWithCustomError(erc721exp, ERC7858.errors.ERC7858InvalidTimeStamp) From 3db86104c3c35a355c437fc80df734ba6bf65b21 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 1 Mar 2025 10:03:07 +0700 Subject: [PATCH 39/53] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20unused?= =?UTF-8?q?=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mocks/contracts/tokens/ERC1155/.keep | 0 test/index.test.ts | 1 - 2 files changed, 1 deletion(-) delete mode 100644 mocks/contracts/tokens/ERC1155/.keep diff --git a/mocks/contracts/tokens/ERC1155/.keep b/mocks/contracts/tokens/ERC1155/.keep deleted file mode 100644 index e69de29b..00000000 diff --git a/test/index.test.ts b/test/index.test.ts index 3036ff94..7fcb1636 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -9,7 +9,6 @@ import {EventEmitter} from "events"; describe("Scenario", async function () { EventEmitter.setMaxListeners(1000); - // abstracts.run(); tokens.run(); utils.run(); }); From 76a819b7d114e3e51135d85fa9939bac8297df59 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sat, 1 Mar 2025 16:36:45 +0700 Subject: [PATCH 40/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20implementing?= =?UTF-8?q?=20missing=20function=20from=20interface?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC721EpochBLSW.sol | 6 +- .../ERC721/extensions/ERC721EpochBase.sol | 18 +++-- .../ERC721/extensions/ERC721EpochTLSW.sol | 6 +- .../extensions/BLSW/MockERC7858EpochBLSW.sol | 21 ++++++ .../extensions/TLSW/MockERC7858EpochTLSWs.sol | 21 ++++++ .../extensions/ERC7858Epoch/deployer.test.ts | 66 +++++++++++++++++++ 6 files changed, 131 insertions(+), 7 deletions(-) create mode 100644 mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol create mode 100644 mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol index b901bc15..b638c7bd 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol @@ -19,7 +19,11 @@ abstract contract ERC721EpochBLSW is ERC721EpochBase, BLSW { bool development_ ) ERC721EpochBase(name_, symbol_) BLSW(initialBlockNumber_, blocksPerEpoch_, windowSize_, development_) {} - function _epochType() internal pure virtual override returns (EXPIRY_TYPE) { + function expiryType() public pure override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.BLOCKS_BASED; + } + + function epochType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; } diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 59d3d553..8c9378ab 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -367,6 +367,12 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE emit ApprovalForAll(owner, operator, approved); } + /// @dev See {IERC7858-balanceOFAtEpoch}. + function balanceOfAtEpoch(uint256 epoch, address owner) external view returns (uint256) { + if (isEpochExpired(epoch)) return 0; + return _computeBalanceAtEpoch(epoch, owner, _pointerProvider(), _getPointersInWindow()); + } + /// @dev See {IERC7858-startTime}. function startTime(uint256 tokenId) external view returns (uint256) { return _tokenPointers[tokenId]; @@ -380,6 +386,12 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } } + /// @dev See {IERC7858-isTokenExpired}. + function isTokenExpired(uint256 tokenId) external view returns (bool) { + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + return _pointerProvider() >= _tokenPointers[tokenId] + _getPointersInWindow(); + } + /// @dev See {IERC7858Epoch-currentEpoch}. function currentEpoch() public view virtual returns (uint256) { return _getEpoch(_pointerProvider()); @@ -391,9 +403,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } /// @dev See {IERC7858Epoch-epochType}. - function epochType() public pure returns (EXPIRY_TYPE) { - return _epochType(); - } + function epochType() public pure virtual returns (EXPIRY_TYPE) {} /// @dev See {IERC7858Epoch-validityDuration}. function validityDuration() public view virtual returns (uint256) { @@ -415,8 +425,6 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE } } - function _epochType() internal pure virtual returns (EXPIRY_TYPE) {} - function _getEpoch(uint256 pointer) internal view virtual returns (uint256) {} function _getWindowRage(uint256 pointer) internal view virtual returns (uint256 fromEpoch, uint256 toEpoch) {} diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol b/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol index 25efc56a..ce73cbea 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol @@ -19,7 +19,11 @@ abstract contract ERC721EpochTLSW is ERC721EpochBase, TLSW { bool development_ ) ERC721EpochBase(name_, symbol_) TLSW(initialBlockTimestamp_, secondsPerEpoch_, windowSize_, development_) {} - function _epochType() internal pure virtual override returns (EXPIRY_TYPE) { + function expiryType() public pure override returns (EXPIRY_TYPE) { + return EXPIRY_TYPE.TIME_BASED; + } + + function epochType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.TIME_BASED; } diff --git a/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol b/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol new file mode 100644 index 00000000..c38d9063 --- /dev/null +++ b/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9.0; + +import {ERC721EpochBLSW as ERC7858} from "../../../../../../contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol"; + +contract MockERC721BLSW is ERC7858 { + constructor( + string memory name_, + string memory symbol_, + uint40 blocksPerEpoch_, + uint8 windowSize_ + ) ERC7858(name_, symbol_, block.number, blocksPerEpoch_, windowSize_, false) {} + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } +} diff --git a/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol b/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol new file mode 100644 index 00000000..bd49c2b8 --- /dev/null +++ b/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0 <0.9.0; + +import {ERC721EpochTLSW as ERC7858} from "../../../../../../contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol"; + +contract MockERC721TLSW is ERC7858 { + constructor( + string memory name_, + string memory symbol_, + uint40 secondsPerEpoch_, + uint8 windowSize_ + ) ERC7858(name_, symbol_, block.timestamp, secondsPerEpoch_, windowSize_, false) {} + + function mint(address to, uint256 tokenId) public { + _mint(to, tokenId); + } + + function burn(uint256 tokenId) public { + _burn(tokenId); + } +} diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts new file mode 100644 index 00000000..c852c4da --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts @@ -0,0 +1,66 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {ethers} from "hardhat"; +import {constants, deployERC7858EpochBLSW, ERC7818BlacklistTLSW, ERC721} from "../../../../constant.test"; +import {MockERC7858EpochBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC721/extensions/BLSW"; +import {MockERC7858EpochTLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC721/extensions/TLSW"; + +export const deployERC7858EpochSelector = async function ({ + blocksPerEpoch = constants.DEFAULT_BLOCKS_PER_EPOCH, // 300 blocks + secondsPerEpoch = constants.DEFAULT_SECONDS_PER_EPOCH, // 3_600 seconds. + windowSize = constants.DEFAULT_WINDOW_SIZE, // fixed width window size 2 epoch. + epochType = constants.EPOCH_TYPE.BLOCKS_BASED, +} = {}) { + if (epochType === constants.EPOCH_TYPE.BLOCKS_BASED) { + return await deployERC7858EpochBLSW({blocksPerEpoch, windowSize}); + } + + return await deployERC7858EpochTLSW({secondsPerEpoch, windowSize}); +}; + +export const deployERC7858EpochTLSW = async function ({ + secondsPerEpoch = constants.DEFAULT_SECONDS_PER_EPOCH, // 3_600 seconds. + windowSize = constants.DEFAULT_WINDOW_SIZE, // fixed width window size 2 epoch. +} = {}) { + const [deployer, alice, bob, charlie] = await ethers.getSigners(); + const ERC7858_EPOCH = await ethers.getContractFactory(MockERC7858EpochTLSW.name, deployer); + const erc7858Epoch = (await ERC7858_EPOCH.deploy( + ERC721.constructor.name, + ERC721.constructor.symbol, + secondsPerEpoch, + windowSize, + )) as any as MockERC7858EpochTLSW; + await erc7858Epoch.waitForDeployment(); + return { + erc7858Epoch, + deployer, + alice, + bob, + charlie, + }; +}; + +export const deployERC7858EpochBLSW = async function ({ + blocksPerEpoch = constants.DEFAULT_BLOCKS_PER_EPOCH, // 300 blocks + windowSize = constants.DEFAULT_WINDOW_SIZE, // fixed width window size 2 epoch. +} = {}) { + const [deployer, alice, bob, charlie] = await ethers.getSigners(); + const ERC7858_EPOCH = await ethers.getContractFactory(MockERC7858EpochBLSW.name, deployer); + const erc7858Epoch = (await ERC7858_EPOCH.deploy( + ERC721.constructor.name, + ERC721.constructor.symbol, + blocksPerEpoch, + windowSize, + )) as any as MockERC7858EpochBLSW; + await erc7858Epoch.waitForDeployment(); + return { + erc7858Epoch, + deployer, + alice, + bob, + charlie, + }; +}; From 96bca9f8c11c28bd9b27cbd9c68c75c751cc7d85 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Sun, 2 Mar 2025 08:44:13 +0700 Subject: [PATCH 41/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20add=20transfer?= =?UTF-8?q?=20logic=20to=20ERC7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC721EpochBLSW.sol | 2 +- .../ERC721/extensions/ERC721EpochBase.sol | 33 +++++++++++++++++++ .../ERC721/extensions/ERC721EpochTLSW.sol | 2 +- 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol index b638c7bd..f2388dd0 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol @@ -22,7 +22,7 @@ abstract contract ERC721EpochBLSW is ERC721EpochBase, BLSW { function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; } - + function epochType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; } diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol index 8c9378ab..723c64ad 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol @@ -112,6 +112,39 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE super.supportsInterface(interfaceId); } + /** + * @dev See {IERC721-transferFrom}. + */ + function transferFrom(address from, address to, uint256 tokenId) public virtual { + if (to == address(0)) { + revert ERC721InvalidReceiver(address(0)); + } + // Setting an "auth" arguments enables the `_isAuthorized` check which verifies that the token exists + // (from != 0). Therefore, it is not needed to verify that the return value is not 0 here. + address previousOwner = _update(to, tokenId, _msgSender()); + if (previousOwner != from) { + revert ERC721IncorrectOwner(from, tokenId, previousOwner); + } + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId) public { + safeTransferFrom(from, to, tokenId, ""); + } + + /** + * @dev See {IERC721-safeTransferFrom}. + */ + function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual { + transferFrom(from, to, tokenId); + ERC721Utils.checkOnERC721Received(_msgSender(), from, to, tokenId, data); + } + + /** + * @dev See {IERC721-balanceOf}. + */ function balanceOf(address owner) public view virtual returns (uint256) { if (owner == address(0)) { revert ERC721InvalidOwner(address(0)); diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol b/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol index ce73cbea..6e14001d 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol @@ -22,7 +22,7 @@ abstract contract ERC721EpochTLSW is ERC721EpochBase, TLSW { function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.TIME_BASED; } - + function epochType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.TIME_BASED; } From 6516a06177f4aab824e26a74c393d148886aa1da Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 3 Mar 2025 10:21:46 +0700 Subject: [PATCH 42/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20change=20files?= =?UTF-8?q?=20name=20and=20init=20test=20for=20erc7858=20epoch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{ERC721BLSW.sol => ERC7858BLSW.sol} | 6 +-- .../{ERC721EXPBase.sol => ERC7858EXPBase.sol} | 2 +- .../{ERC721TLSW.sol => ERC7858TLSW.sol} | 6 +-- ...C721EpochBLSW.sol => ERC7858EpochBLSW.sol} | 6 +-- ...C721EpochBase.sol => ERC7858EpochBase.sol} | 4 +- ...C721EpochTLSW.sol => ERC7858EpochTLSW.sol} | 6 +-- ...MockERC721BLSW.sol => MockERC7858BLSW.sol} | 6 +-- ...MockERC721TLSW.sol => MockERC7858TLSW.sol} | 6 +-- .../extensions/BLSW/MockERC7858EpochBLSW.sol | 4 +- ...pochTLSWs.sol => MockERC7858EpochTLSW.sol} | 4 +- test/constant.test.ts | 20 +++++++-- .../extensions/ERC7818Permit/index.test.ts | 2 +- test/tokens/ERC721/base/approve.test.ts | 2 +- test/tokens/ERC721/base/burn.test.ts | 9 ++-- test/tokens/ERC721/base/deployer.test.ts | 22 ++++----- test/tokens/ERC721/base/interface.test.ts | 24 +++++----- test/tokens/ERC721/base/mint.test.ts | 8 ++-- test/tokens/ERC721/base/transfer.test.ts | 2 +- test/tokens/ERC721/base/transferFrom.test.ts | 2 +- .../extensions/ERC7858Epoch/approve.test.ts | 27 +++++++++++ .../extensions/ERC7858Epoch/burn.test.ts | 27 +++++++++++ .../extensions/ERC7858Epoch/deployer.test.ts | 6 +-- .../extensions/ERC7858Epoch/index.test.ts | 17 +++++++ .../extensions/ERC7858Epoch/mint.test.ts | 45 +++++++++++++++++++ .../extensions/ERC7858Epoch/transfer.test.ts | 27 +++++++++++ .../ERC7858Epoch/transferFrom.test.ts | 27 +++++++++++ test/tokens/ERC721/extensions/index.test.ts | 4 +- test/tokens/ERC721/index.test.ts | 4 +- 28 files changed, 253 insertions(+), 72 deletions(-) rename contracts/tokens/ERC721/{ERC721BLSW.sol => ERC7858BLSW.sol} (79%) rename contracts/tokens/ERC721/{ERC721EXPBase.sol => ERC7858EXPBase.sol} (97%) rename contracts/tokens/ERC721/{ERC721TLSW.sol => ERC7858TLSW.sol} (80%) rename contracts/tokens/ERC721/extensions/{ERC721EpochBLSW.sol => ERC7858EpochBLSW.sol} (86%) rename contracts/tokens/ERC721/extensions/{ERC721EpochBase.sol => ERC7858EpochBase.sol} (99%) rename contracts/tokens/ERC721/extensions/{ERC721EpochTLSW.sol => ERC7858EpochTLSW.sol} (86%) rename mocks/contracts/tokens/ERC721/{MockERC721BLSW.sol => MockERC7858BLSW.sol} (79%) rename mocks/contracts/tokens/ERC721/{MockERC721TLSW.sol => MockERC7858TLSW.sol} (79%) rename mocks/contracts/tokens/ERC721/extensions/TLSW/{MockERC7858EpochTLSWs.sol => MockERC7858EpochTLSW.sol} (74%) create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts diff --git a/contracts/tokens/ERC721/ERC721BLSW.sol b/contracts/tokens/ERC721/ERC7858BLSW.sol similarity index 79% rename from contracts/tokens/ERC721/ERC721BLSW.sol rename to contracts/tokens/ERC721/ERC7858BLSW.sol index 323c6c6f..0f7bf208 100644 --- a/contracts/tokens/ERC721/ERC721BLSW.sol +++ b/contracts/tokens/ERC721/ERC7858BLSW.sol @@ -7,10 +7,10 @@ pragma solidity >=0.8.0 <0.9.0; * @author Kiwari Labs */ -import {ERC721EXPBase} from "./ERC721EXPBase.sol"; +import {ERC7858EXPBase} from "./ERC7858EXPBase.sol"; -abstract contract ERC721BLSW is ERC721EXPBase { - constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} +abstract contract ERC7858BLSW is ERC7858EXPBase { + constructor(string memory name_, string memory symbol_) ERC7858EXPBase(name_, symbol_) {} function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; diff --git a/contracts/tokens/ERC721/ERC721EXPBase.sol b/contracts/tokens/ERC721/ERC7858EXPBase.sol similarity index 97% rename from contracts/tokens/ERC721/ERC721EXPBase.sol rename to contracts/tokens/ERC721/ERC7858EXPBase.sol index 635cb991..067b9ca2 100644 --- a/contracts/tokens/ERC721/ERC721EXPBase.sol +++ b/contracts/tokens/ERC721/ERC7858EXPBase.sol @@ -12,7 +12,7 @@ import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {ERC721Enumerable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; -abstract contract ERC721EXPBase is ERC721, ERC721Enumerable, IERC7858 { +abstract contract ERC7858EXPBase is ERC721, ERC721Enumerable, IERC7858 { struct AssetTimeStamp { uint256 start; uint256 end; diff --git a/contracts/tokens/ERC721/ERC721TLSW.sol b/contracts/tokens/ERC721/ERC7858TLSW.sol similarity index 80% rename from contracts/tokens/ERC721/ERC721TLSW.sol rename to contracts/tokens/ERC721/ERC7858TLSW.sol index 1c25d415..6c8e9616 100644 --- a/contracts/tokens/ERC721/ERC721TLSW.sol +++ b/contracts/tokens/ERC721/ERC7858TLSW.sol @@ -7,10 +7,10 @@ pragma solidity >=0.8.0 <0.9.0; * @author Kiwari Labs */ -import {ERC721EXPBase} from "./ERC721EXPBase.sol"; +import {ERC7858EXPBase} from "./ERC7858EXPBase.sol"; -abstract contract ERC721TLSW is ERC721EXPBase { - constructor(string memory name_, string memory symbol_) ERC721EXPBase(name_, symbol_) {} +abstract contract ERC7858TLSW is ERC7858EXPBase { + constructor(string memory name_, string memory symbol_) ERC7858EXPBase(name_, symbol_) {} function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.TIME_BASED; diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBLSW.sol similarity index 86% rename from contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol rename to contracts/tokens/ERC721/extensions/ERC7858EpochBLSW.sol index f2388dd0..114ede94 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBLSW.sol @@ -7,9 +7,9 @@ pragma solidity >=0.8.0 <0.9.0; */ import {AbstractBLSW as BLSW} from "../../../abstracts/AbstractBLSW.sol"; -import {ERC721EpochBase} from "./ERC721EpochBase.sol"; +import {ERC7858EpochBase} from "./ERC7858EpochBase.sol"; -abstract contract ERC721EpochBLSW is ERC721EpochBase, BLSW { +abstract contract ERC7858EpochBLSW is ERC7858EpochBase, BLSW { constructor( string memory name_, string memory symbol_, @@ -17,7 +17,7 @@ abstract contract ERC721EpochBLSW is ERC721EpochBase, BLSW { uint40 blocksPerEpoch_, uint8 windowSize_, bool development_ - ) ERC721EpochBase(name_, symbol_) BLSW(initialBlockNumber_, blocksPerEpoch_, windowSize_, development_) {} + ) ERC7858EpochBase(name_, symbol_) BLSW(initialBlockNumber_, blocksPerEpoch_, windowSize_, development_) {} function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.BLOCKS_BASED; diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol similarity index 99% rename from contracts/tokens/ERC721/extensions/ERC721EpochBase.sol rename to contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index 723c64ad..e113d471 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -18,7 +18,7 @@ import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.s // @TODO following https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/ERC721.sol -abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IERC721Metadata, IERC7858Epoch { +abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, IERC721Metadata, IERC7858Epoch { using SortedList for SortedList.List; using Strings for uint256; @@ -305,7 +305,7 @@ abstract contract ERC721EpochBase is Context, ERC165, IERC721, IERC721Errors, IE if (to != address(0)) { unchecked { - _balances[from] += 1; + _balances[to] += 1; _recipient.totalBalance += 1; _recipient.tokens[tokenPointer].push(tokenId); _recipient.tokenIndex[tokenPointer][tokenId] = _recipient.tokens[tokenPointer].length - 1; diff --git a/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochTLSW.sol similarity index 86% rename from contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol rename to contracts/tokens/ERC721/extensions/ERC7858EpochTLSW.sol index 6e14001d..fe0be7a3 100644 --- a/contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochTLSW.sol @@ -7,9 +7,9 @@ pragma solidity >=0.8.0 <0.9.0; */ import {AbstractTLSW as TLSW} from "../../../abstracts/AbstractTLSW.sol"; -import {ERC721EpochBase} from "./ERC721EpochBase.sol"; +import {ERC7858EpochBase} from "./ERC7858EpochBase.sol"; -abstract contract ERC721EpochTLSW is ERC721EpochBase, TLSW { +abstract contract ERC7858EpochTLSW is ERC7858EpochBase, TLSW { constructor( string memory name_, string memory symbol_, @@ -17,7 +17,7 @@ abstract contract ERC721EpochTLSW is ERC721EpochBase, TLSW { uint40 secondsPerEpoch_, uint8 windowSize_, bool development_ - ) ERC721EpochBase(name_, symbol_) TLSW(initialBlockTimestamp_, secondsPerEpoch_, windowSize_, development_) {} + ) ERC7858EpochBase(name_, symbol_) TLSW(initialBlockTimestamp_, secondsPerEpoch_, windowSize_, development_) {} function expiryType() public pure override returns (EXPIRY_TYPE) { return EXPIRY_TYPE.TIME_BASED; diff --git a/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol b/mocks/contracts/tokens/ERC721/MockERC7858BLSW.sol similarity index 79% rename from mocks/contracts/tokens/ERC721/MockERC721BLSW.sol rename to mocks/contracts/tokens/ERC721/MockERC7858BLSW.sol index cc930146..1c7cd564 100644 --- a/mocks/contracts/tokens/ERC721/MockERC721BLSW.sol +++ b/mocks/contracts/tokens/ERC721/MockERC7858BLSW.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0 <0.9.0; -import {ERC721BLSW} from "../../../../contracts/tokens/ERC721/ERC721BLSW.sol"; +import {ERC7858BLSW} from "../../../../contracts/tokens/ERC721/ERC7858BLSW.sol"; -contract MockERC721BLSW is ERC721BLSW { - constructor(string memory _name, string memory _symbol) ERC721BLSW(_name, _symbol) {} +contract MockERC7858BLSW is ERC7858BLSW { + constructor(string memory _name, string memory _symbol) ERC7858BLSW(_name, _symbol) {} function mint(address to, uint256 tokenId) public { _mint(to, tokenId); diff --git a/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol b/mocks/contracts/tokens/ERC721/MockERC7858TLSW.sol similarity index 79% rename from mocks/contracts/tokens/ERC721/MockERC721TLSW.sol rename to mocks/contracts/tokens/ERC721/MockERC7858TLSW.sol index 143a6221..8cca9ea8 100644 --- a/mocks/contracts/tokens/ERC721/MockERC721TLSW.sol +++ b/mocks/contracts/tokens/ERC721/MockERC7858TLSW.sol @@ -1,10 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0 <0.9.0; -import {ERC721TLSW} from "../../../../contracts/tokens/ERC721/ERC721TLSW.sol"; +import {ERC7858TLSW} from "../../../../contracts/tokens/ERC721/ERC7858TLSW.sol"; -contract MockERC721TLSW is ERC721TLSW { - constructor(string memory _name, string memory _symbol) ERC721TLSW(_name, _symbol) {} +contract MockERC7858TLSW is ERC7858TLSW { + constructor(string memory _name, string memory _symbol) ERC7858TLSW(_name, _symbol) {} function mint(address to, uint256 tokenId) public { _mint(to, tokenId); diff --git a/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol b/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol index c38d9063..07e733f1 100644 --- a/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol +++ b/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0 <0.9.0; -import {ERC721EpochBLSW as ERC7858} from "../../../../../../contracts/tokens/ERC721/extensions/ERC721EpochBLSW.sol"; +import {ERC7858EpochBLSW as ERC7858} from "../../../../../../contracts/tokens/ERC721/extensions/ERC7858EpochBLSW.sol"; -contract MockERC721BLSW is ERC7858 { +contract MockERC7858EpochBLSW is ERC7858 { constructor( string memory name_, string memory symbol_, diff --git a/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol b/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol similarity index 74% rename from mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol rename to mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol index bd49c2b8..905e9b3e 100644 --- a/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSWs.sol +++ b/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0 <0.9.0; -import {ERC721EpochTLSW as ERC7858} from "../../../../../../contracts/tokens/ERC721/extensions/ERC721EpochTLSW.sol"; +import {ERC7858EpochTLSW as ERC7858} from "../../../../../../contracts/tokens/ERC721/extensions/ERC7858EpochTLSW.sol"; -contract MockERC721TLSW is ERC7858 { +contract MockERC7858EpochTLSW is ERC7858 { constructor( string memory name_, string memory symbol_, diff --git a/test/constant.test.ts b/test/constant.test.ts index c0d7a38d..2bfc757d 100644 --- a/test/constant.test.ts +++ b/test/constant.test.ts @@ -239,14 +239,26 @@ export const ERC20TLSW = { events: {}, }; -export const ERC721TLSW = { - name: "MockERC721TLSW", +export const ERC7858TLSW = { + name: "MockERC7858TLSW", errors: {}, events: {}, }; -export const ERC721BLSW = { - name: "MockERC721BLSW", +export const ERC7858BLSW = { + name: "MockERC7858BLSW", + errors: {}, + events: {}, +}; + +export const ERC7858EpochBLSW = { + name: "MockERC7858EpochBLSW", + errors: {}, + events: {}, +}; + +export const ERC7858EpochTLSW = { + name: "MockERC7858EpochTLSW", errors: {}, events: {}, }; diff --git a/test/tokens/ERC20/extensions/ERC7818Permit/index.test.ts b/test/tokens/ERC20/extensions/ERC7818Permit/index.test.ts index 52acc66c..26c28d7c 100644 --- a/test/tokens/ERC20/extensions/ERC7818Permit/index.test.ts +++ b/test/tokens/ERC20/extensions/ERC7818Permit/index.test.ts @@ -2,7 +2,7 @@ import {constants} from "../../../../constant.test"; import * as Transfer from "./transfer.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("ERC781Permit", async function () { + describe("ERC7818Permit", async function () { Transfer.run({epochType}); }); }; diff --git a/test/tokens/ERC721/base/approve.test.ts b/test/tokens/ERC721/base/approve.test.ts index c5d524da..3d737b6c 100644 --- a/test/tokens/ERC721/base/approve.test.ts +++ b/test/tokens/ERC721/base/approve.test.ts @@ -5,7 +5,7 @@ import {expect} from "chai"; import {ERC721, constants} from "../../../constant.test"; -import {deployERC721Selector} from "./deployer.test"; +import {deployERC7858Selector} from "./deployer.test"; import {hardhat_impersonate, hardhat_reset, hardhat_setBalance, hardhat_stopImpersonating, ethers} from "../../../utils.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { diff --git a/test/tokens/ERC721/base/burn.test.ts b/test/tokens/ERC721/base/burn.test.ts index a4fc5e41..b0969db9 100644 --- a/test/tokens/ERC721/base/burn.test.ts +++ b/test/tokens/ERC721/base/burn.test.ts @@ -5,7 +5,7 @@ import {expect} from "chai"; import {hardhat_latest, hardhat_latestBlock, hardhat_reset} from "../../../utils.test"; -import {deployERC721Selector} from "./deployer.test"; +import {deployERC7858Selector} from "./deployer.test"; import {constants, ERC721} from "../../../constant.test"; import {ZeroAddress} from "ethers"; @@ -22,9 +22,8 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { endTime = 0; }); - // @TODO it("[SUCCESS] burn expirable token", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); @@ -36,7 +35,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[SUCCESS] burn unexpirable token", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); @@ -48,7 +47,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[SUCCESS] burn and re-mint token that has timestamp", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); diff --git a/test/tokens/ERC721/base/deployer.test.ts b/test/tokens/ERC721/base/deployer.test.ts index 9597f928..2888b2eb 100644 --- a/test/tokens/ERC721/base/deployer.test.ts +++ b/test/tokens/ERC721/base/deployer.test.ts @@ -4,20 +4,20 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {ethers} from "hardhat"; -import {constants, ERC721, ERC721BLSW, ERC721TLSW} from "../../../constant.test"; -import {MockERC721BLSW, MockERC721TLSW} from "../../../../typechain-types/mocks/contracts/tokens/ERC721"; +import {constants, ERC721, ERC7858BLSW, ERC7858TLSW} from "../../../constant.test"; +import {MockERC7858BLSW, MockERC7858TLSW} from "../../../../typechain-types/mocks/contracts/tokens/ERC721"; -export const deployERC721Selector = async function ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED} = {}) { +export const deployERC7858Selector = async function ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED} = {}) { if (epochType === constants.EPOCH_TYPE.BLOCKS_BASED) { - return await deployERC721BLSW({}); + return await deployERC7858BLSW({}); } - return await deployERC721TLSW({}); + return await deployERC7858TLSW({}); }; -export const deployERC721TLSW = async function ({} = {}) { +export const deployERC7858TLSW = async function ({} = {}) { const [deployer, alice, bob, charlie, dave] = await ethers.getSigners(); - const ERC721TLSWContract = await ethers.getContractFactory(ERC721TLSW.name, deployer); - const erc721exp = (await ERC721TLSWContract.deploy(ERC721.constructor.name, ERC721.constructor.symbol)) as any as MockERC721TLSW; + const ERC721TLSWContract = await ethers.getContractFactory(ERC7858TLSW.name, deployer); + const erc721exp = (await ERC721TLSWContract.deploy(ERC721.constructor.name, ERC721.constructor.symbol)) as any as MockERC7858TLSW; await erc721exp.waitForDeployment(); return { erc721exp, @@ -29,10 +29,10 @@ export const deployERC721TLSW = async function ({} = {}) { }; }; -export const deployERC721BLSW = async function ({} = {}) { +export const deployERC7858BLSW = async function ({} = {}) { const [deployer, alice, bob, charlie, dave] = await ethers.getSigners(); - const ERC20BLSWContract = await ethers.getContractFactory(ERC721BLSW.name, deployer); - const erc721exp = (await ERC20BLSWContract.deploy(ERC721.constructor.name, ERC721.constructor.symbol)) as any as MockERC721BLSW; + const ERC20BLSWContract = await ethers.getContractFactory(ERC7858BLSW.name, deployer); + const erc721exp = (await ERC20BLSWContract.deploy(ERC721.constructor.name, ERC721.constructor.symbol)) as any as MockERC7858BLSW; await erc721exp.waitForDeployment(); return { erc721exp, diff --git a/test/tokens/ERC721/base/interface.test.ts b/test/tokens/ERC721/base/interface.test.ts index bdc5164e..34dc6ea7 100644 --- a/test/tokens/ERC721/base/interface.test.ts +++ b/test/tokens/ERC721/base/interface.test.ts @@ -6,7 +6,7 @@ import {expect} from "chai"; import {constants, ERC721} from "../../../constant.test"; import {hardhat_reset} from "../../../utils.test"; -import {deployERC721Selector} from "./deployer.test"; +import {deployERC7858Selector} from "./deployer.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { const tokenId = 1; @@ -17,22 +17,22 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[SUCCESS] expiry type", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); expect(await erc721exp.expiryType()).to.equal(epochType); }); it("[SUCCESS] supportsInterface ERC-721", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); expect(await erc721exp.supportsInterface("0x80ac58cd")).to.equal(true); }); it("[SUCCESS] supportsInterface ERC-7858", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); expect(await erc721exp.supportsInterface("0x3ebdfa31")).to.equal(true); }); it("[SUCCESS] return startTime existence token", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); @@ -40,7 +40,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[SUCCESS] return endTime existence token", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); @@ -48,32 +48,32 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[FAILED] supportsInterface ERC-7858Epoch", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); expect(await erc721exp.supportsInterface("0xaaf87b24")).to.equal(false); }); it("[FAILED] supportsInterface unknown", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); expect(await erc721exp.supportsInterface("0xffffffff")).to.equal(false); }); it("[FAILED] return startTime nonexistence token", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); await expect(erc721exp.startTime(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); }); it("[FAILED] return endTime nonexistence token", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); await expect(erc721exp.endTime(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); }); it("[FAILED] update timestamp nonexistence token", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); await expect(erc721exp.updateTimeStamp(tokenId, 0, 0)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); }); it("[FAILED] clear timestamp nonexistence token", async function () { - const {erc721exp} = await deployERC721Selector({epochType}); + const {erc721exp} = await deployERC7858Selector({epochType}); await expect(erc721exp.clearTimeStamp(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); }); }); diff --git a/test/tokens/ERC721/base/mint.test.ts b/test/tokens/ERC721/base/mint.test.ts index 60a5daee..9a98ca51 100644 --- a/test/tokens/ERC721/base/mint.test.ts +++ b/test/tokens/ERC721/base/mint.test.ts @@ -5,7 +5,7 @@ import {expect} from "chai"; import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../utils.test"; -import {deployERC721Selector} from "./deployer.test"; +import {deployERC7858Selector} from "./deployer.test"; import {ERC721, ERC7858, constants} from "../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { @@ -23,7 +23,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[SUCCESS] mint unexpirable token", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); @@ -34,7 +34,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[SUCCESS] mint expirable token", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); @@ -53,7 +53,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { }); it("[FAILED] mint expirable token with invalid timestamp", async function () { - const {erc721exp, alice} = await deployERC721Selector({epochType}); + const {erc721exp, alice} = await deployERC7858Selector({epochType}); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); diff --git a/test/tokens/ERC721/base/transfer.test.ts b/test/tokens/ERC721/base/transfer.test.ts index 1d8720f7..c8c6b7f0 100644 --- a/test/tokens/ERC721/base/transfer.test.ts +++ b/test/tokens/ERC721/base/transfer.test.ts @@ -4,7 +4,7 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {expect} from "chai"; -import {deployERC721Selector} from "./deployer.test"; +import {deployERC7858Selector} from "./deployer.test"; import {ERC721, constants} from "../../../constant.test"; import {ethers, hardhat_impersonate, hardhat_reset, hardhat_setBalance, hardhat_stopImpersonating} from "../../../utils.test"; diff --git a/test/tokens/ERC721/base/transferFrom.test.ts b/test/tokens/ERC721/base/transferFrom.test.ts index 43c6f53f..dd2e7f11 100644 --- a/test/tokens/ERC721/base/transferFrom.test.ts +++ b/test/tokens/ERC721/base/transferFrom.test.ts @@ -4,7 +4,7 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {expect} from "chai"; -import {deployERC721Selector} from "./deployer.test"; +import {deployERC7858Selector} from "./deployer.test"; import {ERC721, constants} from "../../../constant.test"; import {hardhat_reset} from "../../../utils.test"; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts new file mode 100644 index 00000000..4c8843a1 --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts @@ -0,0 +1,27 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; +import {ERC721, ERC7858, constants} from "../../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Approve", async function () { + const tokenId = 1; + const expectBalance = 1; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts new file mode 100644 index 00000000..fc4e23d8 --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts @@ -0,0 +1,27 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; +import {ERC721, ERC7858, constants} from "../../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Burn", async function () { + const tokenId = 1; + const expectBalance = 1; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts index c852c4da..5494234f 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/deployer.test.ts @@ -4,7 +4,7 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {ethers} from "hardhat"; -import {constants, deployERC7858EpochBLSW, ERC7818BlacklistTLSW, ERC721} from "../../../../constant.test"; +import {constants, ERC721, ERC7858EpochBLSW, ERC7858EpochTLSW} from "../../../../constant.test"; import {MockERC7858EpochBLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC721/extensions/BLSW"; import {MockERC7858EpochTLSW} from "../../../../../typechain-types/mocks/contracts/tokens/ERC721/extensions/TLSW"; @@ -26,7 +26,7 @@ export const deployERC7858EpochTLSW = async function ({ windowSize = constants.DEFAULT_WINDOW_SIZE, // fixed width window size 2 epoch. } = {}) { const [deployer, alice, bob, charlie] = await ethers.getSigners(); - const ERC7858_EPOCH = await ethers.getContractFactory(MockERC7858EpochTLSW.name, deployer); + const ERC7858_EPOCH = await ethers.getContractFactory(ERC7858EpochTLSW.name, deployer); const erc7858Epoch = (await ERC7858_EPOCH.deploy( ERC721.constructor.name, ERC721.constructor.symbol, @@ -48,7 +48,7 @@ export const deployERC7858EpochBLSW = async function ({ windowSize = constants.DEFAULT_WINDOW_SIZE, // fixed width window size 2 epoch. } = {}) { const [deployer, alice, bob, charlie] = await ethers.getSigners(); - const ERC7858_EPOCH = await ethers.getContractFactory(MockERC7858EpochBLSW.name, deployer); + const ERC7858_EPOCH = await ethers.getContractFactory(ERC7858EpochBLSW.name, deployer); const erc7858Epoch = (await ERC7858_EPOCH.deploy( ERC721.constructor.name, ERC721.constructor.symbol, diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts index e2ac8cb1..4c992d91 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts @@ -2,3 +2,20 @@ // Node module: kiwari-labs-contracts // This file is licensed under the Apache License 2.0. // License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {constants} from "../../../../constant.test"; +import * as Mint from "./mint.test"; +import * as Burn from "./burn.test"; +import * as Approve from "./burn.test"; +import * as Transfer from "./transfer.test"; +import * as TransferFrom from "./transferFrom.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("ERC7858Epoch", async function () { + Mint.run({epochType}); + Burn.run({epochType}); + Approve.run({epochType}); + Transfer.run({epochType}); + TransferFrom.run({epochType}); + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts new file mode 100644 index 00000000..cac4a275 --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts @@ -0,0 +1,45 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; +import {ERC721, ERC7858, constants} from "../../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Mint", async function () { + const tokenId = 1; + const expectBalance = 1; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + it("[SUCCESS] mint expirable token", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalance); + const validityDuration = Number(await erc7858Epoch.validityDuration()); + const epochLength = Number(await erc7858Epoch.epochLength()); + if (epochType == constants.EPOCH_TYPE.BLOCKS_BASED) { + startTime = await hardhat_latestBlock(); + } else { + startTime = await hardhat_latest(); + } + endTime = startTime + validityDuration * epochLength; + expect(await erc7858Epoch.startTime(tokenId)).to.equal(startTime); + expect(await erc7858Epoch.endTime(tokenId)).to.equal(endTime); + expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); + }); + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts new file mode 100644 index 00000000..a62d4ce0 --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts @@ -0,0 +1,27 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; +import {ERC721, ERC7858, constants} from "../../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Transfer", async function () { + const tokenId = 1; + const expectBalance = 1; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts new file mode 100644 index 00000000..22df441d --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts @@ -0,0 +1,27 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; +import {ERC721, ERC7858, constants} from "../../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("TransferFrom", async function () { + const tokenId = 1; + const expectBalance = 1; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + // @TODO + }); +}; diff --git a/test/tokens/ERC721/extensions/index.test.ts b/test/tokens/ERC721/extensions/index.test.ts index c1c8f4fd..78b1ce30 100644 --- a/test/tokens/ERC721/extensions/index.test.ts +++ b/test/tokens/ERC721/extensions/index.test.ts @@ -4,10 +4,10 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {constants} from "../../../constant.test"; -// import * as ERC7858Epoch from "./ERC7858Epoch/index.test"; +import * as ERC7858Epoch from "./ERC7858Epoch/index.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("extensions", async function () { - // ERC7858Epoch.run({epochType}); + ERC7858Epoch.run({epochType}); }); }; diff --git a/test/tokens/ERC721/index.test.ts b/test/tokens/ERC721/index.test.ts index c8040d3e..758de2c7 100644 --- a/test/tokens/ERC721/index.test.ts +++ b/test/tokens/ERC721/index.test.ts @@ -8,13 +8,13 @@ import * as base from "./base/index.test"; import * as extensions from "./extensions/index.test"; export const run = async () => { - describe("ERC721BLSW", async function () { + describe("ERC7858BLSW", async function () { const epochType = constants.EPOCH_TYPE.BLOCKS_BASED; base.run({epochType}); extensions.run({epochType}); }); - describe("ERC721TLSW", async function () { + describe("ERC7858TLSW", async function () { const epochType = constants.EPOCH_TYPE.TIME_BASED; base.run({epochType}); extensions.run({epochType}); From b1cb4aa7c8bd80fede8dd4fc834e85e06beeb794 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 3 Mar 2025 12:56:56 +0700 Subject: [PATCH 43/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20adding=20test=20ca?= =?UTF-8?q?se=20to=20erc7858=20and=20fix=20nonexistence=20token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC7858EpochBase.sol | 26 +++---- test/tokens/ERC721/base/burn.test.ts | 7 +- .../extensions/ERC7858Epoch/Interface.test.ts | 68 +++++++++++++++++++ .../extensions/ERC7858Epoch/burn.test.ts | 41 ++++++++++- .../extensions/ERC7858Epoch/index.test.ts | 2 + .../extensions/ERC7858Epoch/mint.test.ts | 20 ++++-- 6 files changed, 137 insertions(+), 27 deletions(-) create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index e113d471..f991e8c5 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -7,6 +7,7 @@ pragma solidity >=0.8.0 <0.9.0; */ import {SortedList} from "../../../utils/datastructures/SortedList.sol"; +import {IERC7858} from "../interfaces/IERC7858.sol"; import {IERC7858Epoch} from "../interfaces/IERC7858Epoch.sol"; import {Context} from "@openzeppelin/contracts/utils/Context.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; @@ -74,19 +75,19 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I uint256 pointer, uint256 duration ) private view returns (uint256 balance) { - (uint256 element, uint256 value) = _findValidBalance(account, epoch, pointer, duration); + (uint256 element, uint256 value) = _findUnexpiredBalance(account, epoch, pointer, duration); Epoch storage _account = _epochBalances[epoch][account]; unchecked { balance = value; while (element > 0) { - balance += _account.tokens[element].length; element = _account.list.next(element); + balance += _account.tokens[element].length; } } return balance; } - function _findValidBalance( + function _findUnexpiredBalance( address account, uint256 epoch, uint256 pointer, @@ -108,6 +109,7 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || + interfaceId == type(IERC7858).interfaceId || interfaceId == type(IERC7858Epoch).interfaceId || super.supportsInterface(interfaceId); } @@ -157,15 +159,8 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I revert ERC721InvalidOwner(address(0)); } uint256 pointer = _pointerProvider(); - (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); - uint256 balance = _computeBalanceAtEpoch(fromEpoch, owner, pointer, _getPointersInWindow()); - if (fromEpoch == toEpoch) { - return balance; - } else { - fromEpoch += 1; - } - balance += _computeBalanceOverEpochRange(fromEpoch, toEpoch, owner); - return balance; + (uint256 fromEpoch, ) = _getWindowRage(pointer); + return _computeBalanceAtEpoch(fromEpoch, owner, pointer, _getPointersInWindow()); } /** @@ -408,15 +403,14 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I /// @dev See {IERC7858-startTime}. function startTime(uint256 tokenId) external view returns (uint256) { + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); return _tokenPointers[tokenId]; } /// @dev See {IERC7858-endTime}. function endTime(uint256 tokenId) external view returns (uint256) { - uint256 startTimeCache = _tokenPointers[tokenId]; - if (startTimeCache != 0) { - return startTimeCache + _getPointersInWindow(); - } + if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + return _tokenPointers[tokenId] + _getPointersInWindow(); } /// @dev See {IERC7858-isTokenExpired}. diff --git a/test/tokens/ERC721/base/burn.test.ts b/test/tokens/ERC721/base/burn.test.ts index b0969db9..98be0377 100644 --- a/test/tokens/ERC721/base/burn.test.ts +++ b/test/tokens/ERC721/base/burn.test.ts @@ -7,7 +7,6 @@ import {expect} from "chai"; import {hardhat_latest, hardhat_latestBlock, hardhat_reset} from "../../../utils.test"; import {deployERC7858Selector} from "./deployer.test"; import {constants, ERC721} from "../../../constant.test"; -import {ZeroAddress} from "ethers"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Burn", async function () { @@ -30,7 +29,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc721exp.balanceOf(alice.address)).to.equal(1); expect(await erc721exp.startTime(tokenId)).to.equal(startTime); expect(await erc721exp.endTime(tokenId)).to.equal(endTime); - await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, ZeroAddress, tokenId); + await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, constants.ZERO_ADDRESS, tokenId); await expect(erc721exp.isTokenExpired(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); }); @@ -42,7 +41,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc721exp.balanceOf(alice.address)).to.equal(1); expect(await erc721exp.startTime(tokenId)).to.equal(startTime); expect(await erc721exp.endTime(tokenId)).to.equal(endTime); - await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, ZeroAddress, tokenId); + await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, constants.ZERO_ADDRESS, tokenId); await expect(erc721exp.isTokenExpired(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); }); @@ -61,7 +60,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { await erc721exp.updateTimeStamp(tokenId, startTime, endTime); expect(await erc721exp.startTime(tokenId)).to.equal(startTime); expect(await erc721exp.endTime(tokenId)).to.equal(endTime); - await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, ZeroAddress, tokenId); + await expect(erc721exp.burn(tokenId)).to.emit(erc721exp, ERC721.events.Transfer).withArgs(alice, constants.ZERO_ADDRESS, tokenId); await expect(erc721exp.isTokenExpired(tokenId)).to.be.revertedWithCustomError(erc721exp, ERC721.errors.ERC721NonexistentToken); await expect(erc721exp.mint(alice.address, tokenId)) .to.emit(erc721exp, ERC721.events.Transfer) diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts new file mode 100644 index 00000000..92a618b6 --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts @@ -0,0 +1,68 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {constants} from "../../../../constant.test"; +import {hardhat_reset} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("Interface", async function () { + afterEach(async function () { + await hardhat_reset(); + }); + + it("[SUCCESS] epochType", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.epochType()).to.equal(epochType); + }); + + it("[SUCCESS] validityDuration", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.validityDuration()).to.equal(constants.DEFAULT_WINDOW_SIZE); + }); + + it("[SUCCESS] currentEpoch", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.currentEpoch()).to.equal(0); + }); + + it("[SUCCESS] epochLength", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.currentEpoch()).to.equal(0); + }); + + it("[SUCCESS] isEpochExpired", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.isEpochExpired(0)).to.equal(false); + }); + + it("[SUCCESS] supportsInterface ERC-721", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.supportsInterface("0x80ac58cd")).to.equal(true); + }); + + it("[SUCCESS] supportsInterface ERC-7858", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.supportsInterface("0x3ebdfa31")).to.equal(true); + }); + + it("[SUCCESS] supportsInterface ERC-7858Epoch", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.supportsInterface("0xaaf87b24")).to.equal(true); + }); + + it("[FAILED] isEpochExpired", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + // @TODO skip epoch + // expect(await erc7858Epoch.isEpochExpired(100)).to.equal(false); + }); + + it("[FAILED] supportsInterface unknown", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.supportsInterface("0xffffffff")).to.equal(false); + }); + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts index fc4e23d8..3c13b8f3 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts @@ -6,12 +6,14 @@ import {expect} from "chai"; import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; import {deployERC7858EpochSelector} from "./deployer.test"; -import {ERC721, ERC7858, constants} from "../../../../constant.test"; +import {ERC721, constants} from "../../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Burn", async function () { const tokenId = 1; - const expectBalance = 1; + const expectBalanceAfterMint = 1; + const expectBalanceMultiAfterMint = 10; + const expectBalanceAfterBurn = 0; let startTime = 0; let endTime = 0; @@ -22,6 +24,39 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { endTime = 0; }); - // @TODO + it("[SUCCESS] burn expirable token", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceAfterMint); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceAfterMint); + await expect(erc7858Epoch.burn(tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(alice.address, constants.ZERO_ADDRESS, tokenId); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceAfterBurn); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceAfterBurn); + await expect(erc7858Epoch.startTime(tokenId)).to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721NonexistentToken); + await expect(erc7858Epoch.endTime(tokenId)).to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721NonexistentToken); + await expect(erc7858Epoch.isTokenExpired(tokenId)).to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721NonexistentToken); + }); + + it("[SUCCESS] mint multiple expirable token", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + for (let index = tokenId; index <= 10; index++) { + await expect(erc7858Epoch.mint(alice.address, index)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, index); + } + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceMultiAfterMint); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceMultiAfterMint); + for (let index = tokenId; index <= 10; index++) { + await expect(erc7858Epoch.burn(index)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(alice.address, constants.ZERO_ADDRESS, index); + } + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceAfterBurn); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceAfterBurn); + }); }); }; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts index 4c992d91..471c6387 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts @@ -7,6 +7,7 @@ import {constants} from "../../../../constant.test"; import * as Mint from "./mint.test"; import * as Burn from "./burn.test"; import * as Approve from "./burn.test"; +import * as Interface from "./Interface.test"; import * as Transfer from "./transfer.test"; import * as TransferFrom from "./transferFrom.test"; @@ -15,6 +16,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { Mint.run({epochType}); Burn.run({epochType}); Approve.run({epochType}); + Interface.run({epochType}); Transfer.run({epochType}); TransferFrom.run({epochType}); }); diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts index cac4a275..05d7f3ec 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts @@ -6,12 +6,13 @@ import {expect} from "chai"; import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; import {deployERC7858EpochSelector} from "./deployer.test"; -import {ERC721, ERC7858, constants} from "../../../../constant.test"; +import {ERC721, constants} from "../../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Mint", async function () { const tokenId = 1; const expectBalance = 1; + const expectBalanceMulti = 10; let startTime = 0; let endTime = 0; @@ -24,13 +25,13 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { it("[SUCCESS] mint expirable token", async function () { const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + const validityDuration = Number(await erc7858Epoch.validityDuration()); + const epochLength = Number(await erc7858Epoch.epochLength()); await expect(erc7858Epoch.mint(alice.address, tokenId)) .to.emit(erc7858Epoch, ERC721.events.Transfer) .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); - expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalance); - const validityDuration = Number(await erc7858Epoch.validityDuration()); - const epochLength = Number(await erc7858Epoch.epochLength()); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalance); if (epochType == constants.EPOCH_TYPE.BLOCKS_BASED) { startTime = await hardhat_latestBlock(); } else { @@ -41,5 +42,16 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.endTime(tokenId)).to.equal(endTime); expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); }); + + it("[SUCCESS] mint multiple expirable token", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + for (let index = tokenId; index <= 10; index++) { + await expect(erc7858Epoch.mint(alice.address, index)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, index); + } + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceMulti); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceMulti); + }); }); }; From e515d2ae601b911ef42f54593b109be4a7163fff Mon Sep 17 00:00:00 2001 From: paramet kongjaroen Date: Mon, 3 Mar 2025 13:17:46 +0700 Subject: [PATCH 44/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20remove=20duplicate?= =?UTF-8?q?=20function=20with=20same=20business=20logic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tokens/ERC721/extensions/ERC7858EpochBase.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index f991e8c5..3e3f130f 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -442,16 +442,6 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I return _expired(id); } - /// @dev See {IERC7858Epoch-isTokenValid}. - function isTokenValid(uint256 tokenId) external view returns (bool) { - uint256 pointer = _tokenPointers[tokenId]; - if (pointer != 0) { - if (_pointerProvider() - pointer < _getPointersInWindow()) { - return true; - } - } - } - function _getEpoch(uint256 pointer) internal view virtual returns (uint256) {} function _getWindowRage(uint256 pointer) internal view virtual returns (uint256 fromEpoch, uint256 toEpoch) {} From 9325b32c1bd062ee67c4d3b3c26d4b756d8856aa Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 3 Mar 2025 13:24:07 +0700 Subject: [PATCH 45/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20refactor=20isE?= =?UTF-8?q?pochExpired=20testcase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extensions/ERC7858Epoch/Interface.test.ts | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts index 92a618b6..91b5b1d9 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts @@ -5,7 +5,7 @@ import {expect} from "chai"; import {constants} from "../../../../constant.test"; -import {hardhat_reset} from "../../../../utils.test"; +import {hardhat_increasePointerTo, hardhat_reset} from "../../../../utils.test"; import {deployERC7858EpochSelector} from "./deployer.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { @@ -36,7 +36,14 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { it("[SUCCESS] isEpochExpired", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + const epochLength = await erc7858Epoch.epochLength(); + const duration = await erc7858Epoch.validityDuration(); + + await hardhat_increasePointerTo(epochType, epochLength * duration); expect(await erc7858Epoch.isEpochExpired(0)).to.equal(false); + + await hardhat_increasePointerTo(epochType, epochLength + 1n); + expect(await erc7858Epoch.isEpochExpired(0)).to.equal(true); }); it("[SUCCESS] supportsInterface ERC-721", async function () { @@ -54,12 +61,6 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.supportsInterface("0xaaf87b24")).to.equal(true); }); - it("[FAILED] isEpochExpired", async function () { - const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); - // @TODO skip epoch - // expect(await erc7858Epoch.isEpochExpired(100)).to.equal(false); - }); - it("[FAILED] supportsInterface unknown", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); expect(await erc7858Epoch.supportsInterface("0xffffffff")).to.equal(false); From 8e9d2b82c3ee26ce5c415e3204c46f1b73382f0f Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 3 Mar 2025 16:10:04 +0700 Subject: [PATCH 46/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20adding=20testcase?= =?UTF-8?q?=20to=20erc7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 16 ++--- test/tokens/ERC721/base/approve.test.ts | 21 ------- test/tokens/ERC721/base/index.test.ts | 6 -- test/tokens/ERC721/base/transfer.test.ts | 21 ------- test/tokens/ERC721/base/transferFrom.test.ts | 21 ------- .../extensions/ERC7858Epoch/Interface.test.ts | 60 ++++++++++++++++++- .../extensions/ERC7858Epoch/approve.test.ts | 30 +++++++++- .../extensions/ERC7858Epoch/index.test.ts | 2 +- .../extensions/ERC7858Epoch/mint.test.ts | 1 + 9 files changed, 97 insertions(+), 81 deletions(-) delete mode 100644 test/tokens/ERC721/base/approve.test.ts delete mode 100644 test/tokens/ERC721/base/transfer.test.ts delete mode 100644 test/tokens/ERC721/base/transferFrom.test.ts diff --git a/README.md b/README.md index c17a1c95..87db6b3a 100644 --- a/README.md +++ b/README.md @@ -78,13 +78,13 @@ contract ExpirableERC20 is ERC20BLSW { // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -import {ERC721BLSW} from "@kiwarilabs/contracts/tokens/ERC721/ERC721B.sol"; -import {ERC721EXPBase} from "@kiwarilabs/contracts/tokens/ERC721/ERC721EXPBase.sol"; +import {ERC7858BLSW} from "@kiwarilabs/contracts/tokens/ERC721/ERC7858BLSW.sol"; +import {ERC7858EXPBase} from "@kiwarilabs/contracts/tokens/ERC721/ERC7858EXPBase.sol"; // Expirable ERC721 with individual expiration -contract ExpirableERC721 is ERC721BLSW { +contract ExpirableERC721 is ERC7858BLSW { - constructor (string memory name_, string memory symbol_) ERC721BLSW(name_, symbol) {} + constructor (string memory name_, string memory symbol_) ERC7858BLSW(name_, symbol) {} } ``` @@ -95,11 +95,11 @@ contract ExpirableERC721 is ERC721BLSW { // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.0; -import {ERC721BLSW} from "@kiwarilabs/contracts/tokens/ERC721/extensions/ERC721BLSW.sol"; -import {ERC721EXPEpochBase} from "@kiwarilabs/contracts/tokens/ERC721/extensions/ERC721EpochBase.sol"; +import {ERC7858EpochBLSW} from "@kiwarilabs/contracts/tokens/ERC721/extensions/ERC7858EpochBLSW.sol"; +import {ERC7858EXPEpochBase} from "@kiwarilabs/contracts/tokens/ERC721/extensions/ERC7858EXPEpochBase.sol"; // Expirable ERC721 with epoch expiration -contract ExpirableERC721 is ERC721BLSW { +contract ExpirableERC7858 is ERC7858EpochBLSW { constructor ( string memory name_, @@ -108,7 +108,7 @@ contract ExpirableERC721 is ERC721BLSW { uint40 blocksPerEpoch_, uint8 windowSize_, bool development_) - ERC721BLSW( + ERC7858EpochBLSW( name_, symbol_, initialBlockNumber_, diff --git a/test/tokens/ERC721/base/approve.test.ts b/test/tokens/ERC721/base/approve.test.ts deleted file mode 100644 index 3d737b6c..00000000 --- a/test/tokens/ERC721/base/approve.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. -// Node module: kiwari-labs-contracts -// This file is licensed under the Apache License 2.0. -// License text available at https://www.apache.org/licenses/LICENSE-2.0 - -import {expect} from "chai"; -import {ERC721, constants} from "../../../constant.test"; -import {deployERC7858Selector} from "./deployer.test"; -import {hardhat_impersonate, hardhat_reset, hardhat_setBalance, hardhat_stopImpersonating, ethers} from "../../../utils.test"; - -export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("Approval", async function () { - const tokenId = 1; - - afterEach(async function () { - await hardhat_reset(); - }); - - // @TODO - }); -}; diff --git a/test/tokens/ERC721/base/index.test.ts b/test/tokens/ERC721/base/index.test.ts index 4519d4f7..5a34c669 100644 --- a/test/tokens/ERC721/base/index.test.ts +++ b/test/tokens/ERC721/base/index.test.ts @@ -6,18 +6,12 @@ import {constants} from "../../../constant.test"; import * as Mint from "./mint.test"; import * as Burn from "./burn.test"; -import * as Approval from "./approve.test"; import * as Interface from "./interface.test"; -import * as Transfer from "./transfer.test"; -import * as TransferFrom from "./transferFrom.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("ERC7858:Base", async function () { Mint.run({epochType}); Burn.run({epochType}); - Approval.run({epochType}); Interface.run({epochType}); - Transfer.run({epochType}); - TransferFrom.run({epochType}); }); }; diff --git a/test/tokens/ERC721/base/transfer.test.ts b/test/tokens/ERC721/base/transfer.test.ts deleted file mode 100644 index c8c6b7f0..00000000 --- a/test/tokens/ERC721/base/transfer.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. -// Node module: kiwari-labs-contracts -// This file is licensed under the Apache License 2.0. -// License text available at https://www.apache.org/licenses/LICENSE-2.0 - -import {expect} from "chai"; -import {deployERC7858Selector} from "./deployer.test"; -import {ERC721, constants} from "../../../constant.test"; -import {ethers, hardhat_impersonate, hardhat_reset, hardhat_setBalance, hardhat_stopImpersonating} from "../../../utils.test"; - -export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("Transfer", async function () { - const tokenId = 1; - - afterEach(async function () { - await hardhat_reset(); - }); - - // @TODO - }); -}; diff --git a/test/tokens/ERC721/base/transferFrom.test.ts b/test/tokens/ERC721/base/transferFrom.test.ts deleted file mode 100644 index dd2e7f11..00000000 --- a/test/tokens/ERC721/base/transferFrom.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. -// Node module: kiwari-labs-contracts -// This file is licensed under the Apache License 2.0. -// License text available at https://www.apache.org/licenses/LICENSE-2.0 - -import {expect} from "chai"; -import {deployERC7858Selector} from "./deployer.test"; -import {ERC721, constants} from "../../../constant.test"; -import {hardhat_reset} from "../../../utils.test"; - -export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("TransferFrom", async function () { - const tokenId = 1; - - afterEach(async function () { - await hardhat_reset(); - }); - - // @TODO - }); -}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts index 91b5b1d9..8a308be1 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts @@ -4,16 +4,38 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {expect} from "chai"; -import {constants} from "../../../../constant.test"; +import {constants, ERC721} from "../../../../constant.test"; import {hardhat_increasePointerTo, hardhat_reset} from "../../../../utils.test"; import {deployERC7858EpochSelector} from "./deployer.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + const tokenId = 1; + describe("Interface", async function () { afterEach(async function () { await hardhat_reset(); }); + // Interface ERC-721 and ERC-721Metadata + it("[SUCCESS] ERC-721 and ERC-721Metadata", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.name()).to.equal(ERC721.constructor.name); + expect(await erc7858Epoch.symbol()).to.equal(ERC721.constructor.symbol); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(0); + await expect(erc7858Epoch.balanceOf(constants.ZERO_ADDRESS)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidOwner) + .withArgs(constants.ZERO_ADDRESS); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc7858Epoch.tokenURI(tokenId)).to.equal(""); + }); + + it("[SUCCESS] expiryType", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.expiryType()).to.equal(epochType); + }); + it("[SUCCESS] epochType", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); expect(await erc7858Epoch.epochType()).to.equal(epochType); @@ -46,11 +68,38 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.isEpochExpired(0)).to.equal(true); }); + it("[SUCCESS] balanceOfAtEpoch", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.balanceOfAtEpoch(0, alice.address)).to.equal(0); + }); + + it("[SUCCESS] unexpiredBalanceOf", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(0); + }); + + it("[SUCCESS] unexpiredBalanceOf zeroAddress", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.unexpiredBalanceOf(constants.ZERO_ADDRESS)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidOwner) + .withArgs(constants.ZERO_ADDRESS); + }); + + it("[SUCCESS] supportsInterface ERC-165", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.supportsInterface("0x01ffc9a7")).to.equal(true); + }); + it("[SUCCESS] supportsInterface ERC-721", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); expect(await erc7858Epoch.supportsInterface("0x80ac58cd")).to.equal(true); }); + it("[SUCCESS] supportsInterface ERC-721Metadata", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + expect(await erc7858Epoch.supportsInterface("0x5b5e139f")).to.equal(true); + }); + it("[SUCCESS] supportsInterface ERC-7858", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); expect(await erc7858Epoch.supportsInterface("0x3ebdfa31")).to.equal(true); @@ -61,6 +110,15 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.supportsInterface("0xaaf87b24")).to.equal(true); }); + it("[FAILED] balanceOfAtEpoch expiredEpoch", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + const epochLength = await erc7858Epoch.epochLength(); + const duration = await erc7858Epoch.validityDuration(); + await hardhat_increasePointerTo(epochType, epochLength * duration); + await hardhat_increasePointerTo(epochType, epochLength + 1n); + expect(await erc7858Epoch.balanceOfAtEpoch(0, alice.address)).to.equal(0); + }); + it("[FAILED] supportsInterface unknown", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); expect(await erc7858Epoch.supportsInterface("0xffffffff")).to.equal(false); diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts index 4c8843a1..99342312 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts @@ -6,7 +6,7 @@ import {expect} from "chai"; import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; import {deployERC7858EpochSelector} from "./deployer.test"; -import {ERC721, ERC7858, constants} from "../../../../constant.test"; +import {ERC721, constants} from "../../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Approve", async function () { @@ -22,6 +22,32 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { endTime = 0; }); - // @TODO + it("[SUCCESS] approve token", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice).approve(bob.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Approval) + .withArgs(alice.address, bob.address, tokenId); + expect(await erc7858Epoch.getApproved(tokenId)).to.equal(bob.address); + }); + + it("[FAILED] invalid approved", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(bob).approve(alice.address, tokenId)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidApprover) + .withArgs(bob.address); + }); + + it("[FAILED] approve nonexistence token", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.connect(alice).approve(bob.address, tokenId)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721NonexistentToken) + .withArgs(tokenId); + }); }); }; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts index 471c6387..71355ab3 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts @@ -6,7 +6,7 @@ import {constants} from "../../../../constant.test"; import * as Mint from "./mint.test"; import * as Burn from "./burn.test"; -import * as Approve from "./burn.test"; +import * as Approve from "./approve.test"; import * as Interface from "./Interface.test"; import * as Transfer from "./transfer.test"; import * as TransferFrom from "./transferFrom.test"; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts index 05d7f3ec..6e0a8475 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts @@ -41,6 +41,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.startTime(tokenId)).to.equal(startTime); expect(await erc7858Epoch.endTime(tokenId)).to.equal(endTime); expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); + expect(await erc7858Epoch.ownerOf(tokenId)).to.equal(alice.address); }); it("[SUCCESS] mint multiple expirable token", async function () { From 5a7ce18532f63a6332ebb2df5ba0429afc60840e Mon Sep 17 00:00:00 2001 From: MASDXI Date: Mon, 3 Mar 2025 19:57:38 +0700 Subject: [PATCH 47/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20adding=20testcase?= =?UTF-8?q?=20to=20erc7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC7858EpochBase.sol | 11 +++++- .../extensions/ERC7858Epoch/approve.test.ts | 38 ++++++++++++++++--- .../extensions/ERC7858Epoch/burn.test.ts | 7 +--- .../extensions/ERC7858Epoch/index.test.ts | 4 +- ...nsfer.test.ts => safeTransferFrom.test.ts} | 2 +- .../ERC7858Epoch/transferFrom.test.ts | 33 +++++++++++++--- 6 files changed, 73 insertions(+), 22 deletions(-) rename test/tokens/ERC721/extensions/ERC7858Epoch/{transfer.test.ts => safeTransferFrom.test.ts} (94%) diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index 3e3f130f..8f77193d 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -159,8 +159,15 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I revert ERC721InvalidOwner(address(0)); } uint256 pointer = _pointerProvider(); - (uint256 fromEpoch, ) = _getWindowRage(pointer); - return _computeBalanceAtEpoch(fromEpoch, owner, pointer, _getPointersInWindow()); + (uint256 fromEpoch, uint256 toEpoch) = _getWindowRage(pointer); + uint256 balance = _computeBalanceAtEpoch(fromEpoch, owner, pointer, _getPointersInWindow()); + if (fromEpoch == toEpoch) { + return balance; + } else { + fromEpoch += 1; + } + balance += _computeBalanceOverEpochRange(fromEpoch, toEpoch, owner); + return balance; } /** diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts index 99342312..2c5cf7cc 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts @@ -12,14 +12,9 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("Approve", async function () { const tokenId = 1; const expectBalance = 1; - let startTime = 0; - let endTime = 0; afterEach(async function () { await hardhat_reset(); - /** ensure safety reset starTime and endTime to zero */ - startTime = 0; - endTime = 0; }); it("[SUCCESS] approve token", async function () { @@ -33,6 +28,39 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.getApproved(tokenId)).to.equal(bob.address); }); + it("[SUCCESS] approve token for all", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice).setApprovalForAll(bob.address, true)) + .to.emit(erc7858Epoch, ERC721.events.ApprovalForAll) + .withArgs(alice.address, bob.address, true); + expect(await erc7858Epoch.isApprovedForAll(alice.address, bob.address)).to.equal(true); + }); + + it("[SUCCESS] approve and transferFrom", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice).approve(bob.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Approval) + .withArgs(alice.address, bob.address, tokenId); + expect(await erc7858Epoch.getApproved(tokenId)).to.equal(bob.address); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalance); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalance); + await expect(erc7858Epoch.connect(bob).transferFrom(alice.address, bob.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(alice.address, bob.address, tokenId); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(0); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(0); + expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); + expect(await erc7858Epoch.balanceOf(bob.address)).to.equal(expectBalance); + expect(await erc7858Epoch.unexpiredBalanceOf(bob.address)).to.equal(expectBalance); + expect(await erc7858Epoch.ownerOf(tokenId)).to.equal(bob.address); + }); + it("[FAILED] invalid approved", async function () { const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); await expect(erc7858Epoch.mint(alice.address, tokenId)) diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts index 3c13b8f3..5cbefaae 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/burn.test.ts @@ -14,14 +14,9 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { const expectBalanceAfterMint = 1; const expectBalanceMultiAfterMint = 10; const expectBalanceAfterBurn = 0; - let startTime = 0; - let endTime = 0; afterEach(async function () { await hardhat_reset(); - /** ensure safety reset starTime and endTime to zero */ - startTime = 0; - endTime = 0; }); it("[SUCCESS] burn expirable token", async function () { @@ -41,7 +36,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { await expect(erc7858Epoch.isTokenExpired(tokenId)).to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721NonexistentToken); }); - it("[SUCCESS] mint multiple expirable token", async function () { + it("[SUCCESS] burn multiple expirable token", async function () { const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); for (let index = tokenId; index <= 10; index++) { await expect(erc7858Epoch.mint(alice.address, index)) diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts index 71355ab3..c0c222eb 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts @@ -8,8 +8,8 @@ import * as Mint from "./mint.test"; import * as Burn from "./burn.test"; import * as Approve from "./approve.test"; import * as Interface from "./Interface.test"; -import * as Transfer from "./transfer.test"; import * as TransferFrom from "./transferFrom.test"; +import * as SafeTransferFrom from "./safeTransferFrom.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("ERC7858Epoch", async function () { @@ -17,7 +17,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { Burn.run({epochType}); Approve.run({epochType}); Interface.run({epochType}); - Transfer.run({epochType}); TransferFrom.run({epochType}); + SafeTransferFrom.run({epochType}); }); }; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts similarity index 94% rename from test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts rename to test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts index a62d4ce0..94df1680 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/transfer.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts @@ -9,7 +9,7 @@ import {deployERC7858EpochSelector} from "./deployer.test"; import {ERC721, ERC7858, constants} from "../../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { - describe("Transfer", async function () { + describe("SafeTransferFrom", async function () { const tokenId = 1; const expectBalance = 1; let startTime = 0; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts index 22df441d..a8e2daf3 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/transferFrom.test.ts @@ -12,16 +12,37 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("TransferFrom", async function () { const tokenId = 1; const expectBalance = 1; - let startTime = 0; - let endTime = 0; afterEach(async function () { await hardhat_reset(); - /** ensure safety reset starTime and endTime to zero */ - startTime = 0; - endTime = 0; }); - // @TODO + it("[SUCCESS] transferFrom", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice).transferFrom(alice.address, bob.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(alice.address, bob.address, tokenId); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(0); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(0); + expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); + expect(await erc7858Epoch.balanceOf(bob.address)).to.equal(expectBalance); + expect(await erc7858Epoch.unexpiredBalanceOf(bob.address)).to.equal(expectBalance); + expect(await erc7858Epoch.ownerOf(tokenId)).to.equal(bob.address); + }); + + // @TODO transfer expired tokenId + + it("[FAILED] transferFrom to zeroAddress", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice).transferFrom(alice.address, constants.ZERO_ADDRESS, tokenId)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidReceiver) + .withArgs(constants.ZERO_ADDRESS); + }); }); }; From 84c0c2cdd84c4a9b82f9d7c1ac4eda0e604c1eca Mon Sep 17 00:00:00 2001 From: MASDXI Date: Tue, 4 Mar 2025 10:16:29 +0700 Subject: [PATCH 48/53] =?UTF-8?q?test:=20=F0=9F=92=8D=20adding=20test=20ca?= =?UTF-8?q?se=20to=20erc7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC7858EpochBase.sol | 2 - .../extensions/BLSW/MockERC7858EpochBLSW.sol | 8 +++ .../extensions/TLSW/MockERC7858EpochTLSW.sol | 8 +++ mocks/contracts/utils/MockBLSW.sol | 4 ++ .../extensions/ERC7858Epoch/approve.test.ts | 12 +++- .../extensions/ERC7858Epoch/index.test.ts | 2 + .../extensions/ERC7858Epoch/mint.test.ts | 7 ++ .../extensions/ERC7858Epoch/safeMint.test.ts | 65 +++++++++++++++++++ .../ERC7858Epoch/safeTransferFrom.test.ts | 35 +++++++--- test/utils/algorithms/BLSW/general.test.ts | 8 ++- 10 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 test/tokens/ERC721/extensions/ERC7858Epoch/safeMint.test.ts diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index 8f77193d..1d37f9d6 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -17,8 +17,6 @@ import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; import {IERC721Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; -// @TODO following https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v5.1.0/contracts/token/ERC721/ERC721.sol - abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, IERC721Metadata, IERC7858Epoch { using SortedList for SortedList.List; using Strings for uint256; diff --git a/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol b/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol index 07e733f1..abba19fe 100644 --- a/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol +++ b/mocks/contracts/tokens/ERC721/extensions/BLSW/MockERC7858EpochBLSW.sol @@ -18,4 +18,12 @@ contract MockERC7858EpochBLSW is ERC7858 { function burn(uint256 tokenId) public { _burn(tokenId); } + + function safeMint(address to, uint256 tokenId) public { + _safeMint(to, tokenId); + } + + function safeMint(address to, uint256 tokenId, bytes memory data) public { + _safeMint(to, tokenId, data); + } } diff --git a/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol b/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol index 905e9b3e..9ed6620c 100644 --- a/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol +++ b/mocks/contracts/tokens/ERC721/extensions/TLSW/MockERC7858EpochTLSW.sol @@ -18,4 +18,12 @@ contract MockERC7858EpochTLSW is ERC7858 { function burn(uint256 tokenId) public { _burn(tokenId); } + + function safeMint(address to, uint256 tokenId) public { + _safeMint(to, tokenId); + } + + function safeMint(address to, uint256 tokenId, bytes memory data) public { + _safeMint(to, tokenId, data); + } } diff --git a/mocks/contracts/utils/MockBLSW.sol b/mocks/contracts/utils/MockBLSW.sol index 5d58362c..0ea2effa 100644 --- a/mocks/contracts/utils/MockBLSW.sol +++ b/mocks/contracts/utils/MockBLSW.sol @@ -16,6 +16,10 @@ contract MockBLSW { window.initializedState(blocksPerEpoch, windowSize_, safe); } + function getInitialBlockNumber() public view returns (uint256) { + return window.getInitialBlockNumber(); + } + function windowRange(uint256 blockNumber) public view returns (uint256, uint256) { return window.windowRange(blockNumber); } diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts index 2c5cf7cc..9ee9bbe4 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/approve.test.ts @@ -61,7 +61,7 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.ownerOf(tokenId)).to.equal(bob.address); }); - it("[FAILED] invalid approved", async function () { + it("[FAILED] invalid approver", async function () { const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); await expect(erc7858Epoch.mint(alice.address, tokenId)) .to.emit(erc7858Epoch, ERC721.events.Transfer) @@ -71,6 +71,16 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { .withArgs(bob.address); }); + it("[FAILED] invalid approved for all", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice).setApprovalForAll(constants.ZERO_ADDRESS, true)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidOperator) + .withArgs(constants.ZERO_ADDRESS); + }); + it("[FAILED] approve nonexistence token", async function () { const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); await expect(erc7858Epoch.connect(alice).approve(bob.address, tokenId)) diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts index c0c222eb..879b1dc7 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/index.test.ts @@ -5,6 +5,7 @@ import {constants} from "../../../../constant.test"; import * as Mint from "./mint.test"; +import * as SafeMint from "./safeMint.test"; import * as Burn from "./burn.test"; import * as Approve from "./approve.test"; import * as Interface from "./Interface.test"; @@ -14,6 +15,7 @@ import * as SafeTransferFrom from "./safeTransferFrom.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("ERC7858Epoch", async function () { Mint.run({epochType}); + SafeMint.run({epochType}); Burn.run({epochType}); Approve.run({epochType}); Interface.run({epochType}); diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts index 6e0a8475..f56aa5ad 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/mint.test.ts @@ -54,5 +54,12 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceMulti); expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceMulti); }); + + it("[FAILED] mint expirable token to zeroAddress", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(constants.ZERO_ADDRESS, tokenId)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidReceiver) + .withArgs(constants.ZERO_ADDRESS); + }); }); }; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/safeMint.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/safeMint.test.ts new file mode 100644 index 00000000..1ba5e841 --- /dev/null +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/safeMint.test.ts @@ -0,0 +1,65 @@ +// Copyright Kiwari Labs and @kiwarilabs/contracts contributors 2024,2025. All Rights Reserved. +// Node module: kiwari-labs-contracts +// This file is licensed under the Apache License 2.0. +// License text available at https://www.apache.org/licenses/LICENSE-2.0 + +import {expect} from "chai"; +import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {deployERC7858EpochSelector} from "./deployer.test"; +import {ERC721, constants} from "../../../../constant.test"; + +export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { + describe("SafeMint", async function () { + const tokenId = 1; + const expectBalance = 1; + const expectBalanceMulti = 10; + let startTime = 0; + let endTime = 0; + + afterEach(async function () { + await hardhat_reset(); + /** ensure safety reset starTime and endTime to zero */ + startTime = 0; + endTime = 0; + }); + + it("[SUCCESS] safeMint expirable token", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + const validityDuration = Number(await erc7858Epoch.validityDuration()); + const epochLength = Number(await erc7858Epoch.epochLength()); + await expect(erc7858Epoch["safeMint(address,uint256)"](alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalance); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalance); + if (epochType == constants.EPOCH_TYPE.BLOCKS_BASED) { + startTime = await hardhat_latestBlock(); + } else { + startTime = await hardhat_latest(); + } + endTime = startTime + validityDuration * epochLength; + expect(await erc7858Epoch.startTime(tokenId)).to.equal(startTime); + expect(await erc7858Epoch.endTime(tokenId)).to.equal(endTime); + expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); + expect(await erc7858Epoch.ownerOf(tokenId)).to.equal(alice.address); + }); + + it("[SUCCESS] safeMint multiple expirable token", async function () { + const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); + for (let index = tokenId; index <= 10; index++) { + await expect(erc7858Epoch["safeMint(address,uint256)"](alice.address, index)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, index); + } + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(expectBalanceMulti); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(expectBalanceMulti); + }); + + it("[FAILED] safeMint expirable token to zeroAddress", async function () { + const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(constants.ZERO_ADDRESS, tokenId)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidReceiver) + .withArgs(constants.ZERO_ADDRESS); + }); + }); +}; diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts index 94df1680..2086f47c 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/safeTransferFrom.test.ts @@ -4,24 +4,43 @@ // License text available at https://www.apache.org/licenses/LICENSE-2.0 import {expect} from "chai"; -import {hardhat_reset, hardhat_latestBlock, hardhat_latest} from "../../../../utils.test"; +import {hardhat_reset} from "../../../../utils.test"; import {deployERC7858EpochSelector} from "./deployer.test"; -import {ERC721, ERC7858, constants} from "../../../../constant.test"; +import {ERC721, constants} from "../../../../constant.test"; export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { describe("SafeTransferFrom", async function () { const tokenId = 1; const expectBalance = 1; - let startTime = 0; - let endTime = 0; afterEach(async function () { await hardhat_reset(); - /** ensure safety reset starTime and endTime to zero */ - startTime = 0; - endTime = 0; }); - // @TODO + it("[SUCCESS] safeTransferFrom", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice)["safeTransferFrom(address,address,uint256)"](alice.address, bob.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(alice.address, bob.address, tokenId); + expect(await erc7858Epoch.balanceOf(alice.address)).to.equal(0); + expect(await erc7858Epoch.unexpiredBalanceOf(alice.address)).to.equal(0); + expect(await erc7858Epoch.isTokenExpired(tokenId)).to.equal(false); + expect(await erc7858Epoch.balanceOf(bob.address)).to.equal(expectBalance); + expect(await erc7858Epoch.unexpiredBalanceOf(bob.address)).to.equal(expectBalance); + expect(await erc7858Epoch.ownerOf(tokenId)).to.equal(bob.address); + }); + + it("[FAILED] safeTransferFrom to zeroAddress", async function () { + const {erc7858Epoch, alice, bob} = await deployERC7858EpochSelector({epochType}); + await expect(erc7858Epoch.mint(alice.address, tokenId)) + .to.emit(erc7858Epoch, ERC721.events.Transfer) + .withArgs(constants.ZERO_ADDRESS, alice.address, tokenId); + await expect(erc7858Epoch.connect(alice)["safeTransferFrom(address,address,uint256)"](alice.address, constants.ZERO_ADDRESS, tokenId)) + .to.revertedWithCustomError(erc7858Epoch, ERC721.errors.ERC721InvalidReceiver) + .withArgs(constants.ZERO_ADDRESS); + }); }); }; diff --git a/test/utils/algorithms/BLSW/general.test.ts b/test/utils/algorithms/BLSW/general.test.ts index 77ffae33..87b8cc73 100644 --- a/test/utils/algorithms/BLSW/general.test.ts +++ b/test/utils/algorithms/BLSW/general.test.ts @@ -6,7 +6,7 @@ import {expect} from "chai"; import {constants, SlidingWindow} from "../../../constant.test"; import {calculateSlidingWindowState, deployBLSW} from "./deployer.test"; -import {hardhat_reset} from "../../../utils.test"; +import {hardhat_latestBlock, hardhat_reset} from "../../../utils.test"; export const run = async () => { describe("General", async function () { @@ -14,6 +14,12 @@ export const run = async () => { await hardhat_reset(); }); + it("[SUCCESS] getInitialBlockNumber", async function () { + const {slidingWindow} = await deployBLSW({}); + const blockNumber = await hardhat_latestBlock(); + expect(await slidingWindow.getInitialBlockNumber()).to.equal(blockNumber); + }); + it("[SUCCESS] blocksInEpoch", async function () { const {slidingWindow} = await deployBLSW({}); const self = calculateSlidingWindowState({}); From 4e0dd592e3211badea423c5d0e8edb027ceafef6 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 5 Mar 2025 14:06:16 +0700 Subject: [PATCH 49/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20incorrect=20interfa?= =?UTF-8?q?ceId=20erc7858epoch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ERC721/extensions/ERC7858EpochBase.sol | 4 ++-- .../tokens/ERC721/interfaces/IERC7858Epoch.sol | 18 +++++++++--------- .../extensions/ERC7858Epoch/Interface.test.ts | 10 +++++----- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index 1d37f9d6..d80e7849 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -400,8 +400,8 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I emit ApprovalForAll(owner, operator, approved); } - /// @dev See {IERC7858-balanceOFAtEpoch}. - function balanceOfAtEpoch(uint256 epoch, address owner) external view returns (uint256) { + /// @dev See {IERC7858-unexpiredBalanceOfAtEpoch}. + function unexpiredBalanceOfAtEpoch(uint256 epoch, address owner) external view override returns (uint256) { if (isEpochExpired(epoch)) return 0; return _computeBalanceAtEpoch(epoch, owner, _pointerProvider(), _getPointersInWindow()); } diff --git a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol index 4631df3e..8aa1642e 100644 --- a/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol +++ b/contracts/tokens/ERC721/interfaces/IERC7858Epoch.sol @@ -6,15 +6,6 @@ pragma solidity >=0.8.0 <0.9.0; import {IERC7858} from "./IERC7858.sol"; interface IERC7858Epoch is IERC7858 { - /** - * @dev Retrieves the balance of a specific `epoch` owned by an account. - * @param epoch The `epoch for which the balance is checked. - * @param account The address of the account. - * @return uint256 The balance of the specified `epoch`. - * @notice "MUST" return 0 if the specified `epoch` is expired. - */ - function balanceOfAtEpoch(uint256 epoch, address account) external view returns (uint256); - /** * @dev Retrieves the current epoch of the contract. * @return uint256 The current epoch of the token contract, @@ -52,6 +43,15 @@ interface IERC7858Epoch is IERC7858 { */ function unexpiredBalanceOf(address account) external view returns (uint256); + /** + * @dev Retrieves the balance of a specific `epoch` owned by an account. + * @param epoch The `epoch for which the balance is checked. + * @param account The address of the account. + * @return uint256 The balance of the specified `epoch`. + * @notice "MUST" return 0 if the specified `epoch` is expired. + */ + function unexpiredBalanceOfAtEpoch(uint256 epoch, address account) external view returns (uint256); + /** * @dev Retrieves the validity duration of each token. * @return uint256 The validity duration of each token in `epoch` unit. diff --git a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts index 8a308be1..61532641 100644 --- a/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts +++ b/test/tokens/ERC721/extensions/ERC7858Epoch/Interface.test.ts @@ -68,9 +68,9 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { expect(await erc7858Epoch.isEpochExpired(0)).to.equal(true); }); - it("[SUCCESS] balanceOfAtEpoch", async function () { + it("[SUCCESS] unexpiredBalanceOfAtEpoch", async function () { const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); - expect(await erc7858Epoch.balanceOfAtEpoch(0, alice.address)).to.equal(0); + expect(await erc7858Epoch.unexpiredBalanceOfAtEpoch(0, alice.address)).to.equal(0); }); it("[SUCCESS] unexpiredBalanceOf", async function () { @@ -107,16 +107,16 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { it("[SUCCESS] supportsInterface ERC-7858Epoch", async function () { const {erc7858Epoch} = await deployERC7858EpochSelector({epochType}); - expect(await erc7858Epoch.supportsInterface("0xaaf87b24")).to.equal(true); + expect(await erc7858Epoch.supportsInterface("0x8f55b98a")).to.equal(true); }); - it("[FAILED] balanceOfAtEpoch expiredEpoch", async function () { + it("[FAILED] unexpiredBalanceOfAtEpoch expiredEpoch", async function () { const {erc7858Epoch, alice} = await deployERC7858EpochSelector({epochType}); const epochLength = await erc7858Epoch.epochLength(); const duration = await erc7858Epoch.validityDuration(); await hardhat_increasePointerTo(epochType, epochLength * duration); await hardhat_increasePointerTo(epochType, epochLength + 1n); - expect(await erc7858Epoch.balanceOfAtEpoch(0, alice.address)).to.equal(0); + expect(await erc7858Epoch.unexpiredBalanceOfAtEpoch(0, alice.address)).to.equal(0); }); it("[FAILED] supportsInterface unknown", async function () { From 5c45082c37cabd74416388e60dc0f5920da9768a Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 5 Mar 2025 16:57:46 +0700 Subject: [PATCH 50/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20increase=20rea?= =?UTF-8?q?bility=20and=20ensure=20ganularity=20balanceOf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/utils/datastructures/SortedList.sol | 8 ++++---- test/tokens/ERC20/ERC7818Behavior/interface.test.ts | 6 +++++- test/tokens/index.test.ts | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/contracts/utils/datastructures/SortedList.sol b/contracts/utils/datastructures/SortedList.sol index 9d1e1cb5..01d16e75 100644 --- a/contracts/utils/datastructures/SortedList.sol +++ b/contracts/utils/datastructures/SortedList.sol @@ -27,7 +27,7 @@ library SortedList { * @return array containing the indices of nodes in the linked list, ordered according to the specified direction. */ function _toArray(List storage self, uint256 start, uint256 length) private view returns (uint256[] memory array) { - if (start == SENTINEL) return array; + if (!contains(self, start)) return array; array = new uint256[](length); uint128 index; unchecked { @@ -129,7 +129,7 @@ library SortedList { uint256 beforeElement = self._nodes[element][PREVIOUS]; uint256 afterSentinel = self._nodes[SENTINEL][NEXT]; assembly { - result := or(eq(afterSentinel, element), gt(beforeElement, 0x00)) + result := or(eq(afterSentinel, element), gt(beforeElement, SENTINEL)) } } @@ -198,13 +198,13 @@ library SortedList { * @return array containing the indices of nodes in ascending order. */ function toArray(List storage self) internal view returns (uint256[] memory array) { - return _toArray(self, front(self), 0x200); + return _toArray(self, front(self), size()); } /* * @dev pagination like with static length set to 512. */ function toArray(List storage self, uint256 start) internal view returns (uint256[] memory array) { - return _toArray(self, start, 0x200); + return _toArray(self, start, size()); } } diff --git a/test/tokens/ERC20/ERC7818Behavior/interface.test.ts b/test/tokens/ERC20/ERC7818Behavior/interface.test.ts index e0dce510..5e5035af 100644 --- a/test/tokens/ERC20/ERC7818Behavior/interface.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/interface.test.ts @@ -36,7 +36,11 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { const {erc20exp, alice} = await deployERC20Selector({epochType}); const epoch = await erc20exp.currentEpoch(); expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(0); - expect(await erc20exp.balanceOfAtEpoch(epoch + 1n, alice.address)).to.equal(0); + await erc20exp.mint(alice.address, 1); + await erc20exp.mint(alice.address, 1); + expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(2); + await hardhat_increasePointerTo(epochType, (await erc20exp.epochLength()) * (await erc20exp.validityDuration()) - 1n); + expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(1); }); it("[SUCCESS] currentEpoch", async function () { diff --git a/test/tokens/index.test.ts b/test/tokens/index.test.ts index 948251f9..50030b4a 100644 --- a/test/tokens/index.test.ts +++ b/test/tokens/index.test.ts @@ -8,7 +8,7 @@ import * as ERC721 from "./ERC721/index.test"; export const run = async () => { describe("tokens", async function () { - // ERC20.run(); + ERC20.run(); ERC721.run(); }); }; From 377b7a0a8e3e7c41d0f8d1944fa2b385c6f3463c Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 5 Mar 2025 17:36:08 +0700 Subject: [PATCH 51/53] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20increase=20rea?= =?UTF-8?q?dability=20of=20erc7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/ERC7858EXPBase.sol | 10 +++++----- .../tokens/ERC721/extensions/ERC7858EpochBase.sol | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/tokens/ERC721/ERC7858EXPBase.sol b/contracts/tokens/ERC721/ERC7858EXPBase.sol index 067b9ca2..98b98457 100644 --- a/contracts/tokens/ERC721/ERC7858EXPBase.sol +++ b/contracts/tokens/ERC721/ERC7858EXPBase.sol @@ -35,7 +35,7 @@ abstract contract ERC7858EXPBase is ERC721, ERC721Enumerable, IERC7858 { } function _validation(uint256 tokenId) internal view returns (bool) { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); AssetTimeStamp memory timestamp = _tokensTimeStamp[tokenId]; uint256 current = _pointerProvider(); // if start and end is {0, 0} mean token non-expirable and return false. @@ -47,7 +47,7 @@ abstract contract ERC7858EXPBase is ERC721, ERC721Enumerable, IERC7858 { } function _updateTimeStamp(uint256 tokenId, uint256 start, uint256 end) internal { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); if (start >= end) { revert ERC7858InvalidTimeStamp(start, end); } @@ -58,7 +58,7 @@ abstract contract ERC7858EXPBase is ERC721, ERC721Enumerable, IERC7858 { } function _clearTimeStamp(uint256 tokenId) internal { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); delete _tokensTimeStamp[tokenId]; } @@ -66,7 +66,7 @@ abstract contract ERC7858EXPBase is ERC721, ERC721Enumerable, IERC7858 { * @dev See {IERC7858-startTime}. */ function startTime(uint256 tokenId) public view virtual override returns (uint256) { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); return _tokensTimeStamp[tokenId].start; } @@ -74,7 +74,7 @@ abstract contract ERC7858EXPBase is ERC721, ERC721Enumerable, IERC7858 { * @dev See {IERC7858-endTime}. */ function endTime(uint256 tokenId) public view virtual override returns (uint256) { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); return _tokensTimeStamp[tokenId].end; } diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index d80e7849..2a7439a6 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -408,19 +408,19 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I /// @dev See {IERC7858-startTime}. function startTime(uint256 tokenId) external view returns (uint256) { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); return _tokenPointers[tokenId]; } /// @dev See {IERC7858-endTime}. function endTime(uint256 tokenId) external view returns (uint256) { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); return _tokenPointers[tokenId] + _getPointersInWindow(); } /// @dev See {IERC7858-isTokenExpired}. function isTokenExpired(uint256 tokenId) external view returns (bool) { - if (_ownerOf(tokenId) == address(0)) revert ERC721NonexistentToken(tokenId); + _requireOwned(tokenId); return _pointerProvider() >= _tokenPointers[tokenId] + _getPointersInWindow(); } From d8f46c5e7d7aeb1f5f24ad6c5260bb5ca1c5cee9 Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 5 Mar 2025 18:29:28 +0700 Subject: [PATCH 52/53] =?UTF-8?q?perf:=20=E2=9A=A1=EF=B8=8F=20optimize=20u?= =?UTF-8?q?pdate=20fucn=20erc7858?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index 2a7439a6..112bc363 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -263,20 +263,19 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I } function _update(address to, uint256 tokenId, address auth) internal virtual returns (address) { - uint256 pointer = _pointerProvider(); // current block or timestamp uint256 tokenPointer = _tokenPointers[tokenId]; + uint256 pointer = tokenPointer; address from = _ownerOf(tokenId); // if the tokenId is not exist before minting it if (to == address(0)) { _tokenPointers[tokenId] = 0; } if (tokenPointer == 0) { + pointer = _pointerProvider(); // current block or timestamp tokenPointer = pointer; _tokenPointers[tokenId] = pointer; emit TokenExpiryUpdated(tokenId, pointer, pointer + _getPointersInWindow()); - } else { - pointer = tokenPointer; } uint256 epoch = _getEpoch(pointer); From c68007b8fc8c22e3c1cedc249f94c190e63f3bbb Mon Sep 17 00:00:00 2001 From: MASDXI Date: Wed, 5 Mar 2025 21:05:15 +0700 Subject: [PATCH 53/53] =?UTF-8?q?fix:=20=F0=9F=90=9B=20case=20next=20eleme?= =?UTF-8?q?nt=20infinity=20loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/tokens/ERC20/ERC20EXPBase.sol | 3 +++ contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol | 3 +++ test/tokens/ERC20/ERC7818Behavior/interface.test.ts | 6 +++--- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/contracts/tokens/ERC20/ERC20EXPBase.sol b/contracts/tokens/ERC20/ERC20EXPBase.sol index e4d9a6e2..b88dab0f 100644 --- a/contracts/tokens/ERC20/ERC20EXPBase.sol +++ b/contracts/tokens/ERC20/ERC20EXPBase.sol @@ -241,6 +241,9 @@ abstract contract ERC20EXPBase is Context, IERC20Errors, IERC20Metadata, IERC781 element = list.front(); unchecked { while (pointer - element >= duration) { + if (element == 0) { + break; + } element = list.next(element); } } diff --git a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol index 112bc363..596f5a7b 100644 --- a/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol +++ b/contracts/tokens/ERC721/extensions/ERC7858EpochBase.sol @@ -96,6 +96,9 @@ abstract contract ERC7858EpochBase is Context, ERC165, IERC721, IERC721Errors, I element = list.front(); unchecked { while (pointer - element >= duration) { + if (element == 0) { + break; + } element = list.next(element); } } diff --git a/test/tokens/ERC20/ERC7818Behavior/interface.test.ts b/test/tokens/ERC20/ERC7818Behavior/interface.test.ts index 5e5035af..b798e3f2 100644 --- a/test/tokens/ERC20/ERC7818Behavior/interface.test.ts +++ b/test/tokens/ERC20/ERC7818Behavior/interface.test.ts @@ -37,10 +37,10 @@ export const run = async ({epochType = constants.EPOCH_TYPE.BLOCKS_BASED}) => { const epoch = await erc20exp.currentEpoch(); expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(0); await erc20exp.mint(alice.address, 1); - await erc20exp.mint(alice.address, 1); - expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(2); - await hardhat_increasePointerTo(epochType, (await erc20exp.epochLength()) * (await erc20exp.validityDuration()) - 1n); + // await erc20exp.mint(alice.address, 1); expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(1); + await hardhat_increasePointerTo(epochType, (await erc20exp.epochLength()) * (await erc20exp.validityDuration())); + expect(await erc20exp.balanceOfAtEpoch(epoch, alice.address)).to.equal(0); }); it("[SUCCESS] currentEpoch", async function () {