From a765b0d92c60f62de875c4c2f04508ca634587c8 Mon Sep 17 00:00:00 2001 From: Robert Kaiser Date: Tue, 14 Apr 2020 14:36:06 +0200 Subject: [PATCH 1/2] add an _exists() function to be more in line with the ERC-721 implementation and satisfy https://twitter.com/xanderatallah/status/1232124941425881089 Use an explicit registration internally to mark token IDs as existing per comments from nventuro, add an event to flag the existence of this token ID --- contracts/token/ERC1155/ERC1155.sol | 35 +++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 2e39cbb423b..91c98a71486 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -31,6 +31,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { // Used as the URI for all token types by relying on ID substition, e.g. https://token-cdn-domain/{id}.json string private _uri; + // Mapping token ID to that token being registered as existing + mapping (uint256 => bool) private _tokenExists; + /* * bytes4(keccak256('balanceOf(address,uint256)')) == 0x00fdd58e * bytes4(keccak256('balanceOfBatch(address[],uint256[])')) == 0x4e1273f4 @@ -85,6 +88,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { */ function balanceOf(address account, uint256 id) public view override returns (uint256) { require(account != address(0), "ERC1155: balance query for the zero address"); + require(_exists(id), "ERC1155: balance query for nonexistent token"); return _balances[id][account]; } @@ -229,6 +233,31 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _uri = newuri; } + /** + * @dev Register a token ID so other contract functionality knows this token + * actually exists and this ID is valid. Minting will automatically call this. + * Also emits an event to flag the existence, see the spec: + * "To broadcast the existence of a token ID with no initial balance, the contract + * SHOULD emit the TransferSingle event from 0x0 to 0x0, with the token creator as + * _operator, and a _value of 0." + * @param id uint256 ID of the token to register + */ + function _registerToken(uint256 id) internal virtual { + if (!_tokenExists[id]) { + _tokenExists[id] = true; + emit TransferSingle(msg.sender, address(0), address(0), id, 0); + } + } + + /** + * @dev Returns whether the specified token exists. Use {_registerTokenID} to set this flag. + * @param id uint256 ID of the token to query the existence of + * @return bool whether the token exists + */ + function _exists(uint256 id) internal view returns (bool) { + return _tokenExists[id]; + } + /** * @dev Creates `amount` tokens of token type `id`, and assigns them to `account`. * @@ -247,6 +276,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data); + if (!_exists(id)) { + _registerToken(id); + } _balances[id][account] = _balances[id][account].add(amount); emit TransferSingle(operator, address(0), account, id, amount); @@ -271,6 +303,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _beforeTokenTransfer(operator, address(0), to, ids, amounts, data); for (uint i = 0; i < ids.length; i++) { + if (!_exists(ids[i])) { + _registerToken(ids[i]); + } _balances[ids[i]][to] = amounts[i].add(_balances[ids[i]][to]); } From 4d530c592219b8df79dd40c4099e172daa75c6b1 Mon Sep 17 00:00:00 2001 From: Robert Kaiser Date: Wed, 29 Jul 2020 16:59:28 +0200 Subject: [PATCH 2/2] rework _exists() and _registerToken() slightly, do not require() it in other functions --- contracts/token/ERC1155/ERC1155.sol | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 91c98a71486..609cb78ac17 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -31,8 +31,8 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { // Used as the URI for all token types by relying on ID substition, e.g. https://token-cdn-domain/{id}.json string private _uri; - // Mapping token ID to that token being registered as existing - mapping (uint256 => bool) private _tokenExists; + // Mapping token ID to that token being registered as existing (1 for existing, 0 for not existing) + mapping (uint256 => uint256) private _tokenExists; /* * bytes4(keccak256('balanceOf(address,uint256)')) == 0x00fdd58e @@ -88,7 +88,6 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { */ function balanceOf(address account, uint256 id) public view override returns (uint256) { require(account != address(0), "ERC1155: balance query for the zero address"); - require(_exists(id), "ERC1155: balance query for nonexistent token"); return _balances[id][account]; } @@ -234,28 +233,29 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { } /** - * @dev Register a token ID so other contract functionality knows this token - * actually exists and this ID is valid. Minting will automatically call this. - * Also emits an event to flag the existence, see the spec: + * @dev Register an `id` so other contract functionality knows this token actually + * exists and this ID is valid. Minting will automatically call this. + * If `sendEvent` is true, emits an event to flag the existence, see the spec: * "To broadcast the existence of a token ID with no initial balance, the contract * SHOULD emit the TransferSingle event from 0x0 to 0x0, with the token creator as * _operator, and a _value of 0." - * @param id uint256 ID of the token to register + * Minting will call this without sending an event as a similar event is sent for + * the minting itself already. */ - function _registerToken(uint256 id) internal virtual { - if (!_tokenExists[id]) { - _tokenExists[id] = true; - emit TransferSingle(msg.sender, address(0), address(0), id, 0); + function _registerToken(uint256 id, bool sendEvent) internal virtual { + if (_tokenExists[id] == 0) { + _tokenExists[id] = 1; + if (sendEvent) { + emit TransferSingle(msg.sender, address(0), address(0), id, 0); + } } } /** - * @dev Returns whether the specified token exists. Use {_registerTokenID} to set this flag. - * @param id uint256 ID of the token to query the existence of - * @return bool whether the token exists + * @dev Returns whether the token `id` exists. Use {_registerToken} to set this flag. */ function _exists(uint256 id) internal view returns (bool) { - return _tokenExists[id]; + return _tokenExists[id] != 0; } /** @@ -277,8 +277,9 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { _beforeTokenTransfer(operator, address(0), account, _asSingletonArray(id), _asSingletonArray(amount), data); if (!_exists(id)) { - _registerToken(id); + _registerToken(id, false); } + _balances[id][account] = _balances[id][account].add(amount); emit TransferSingle(operator, address(0), account, id, amount); @@ -304,7 +305,7 @@ contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI { for (uint i = 0; i < ids.length; i++) { if (!_exists(ids[i])) { - _registerToken(ids[i]); + _registerToken(ids[i], false); } _balances[ids[i]][to] = amounts[i].add(_balances[ids[i]][to]); }