diff --git a/contracts/src/ERC20FixedDenomination.sol b/contracts/src/ERC20FixedDenomination.sol index 059e8eb..68b395c 100644 --- a/contracts/src/ERC20FixedDenomination.sol +++ b/contracts/src/ERC20FixedDenomination.sol @@ -154,7 +154,6 @@ contract ERC20FixedDenomination is ERC404NullOwnerCappedUpgradeable { /// @notice Returns metadata URI for NFT tokens /// @dev Returns a data URI with JSON metadata fetched from the main Ethscriptions contract function tokenURI(uint256 mintId) public view virtual override returns (string memory) { - _validateTokenId(mintId); ownerOf(mintId); // reverts on invalid / nonexistent // Get the ethscriptionId for this mintId from the manager diff --git a/contracts/src/ERC20NullOwnerCappedUpgradeable.sol b/contracts/src/ERC20NullOwnerCappedUpgradeable.sol deleted file mode 100644 index 91b7cda..0000000 --- a/contracts/src/ERC20NullOwnerCappedUpgradeable.sol +++ /dev/null @@ -1,189 +0,0 @@ -// SPDX-License-Identifier: MIT -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 {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"; - -/// @title ERC20NullOwnerCappedUpgradeable -/// @notice ERC20 (Upgradeable) + Cap adapted to treat address(0) as a valid holder; single storage struct -abstract contract ERC20NullOwnerCappedUpgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors { - /// @custom:storage-location erc7201:ethscriptions.storage.ERC20NullOwnerCapped - struct TokenStorage { - mapping(address account => uint256) balances; - mapping(address account => mapping(address spender => uint256)) allowances; - uint256 totalSupply; - string name; - string symbol; - uint256 cap; - } - - // Unique storage slot for this combined ERC20 + Cap storage - // keccak256(abi.encode(uint256(keccak256("ethscriptions.storage.ERC20NullOwnerCapped")) - 1)) & ~bytes32(uint256(0xff)) - bytes32 private constant STORAGE_LOCATION = 0x4d6f413771b260e6694ffc0cfc1fc0bdb079c880580315446b9e26b778417b00; - - function _getS() private pure returns (TokenStorage storage $) { - assembly { - $.slot := STORAGE_LOCATION - } - } - - // Errors copied from OZ - error ERC20ExceededCap(uint256 increasedSupply, uint256 cap); - error ERC20InvalidCap(uint256 cap); - - // Initializers - function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing { - __ERC20_init_unchained(name_, symbol_); - } - - function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing { - TokenStorage storage $ = _getS(); - $.name = name_; - $.symbol = symbol_; - } - - function __ERC20Capped_init(uint256 cap_) internal onlyInitializing { - __ERC20Capped_init_unchained(cap_); - } - - function __ERC20Capped_init_unchained(uint256 cap_) internal onlyInitializing { - TokenStorage storage $ = _getS(); - if (cap_ == 0) { - revert ERC20InvalidCap(0); - } - $.cap = cap_; - } - - // Views - function name() public view virtual returns (string memory) { - TokenStorage storage $ = _getS(); - return $.name; - } - - function symbol() public view virtual returns (string memory) { - TokenStorage storage $ = _getS(); - return $.symbol; - } - - function decimals() public view virtual returns (uint8) { - return 18; - } - - function totalSupply() public view virtual returns (uint256) { - TokenStorage storage $ = _getS(); - return $.totalSupply; - } - - function balanceOf(address account) public view virtual returns (uint256) { - TokenStorage storage $ = _getS(); - return $.balances[account]; - } - - function allowance(address owner, address spender) public view virtual returns (uint256) { - TokenStorage storage $ = _getS(); - return $.allowances[owner][spender]; - } - - // External ERC-20 (can be overridden to restrict usage in child) - function transfer(address to, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _transfer(owner, to, value); - return true; - } - - function approve(address spender, uint256 value) public virtual returns (bool) { - address owner = _msgSender(); - _approve(owner, spender, value); - return true; - } - - function transferFrom(address from, address to, uint256 value) public virtual returns (bool) { - address spender = _msgSender(); - _spendAllowance(from, spender, value); - _transfer(from, to, value); - return true; - } - - // Internal core - function _transfer(address from, address to, uint256 value) internal { - if (from == address(0)) { - revert ERC20InvalidSender(address(0)); - } - // Allow `to == address(0)` to support null-owner semantics - _update(from, to, value); - } - - // Update balances without affecting total supply (supports from/to == address(0)) - function _update(address from, address to, uint256 value) internal virtual { - TokenStorage storage $ = _getS(); - // Debit from - uint256 fromBalance = $.balances[from]; - if (fromBalance < value) { - revert ERC20InsufficientBalance(from, fromBalance, value); - } - unchecked { - $.balances[from] = fromBalance - value; - } - // Credit to - unchecked { - $.balances[to] += value; - } - emit Transfer(from, to, value); - } - - // Mint (null-owner aware): increases totalSupply and credits recipient (can be address(0)) - function _mint(address account, uint256 value) internal { - TokenStorage storage $ = _getS(); - - uint256 newSupply = $.totalSupply + value; - if (newSupply > $.cap) { - revert ERC20ExceededCap(newSupply, $.cap); - } - - $.totalSupply = newSupply; - - unchecked { $.balances[account] += value; } - - emit Transfer(address(0), account, value); - } - - // Approvals - function _approve(address owner, address spender, uint256 value) internal { - _approve(owner, spender, value, true); - } - - function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual { - TokenStorage storage $ = _getS(); - if (owner == address(0)) { - revert ERC20InvalidApprover(address(0)); - } - if (spender == address(0)) { - revert ERC20InvalidSpender(address(0)); - } - $.allowances[owner][spender] = value; - if (emitEvent) { - emit Approval(owner, spender, value); - } - } - - function _spendAllowance(address owner, address spender, uint256 value) internal virtual { - uint256 currentAllowance = allowance(owner, spender); - if (currentAllowance < type(uint256).max) { - if (currentAllowance < value) { - revert ERC20InsufficientAllowance(spender, currentAllowance, value); - } - unchecked { - _approve(owner, spender, currentAllowance - value, false); - } - } - } - - // Cap view - function maxSupply() public view virtual returns (uint256) { - TokenStorage storage $ = _getS(); - return $.cap; - } -}