-
Notifications
You must be signed in to change notification settings - Fork 25
Refactor ERC404NullOwnerCappedUpgradeable contract by removing unused… #150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,13 +3,10 @@ pragma solidity 0.8.24; | |||||
|
|
||||||
| import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||||||
| import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||||||
| import {IERC721Receiver} from "@openzeppelin/contracts/interfaces/IERC721Receiver.sol"; | ||||||
| import {ContextUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; | ||||||
| import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol"; | ||||||
| import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; | ||||||
| import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol"; | ||||||
| import "./interfaces/IERC404.sol"; | ||||||
| import "./lib/DoubleEndedQueue.sol"; | ||||||
|
|
||||||
| /// @title ERC404NullOwnerCappedUpgradeable | ||||||
| /// @notice Hybrid ERC20/ERC721 implementation with null owner support, supply cap, and upgradeability | ||||||
|
|
@@ -20,11 +17,8 @@ abstract contract ERC404NullOwnerCappedUpgradeable is | |||||
| IERC165, | ||||||
| IERC20, | ||||||
| IERC20Metadata, | ||||||
| IERC20Errors, | ||||||
| IERC404 | ||||||
| IERC20Errors | ||||||
| { | ||||||
| using DoubleEndedQueue for DoubleEndedQueue.Uint256Deque; | ||||||
|
|
||||||
| struct TokenData { | ||||||
| address owner; // current owner (can be address(0) for null-owner) | ||||||
| uint88 index; // position in owned[owner] array | ||||||
|
|
@@ -43,44 +37,26 @@ abstract contract ERC404NullOwnerCappedUpgradeable is | |||||
| uint256 totalSupply; | ||||||
| uint256 cap; | ||||||
|
|
||||||
| // === ERC404 NFT State === | ||||||
| DoubleEndedQueue.Uint256Deque storedERC721Ids; | ||||||
| mapping(address => uint256[]) owned; | ||||||
| mapping(uint256 => TokenData) tokens; | ||||||
| mapping(uint256 => address) getApproved; | ||||||
| mapping(address => mapping(address => bool)) isApprovedForAll; | ||||||
| mapping(address => bool) erc721TransferExempt; | ||||||
| uint256 minted; // Number of NFTs minted | ||||||
| uint256 units; // Units for NFT minting (e.g., 1000 * 10^18) | ||||||
| uint256 initialChainId; | ||||||
| bytes32 initialDomainSeparator; | ||||||
| mapping(address => uint256) nonces; | ||||||
|
|
||||||
| // === Metadata === | ||||||
| string name; | ||||||
| string symbol; | ||||||
| } | ||||||
|
|
||||||
| // ============================================================= | ||||||
| // CONSTANTS | ||||||
| // ============================================================= | ||||||
|
|
||||||
| /// @dev Unique storage slot for EIP-7201 namespaced storage | ||||||
| /// keccak256(abi.encode(uint256(keccak256("ethscriptions.storage.ERC404NullOwnerCapped")) - 1)) & ~bytes32(uint256(0xff)) | ||||||
| bytes32 private constant STORAGE_LOCATION = 0x8a0c9d8e5f7b3a2c1d4e6f8a9b7c5d3e2f1a4b6c8d9e7f5a3b2c1d4e6f8a9b00; | ||||||
|
|
||||||
|
|
||||||
| // ============================================================= | ||||||
| // EVENTS | ||||||
| // ============================================================= | ||||||
|
|
||||||
| // ERC20 Events are inherited from IERC20 (Transfer, Approval) | ||||||
|
|
||||||
| // ERC721 Events (using different names to avoid conflicts with ERC20) | ||||||
| // event Transfer(address indexed from, address indexed to, uint256 value); | ||||||
| event ERC20Transfer(address indexed from, address indexed to, uint256 value); | ||||||
| event ERC721Transfer(address indexed from, address indexed to, uint256 indexed id); | ||||||
| event ERC721Approval(address indexed owner, address indexed spender, uint256 indexed id); | ||||||
| event ApprovalForAll(address indexed owner, address indexed operator, bool approved); | ||||||
|
|
||||||
| // ============================================================= | ||||||
| // CUSTOM ERRORS | ||||||
|
|
@@ -91,14 +67,22 @@ abstract contract ERC404NullOwnerCappedUpgradeable is | |||||
| error ERC20InvalidCap(uint256 cap); | ||||||
| error InvalidUnits(uint256 units); | ||||||
| error NotImplemented(); | ||||||
| error NotFound(); | ||||||
| error InvalidTokenId(); | ||||||
| error AlreadyExists(); | ||||||
| error InvalidRecipient(); | ||||||
| error Unauthorized(); | ||||||
| error OwnedIndexOverflow(); | ||||||
|
|
||||||
| // ============================================================= | ||||||
| // STORAGE ACCESSOR | ||||||
| // ============================================================= | ||||||
|
|
||||||
| function _getS() internal pure returns (TokenStorage storage $) { | ||||||
| bytes32 slot = keccak256("ethscriptions.storage.ERC404NullOwnerCapped"); | ||||||
|
||||||
| bytes32 slot = keccak256("ethscriptions.storage.ERC404NullOwnerCapped"); | |
| bytes32 slot = keccak256(abi.encode(uint256(keccak256("ethscriptions.storage.ERC404NullOwnerCapped")) - 1)) & ~bytes32(uint256(0xff)); |
Copilot
AI
Nov 25, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected. The line contains only spaces, which violates code formatting standards.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug: Storage slot calculation breaks EIP-7201 compatibility for upgrades
The _getS() function now computes the storage slot using simple keccak256("ethscriptions.storage.ERC404NullOwnerCapped"), but the original code used the proper EIP-7201 formula: keccak256(abi.encode(uint256(keccak256(id)) - 1)) & ~bytes32(uint256(0xff)) which produced 0x8a0c9d8e5f7b3a2c1d4e6f8a9b7c5d3e2f1a4b6c8d9e7f5a3b2c1d4e6f8a9b00. These produce completely different storage slots. For this upgradeable contract, any upgrade would cause the contract to read/write to a different storage location, making all existing state data (balances, tokens, ownership) completely inaccessible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trailing whitespace detected. The line contains only spaces after the closing brace, which violates code formatting standards.