From 1ab9da3422b89d1f8a0d6ca23ec9cc6fc0bd791f Mon Sep 17 00:00:00 2001 From: Matej Poklemba Date: Tue, 9 Apr 2024 16:25:35 +0200 Subject: [PATCH] Fix mint prevention issue (#340) User could mint `_totalSupplyAll - type(uint256).max` amount of tokens, which brings the `_totalSupplyAll` to its maximum value, preventing any further mints because of overflow error. --- .../token/InverseAppProjected1155.sol | 39 +++++++++++++++++-- .../token/InverseBaseProjected1155.sol | 38 ++++++++++++++++-- 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/packages/contracts/evm-contracts/contracts/token/InverseAppProjected1155.sol b/packages/contracts/evm-contracts/contracts/token/InverseAppProjected1155.sol index b2298802c..9477160bf 100644 --- a/packages/contracts/evm-contracts/contracts/token/InverseAppProjected1155.sol +++ b/packages/contracts/evm-contracts/contracts/token/InverseAppProjected1155.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.13; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; @@ -18,7 +17,7 @@ struct MintEntry { /// @dev A Paima Inverse Projection ERC1155 token where initialization is handled by the app-layer. /// A standard ERC1155 that can be freely minted and stores an unique `` pair (used in `tokenURI`) when minted. -contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Ownable { +contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155, Ownable { using Strings for uint256; string public name; @@ -27,6 +26,7 @@ contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Own mapping(uint256 id => MintEntry) public tokenToMint; mapping(address minter => uint256) public mintCount; mapping(uint256 id => uint256) private _initialSupply; + mapping(uint256 id => uint256) private _totalSupply; /// @dev The token ID that will be minted when calling the `mint` function. uint256 public currentTokenId; @@ -72,6 +72,11 @@ contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Own return _initialSupply[id]; } + /// @dev Returns the total amount of tokens with ID `id` that currently exists. + function totalSupply(uint256 id) public view virtual returns (uint256) { + return _totalSupply[id]; + } + /// @dev Mints `value` of a new token to the transaction sender. /// Increases the `currentTokenId`. /// Reverts if transaction sender is a smart contract that does not implement IERC1155Receiver-onERC1155Received. @@ -132,7 +137,7 @@ contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Own uint256 id, string memory customBaseUri ) public view virtual returns (string memory) { - require(exists(id), "InverseAppProjected1155: URI query for nonexistent token"); + require(_totalSupply[id] > 0, "InverseAppProjected1155: URI query for nonexistent token"); MintEntry memory entry = tokenToMint[id]; string memory URI = bytes(customBaseUri).length > 0 ? string.concat( @@ -144,7 +149,7 @@ contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Own "/", entry.userTokenId.toString(), "/", - initialSupply(id).toString() + _initialSupply[id].toString() ) : ""; return string(abi.encodePacked(URI, baseExtension)); @@ -183,4 +188,30 @@ contract InverseAppProjected1155 is IInverseAppProjected1155, ERC1155Supply, Own function updateMetadata(uint256 _tokenId) public virtual { emit MetadataUpdate(_tokenId); } + + /// @dev See {ERC1155-_update}. + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override { + super._update(from, to, ids, values); + + if (from == address(0)) { + for (uint256 i = 0; i < ids.length; ++i) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply[ids[i]] += values[i]; + } + } + + if (to == address(0)) { + for (uint256 i = 0; i < ids.length; ++i) { + unchecked { + // Overflow not possible: values[i] <= balanceOf(from, ids[i]) <= totalSupply(ids[i]) + _totalSupply[ids[i]] -= values[i]; + } + } + } + } } diff --git a/packages/contracts/evm-contracts/contracts/token/InverseBaseProjected1155.sol b/packages/contracts/evm-contracts/contracts/token/InverseBaseProjected1155.sol index e52ce34e2..10c1ac7b7 100644 --- a/packages/contracts/evm-contracts/contracts/token/InverseBaseProjected1155.sol +++ b/packages/contracts/evm-contracts/contracts/token/InverseBaseProjected1155.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.13; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; -import {ERC1155Supply} from "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Supply.sol"; import {IERC1155MetadataURI} from "@openzeppelin/contracts/token/ERC1155/extensions/IERC1155MetadataURI.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; @@ -13,7 +12,7 @@ import {IUri} from "./IUri.sol"; /// @dev A Paima Inverse Projection ERC1155 token where initialization is handled by the base-layer. /// A standard ERC1155 that accepts calldata in the mint function for any initialization data needed in a Paima dApp. -contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155Supply, Ownable { +contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155, Ownable { using Strings for uint256; string public name; @@ -26,6 +25,8 @@ contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155Supply, O /// @dev Base extension that is used in the `uri` function to form the end of the token URI. string public baseExtension; + mapping(uint256 id => uint256) private _totalSupply; + /// @dev Sets the NFT's `name`, `symbol`, and transfers ownership to `owner`. /// Also sets `currentTokenId` to 1. constructor( @@ -48,6 +49,11 @@ contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155Supply, O super.supportsInterface(interfaceId); } + /// @dev Returns the total amount of tokens with ID `id` that currently exists. + function totalSupply(uint256 id) public view virtual returns (uint256) { + return _totalSupply[id]; + } + /// @dev Mints `value` of a new token to transaction sender, passing `initialData` to be emitted in the event. /// Increases the `currentTokenId`. /// Reverts if transaction sender is a smart contract that does not implement IERC1155Receiver-onERC1155Received. @@ -94,7 +100,7 @@ contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155Supply, O uint256 id, string memory customBaseUri ) public view virtual returns (string memory) { - require(exists(id), "InverseBaseProjected1155: URI query for nonexistent token"); + require(_totalSupply[id] > 0, "InverseBaseProjected1155: URI query for nonexistent token"); string memory URI = bytes(customBaseUri).length > 0 ? string.concat(customBaseUri, "eip155:", block.chainid.toString(), "/", id.toString()) : ""; @@ -134,4 +140,30 @@ contract InverseBaseProjected1155 is IInverseBaseProjected1155, ERC1155Supply, O function updateMetadata(uint256 _tokenId) public virtual { emit MetadataUpdate(_tokenId); } + + /// @dev See {ERC1155-_update}. + function _update( + address from, + address to, + uint256[] memory ids, + uint256[] memory values + ) internal virtual override { + super._update(from, to, ids, values); + + if (from == address(0)) { + for (uint256 i = 0; i < ids.length; ++i) { + // Overflow check required: The rest of the code assumes that totalSupply never overflows + _totalSupply[ids[i]] += values[i]; + } + } + + if (to == address(0)) { + for (uint256 i = 0; i < ids.length; ++i) { + unchecked { + // Overflow not possible: values[i] <= balanceOf(from, ids[i]) <= totalSupply(ids[i]) + _totalSupply[ids[i]] -= values[i]; + } + } + } + } }