Skip to content

Commit

Permalink
Fix mint prevention issue (#340)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
matejos authored Apr 9, 2024
1 parent 6c0af96 commit 1ab9da3
Show file tree
Hide file tree
Showing 2 changed files with 70 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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 `<minter, userTokenId>` 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;
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand All @@ -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));
Expand Down Expand Up @@ -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];
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;
Expand All @@ -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(
Expand All @@ -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.
Expand Down Expand Up @@ -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())
: "";
Expand Down Expand Up @@ -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];
}
}
}
}
}

0 comments on commit 1ab9da3

Please sign in to comment.