diff --git a/contracts/helpers/ExampleERC721.sol b/contracts/helpers/ExampleERC721.sol index e6982c2..ccd86ab 100644 --- a/contracts/helpers/ExampleERC721.sol +++ b/contracts/helpers/ExampleERC721.sol @@ -6,11 +6,11 @@ import "../package/tokens/extensions/Minter.sol"; import "../package/tokens/extensions/ParentContracts.sol"; import "../package/tokens/extensions/SharedHolders.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; -import "../package/tokens/OptimizedEnumerable.sol"; +import "../package/tokens/ERC721OptimizedEnumerable.sol"; import "../package/royalty/ERC2981RoyaltyFull.sol"; -contract ExampleERC721 is Ownable, ERC2981RoyaltyFull, Minter, SharedHolders, ParentContracts, OptimizedEnumerable { - constructor(string memory name, string memory symbol) OptimizedEnumerable(name, symbol) Ownable(msg.sender) {} +contract ExampleERC721 is Ownable, ERC2981RoyaltyFull, Minter, SharedHolders, ParentContracts, ERC721OptimizedEnumerable { + constructor(string memory name, string memory symbol) ERC721OptimizedEnumerable(name, symbol) Ownable(msg.sender) {} function _authorizeAddParent(address newContract) internal override onlyOwner {} @@ -22,9 +22,16 @@ contract ExampleERC721 is Ownable, ERC2981RoyaltyFull, Minter, SharedHolders, Pa address to, uint256 tokenId, address auth - ) internal override(Minter, OptimizedEnumerable) returns (address) { + ) internal override(Minter, ERC721OptimizedEnumerable) returns (address) { Minter._update(to, tokenId, auth); - return OptimizedEnumerable._update(to, tokenId, auth); + return ERC721OptimizedEnumerable._update(to, tokenId, auth); + } + + function _exists(uint256 tokenId) internal view virtual override returns (bool){ + if (_ownerOf(tokenId) == address(0)){ + return false; + } + return true; } function _ownerOf( @@ -33,12 +40,8 @@ contract ExampleERC721 is Ownable, ERC2981RoyaltyFull, Minter, SharedHolders, Pa return ERC721._ownerOf(tokenId); } - function _exists(uint256 tokenId) internal view override(ERC2981RoyaltyFull, OptimizedEnumerable) returns (bool) { - return OptimizedEnumerable._exists(tokenId); - } - function mint(address to) public virtual { - OptimizedEnumerable._mint(to); + ERC721OptimizedEnumerable._mint(to); } function burn(uint256 tokenId) public virtual { @@ -52,8 +55,8 @@ contract ExampleERC721 is Ownable, ERC2981RoyaltyFull, Minter, SharedHolders, Pa function supportsInterface( bytes4 interfaceId - ) public view virtual override(OptimizedEnumerable, DerivedERC2981Royalty) returns (bool) { + ) public view virtual override(ERC721OptimizedEnumerable, DerivedERC2981Royalty) returns (bool) { return - OptimizedEnumerable.supportsInterface(interfaceId) || DerivedERC2981Royalty.supportsInterface(interfaceId); + ERC721OptimizedEnumerable.supportsInterface(interfaceId) || DerivedERC2981Royalty.supportsInterface(interfaceId); } } diff --git a/contracts/package/ERC/IERC5169.sol b/contracts/package/ERC/IERC5169.sol index b56838b..2134884 100644 --- a/contracts/package/ERC/IERC5169.sol +++ b/contracts/package/ERC/IERC5169.sol @@ -14,6 +14,6 @@ interface IERC5169 { function scriptURI() external view returns (string[] memory); /// @notice Update the scriptURI - /// emits event ScriptUpdate(scriptURI memory newScriptURI); + /// emits event ScriptUpdate(string[]) function setScriptURI(string[] memory) external; } diff --git a/contracts/package/README.md b/contracts/package/README.md new file mode 100644 index 0000000..25f1056 --- /dev/null +++ b/contracts/package/README.md @@ -0,0 +1,7 @@ +## Changes + +### v2.5.0 +ERC721OptimizedEnumerable + ERC721OptimizedEnumerableUpgradeable refactored for Openzeppelin v5.x + +### v2.4.0 +Compatability with Openzeppelin v5.x \ No newline at end of file diff --git a/contracts/package/package.json b/contracts/package/package.json index f910d96..85ab34d 100644 --- a/contracts/package/package.json +++ b/contracts/package/package.json @@ -1,6 +1,6 @@ { "name": "stl-contracts", - "version": "2.4.0", + "version": "2.5.1", "description": "STL contracts", "repository": { "type": "git", diff --git a/contracts/package/tokens/OptimizedEnumerableBase.sol b/contracts/package/tokens/ERC721OptimizedEnumerable.sol similarity index 50% rename from contracts/package/tokens/OptimizedEnumerableBase.sol rename to contracts/package/tokens/ERC721OptimizedEnumerable.sol index c09ce5a..5bfbd63 100644 --- a/contracts/package/tokens/OptimizedEnumerableBase.sol +++ b/contracts/package/tokens/ERC721OptimizedEnumerable.sol @@ -2,52 +2,70 @@ pragma solidity ^0.8.20; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; -import "@openzeppelin/contracts/interfaces/IERC165.sol"; +// import "./OptimizedEnumerableBase.sol"; -// disable "is IERC721Enumerable" to avoid multiple methods override for OptimizedEnumerableUpgradeable -abstract contract OptimizedEnumerableBase is IERC165 { +contract ERC721OptimizedEnumerable is ERC721 { uint private _tokenIdCounter; - // count burnt token number to calc totalSupply() uint256 private _burnt; - //slither-disable-next-line dead-code - function _update(address to, uint256, address) internal virtual returns (address) { + error IndexOutOfBounds(); + error ZeroAddressCantBeOwner(); + + function _update(address to, uint256 tokenId, address from) internal virtual override returns (address) { if (to == address(0)) { _burnt++; - } - return address(0); + } + return ERC721._update(to, tokenId, from); } - function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) { - return interfaceId == type(IERC721Enumerable).interfaceId || interfaceId == type(IERC165).interfaceId; + constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} + + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return + interfaceId == type(IERC721Enumerable).interfaceId + || super.supportsInterface(interfaceId); } - function balanceOf(address owner) public view virtual returns (uint256 balance); + function _mint(address to) internal { + uint newTokenId = _prepareTokenId(); + ERC721._mint(to, newTokenId); + } - function ownerOf(uint256 tokenId) public view virtual returns (address owner); + function getNextTokenId() internal view returns (uint){ + return _tokenIdCounter; + } - function _exists(uint256 tokenId) internal view virtual returns (bool); + function _prepareTokenId() internal returns (uint) { + return _tokenIdCounter++; + } /** * Foreach all minted tokens until reached appropriate index */ function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) { - require(index < balanceOf(owner), "Owner index out of bounds"); - uint256 numMinted = _tokenIdCounter; + if (index >= balanceOf(owner)){ + revert IndexOutOfBounds(); + } + + if (owner == address(0)){ + revert ZeroAddressCantBeOwner(); + } + + uint256 numMinted = getNextTokenId(); uint256 tokenIdsIdx = 0; - // Counter overflow is impossible as the loop breaks when uint256 i is equal to another uint256 numMintedSoFar. unchecked { - for (uint256 i = 0; i < numMinted; i++) { - if (_exists(i) && (ownerOf(i) == owner)) { + for (uint256 i = 0; i < numMinted; i++) { + if (_ownerOf(i) == owner) { if (tokenIdsIdx == index) { return i; } - tokenIdsIdx = tokenIdsIdx + 1; + tokenIdsIdx++; } } } @@ -59,23 +77,24 @@ abstract contract OptimizedEnumerableBase is IERC165 { } function totalSupply() public view virtual returns (uint256) { - return _tokenIdCounter - _burnt; + return getNextTokenId() - _burnt; } /** * @dev See {IERC721Enumerable-tokenByIndex}. */ function tokenByIndex(uint256 index) public view virtual returns (uint256) { - uint256 numMintedSoFar = _tokenIdCounter; + uint256 numMintedSoFar = getNextTokenId(); - require(index < totalSupply(), "Index out of bounds"); + if (index >= totalSupply()){ + revert IndexOutOfBounds(); + } uint256 tokenIdsIdx = 0; - // Counter overflow is impossible as the loop breaks when uint256 i is equal to another uint256 numMintedSoFar. unchecked { for (uint256 i = 0; i < numMintedSoFar; i++) { - if (_exists(i)) { + if (_ownerOf(i) != address(0)) { if (tokenIdsIdx == index) { return i; } @@ -86,12 +105,9 @@ abstract contract OptimizedEnumerableBase is IERC165 { // Execution should never reach this point. assert(false); + // added to stop compiler warnings return 0; } - - function _prepareTokenId() internal returns (uint) { - uint newTokenId = _tokenIdCounter; - _tokenIdCounter++; - return newTokenId; - } } + + diff --git a/contracts/package/tokens/ERC721OptimizedEnumerableUpgradeable.sol b/contracts/package/tokens/ERC721OptimizedEnumerableUpgradeable.sol new file mode 100644 index 0000000..6334b23 --- /dev/null +++ b/contracts/package/tokens/ERC721OptimizedEnumerableUpgradeable.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol"; + +contract ERC721OptimizedEnumerableUpgradeable is IERC721Enumerable, ERC721Upgradeable { + + error IndexOutOfBounds(); + error ZeroAddressCantBeOwner(); + + struct ERC721EnumStorage { + uint _tokenIdCounter; + // count burnt token number to calc totalSupply() + uint256 _burnt; + } + + function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Upgradeable, IERC165) returns (bool) { + return + interfaceId == type(IERC721Enumerable).interfaceId + || super.supportsInterface(interfaceId); + } + + // keccak256(abi.encode(uint256(keccak256("stl.storage.ERC721OptimizedEnumerable")) - 1)) + bytes32 private constant ERC721OptimizedEnumerableLocation = 0x0c12c17af20e858ae142203eca79d9fe977cde9a6d2226d7db28f4c9277f8085; + + function _getERC721EnumStorage() private pure returns (ERC721EnumStorage storage $) { + assembly { + $.slot := ERC721OptimizedEnumerableLocation + } + } + + function _update(address to, uint256 tokenId, address from) internal virtual override returns (address) { + if (to == address(0)) { + ERC721EnumStorage storage $ = _getERC721EnumStorage(); + $._burnt++; + } + return ERC721Upgradeable._update(to, tokenId, from); + } + + function getNextTokenId() internal view returns (uint){ + ERC721EnumStorage storage $ = _getERC721EnumStorage(); + return $._tokenIdCounter; + } + + function _prepareTokenId() internal returns (uint) { + ERC721EnumStorage storage $ = _getERC721EnumStorage(); + uint newTokenId = $._tokenIdCounter++; + return newTokenId; + } + + /** + * Foreach all minted tokens until reached appropriate index + */ + function tokenOfOwnerByIndex(address owner, uint256 index) public view virtual returns (uint256) { + if (index >= balanceOf(owner)){ + revert IndexOutOfBounds(); + } + + if (owner == address(0)){ + revert ZeroAddressCantBeOwner(); + } + + uint256 numMinted = getNextTokenId(); + uint256 tokenIdsIdx = 0; + + unchecked { + for (uint256 i = 0; i < numMinted; i++) { + if (_ownerOf(i) == owner) { + if (tokenIdsIdx == index) { + return i; + } + tokenIdsIdx++; + } + } + } + + // Execution should never reach this point. + assert(false); + // added to stop compiler warnings + return 0; + } + + function totalSupply() public view virtual returns (uint256) { + ERC721EnumStorage storage $ = _getERC721EnumStorage(); + return $._tokenIdCounter - $._burnt; + } + + /** + * @dev See {IERC721Enumerable-tokenByIndex}. + */ + function tokenByIndex(uint256 index) public view virtual returns (uint256) { + uint256 numMintedSoFar = getNextTokenId(); + + if (index >= totalSupply()){ + revert IndexOutOfBounds(); + } + + uint256 tokenIdsIdx = 0; + + unchecked { + for (uint256 i = 0; i < numMintedSoFar; i++) { + if (_ownerOf(i) != address(0)) { + if (tokenIdsIdx == index) { + return i; + } + tokenIdsIdx++; + } + } + } + + // Execution should never reach this point. + assert(false); + return 0; + } + + function _mint(address to) internal { + uint newTokenId = _prepareTokenId(); + ERC721Upgradeable._mint(to, newTokenId); + } +} diff --git a/contracts/package/tokens/OptimizedEnumerable.sol b/contracts/package/tokens/OptimizedEnumerable.sol deleted file mode 100644 index ee1acb5..0000000 --- a/contracts/package/tokens/OptimizedEnumerable.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "./OptimizedEnumerableBase.sol"; - -contract OptimizedEnumerable is OptimizedEnumerableBase, ERC721 { - function _update( - address to, - uint256 tokenId, - address auth - ) internal virtual override(OptimizedEnumerableBase, ERC721) returns (address) { - OptimizedEnumerableBase._update(to, tokenId, auth); - return ERC721._update( to, tokenId, auth); - } - - function balanceOf( - address owner - ) public view virtual override(OptimizedEnumerableBase, ERC721) returns (uint256 balance) { - return ERC721.balanceOf(owner); - } - - function ownerOf( - uint256 tokenId - ) public view virtual override(OptimizedEnumerableBase, ERC721) returns (address owner) { - return ERC721._ownerOf(tokenId); - } - - function _exists(uint256 tokenId) internal view virtual override returns (bool) { - return ownerOf(tokenId) != address(0); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721, OptimizedEnumerableBase) returns (bool) { - return ERC721.supportsInterface(interfaceId) || OptimizedEnumerableBase.supportsInterface(interfaceId); - } - - constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_) {} - - //slither-disable-next-line dead-code - function _mint(address to) internal { - uint newTokenId = _prepareTokenId(); - ERC721._mint(to, newTokenId); - } -} diff --git a/contracts/package/tokens/OptimizedEnumerableUpgradeable.sol b/contracts/package/tokens/OptimizedEnumerableUpgradeable.sol deleted file mode 100644 index 64f74f3..0000000 --- a/contracts/package/tokens/OptimizedEnumerableUpgradeable.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol"; -import "./OptimizedEnumerableBase.sol"; - -contract OptimizedEnumerableUpgradeable is OptimizedEnumerableBase, ERC721Upgradeable { - function _update( - address to, - uint256 tokenId, - address auth - ) internal virtual override(OptimizedEnumerableBase, ERC721Upgradeable) returns(address) { - OptimizedEnumerableBase._update(to, tokenId, auth); - return ERC721Upgradeable._update(to, tokenId, auth); - } - - function balanceOf( - address owner - ) public view virtual override(OptimizedEnumerableBase, ERC721Upgradeable) returns (uint256 balance) { - return ERC721Upgradeable.balanceOf(owner); - } - - function ownerOf( - uint256 tokenId - ) public view virtual override(OptimizedEnumerableBase, ERC721Upgradeable) returns (address owner) { - return ERC721Upgradeable.ownerOf(tokenId); - } - - function _exists(uint256 tokenId) internal view virtual override returns (bool) { - return ownerOf(tokenId) != address(0); - } - - function supportsInterface( - bytes4 interfaceId - ) public view virtual override(ERC721Upgradeable, OptimizedEnumerableBase) returns (bool) { - return - ERC721Upgradeable.supportsInterface(interfaceId) || OptimizedEnumerableBase.supportsInterface(interfaceId); - } - - //slither-disable-next-line dead-code - function _mint(address to) internal { - uint newTokenId = _prepareTokenId(); - ERC721Upgradeable._mint(to, newTokenId); - } -} diff --git a/package.json b/package.json index 61dcc05..d50981e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "stl-contracts", - "version": "2.2.0", + "version": "2.5.1", "description": "STL contracts", "scripts": { "compile": "hardhat compile", diff --git a/test/ERC721Extensions.test.ts b/test/ERC721Extensions.test.ts index 5fec1ec..3f12ccf 100644 --- a/test/ERC721Extensions.test.ts +++ b/test/ERC721Extensions.test.ts @@ -1,7 +1,7 @@ const { ethers, waffle } = require("hardhat"); import { expect } from "chai"; -const SampleERC721Json = require("../artifacts/contracts/package/tokens/OptimizedEnumerable.sol/OptimizedEnumerable.json"); +const SampleERC721Json = require("../artifacts/contracts/package/tokens/ERC721OptimizedEnumerable.sol/ERC721OptimizedEnumerable.json"); describe("ERC20 extensions", function () { async function setup() {