diff --git a/contracts/mocks/ERC1155Mock.sol b/contracts/mocks/ERC1155Mock.sol index b6f40cd6517..5b66cfe8e71 100644 --- a/contracts/mocks/ERC1155Mock.sol +++ b/contracts/mocks/ERC1155Mock.sol @@ -9,6 +9,14 @@ import "../token/ERC1155/ERC1155.sol"; * This mock just publicizes internal functions for testing purposes */ contract ERC1155Mock is ERC1155 { + constructor (string memory uri) public ERC1155(uri) { + // solhint-disable-previous-line no-empty-blocks + } + + function setURI(string memory newuri) public { + _setURI(newuri); + } + function mint(address to, uint256 id, uint256 value, bytes memory data) public { _mint(to, id, value, data); } diff --git a/contracts/token/ERC1155/ERC1155.sol b/contracts/token/ERC1155/ERC1155.sol index 47cc301dd5a..e28b8cb1031 100644 --- a/contracts/token/ERC1155/ERC1155.sol +++ b/contracts/token/ERC1155/ERC1155.sol @@ -3,6 +3,7 @@ pragma solidity ^0.6.0; import "./IERC1155.sol"; +import "./IERC1155MetadataURI.sol"; import "./IERC1155Receiver.sol"; import "../../math/SafeMath.sol"; import "../../utils/Address.sol"; @@ -15,8 +16,7 @@ import "../../introspection/ERC165.sol"; * See https://eips.ethereum.org/EIPS/eip-1155 * Originally based on code by Enjin: https://github.com/enjin/erc-1155 */ -contract ERC1155 is ERC165, IERC1155 -{ +contract ERC1155 is ERC165, IERC1155, IERC1155MetadataURI { using SafeMath for uint256; using Address for address; @@ -26,6 +26,9 @@ contract ERC1155 is ERC165, IERC1155 // Mapping from account to operator approvals mapping (address => mapping(address => bool)) private _operatorApprovals; + // Used as the URI for all token types by relying on ID substition, e.g. https://token-cdn-domain/{id}.json + string private _uri; + /* * bytes4(keccak256('balanceOf(address,uint256)')) == 0x00fdd58e * bytes4(keccak256('balanceOfBatch(address[],uint256[])')) == 0x4e1273f4 @@ -39,9 +42,36 @@ contract ERC1155 is ERC165, IERC1155 */ bytes4 private constant _INTERFACE_ID_ERC1155 = 0xd9b67a26; - constructor() public { + /* + * bytes4(keccak256('uri(uint256)')) == 0x0e89341c + */ + bytes4 private constant _INTERFACE_ID_ERC1155_METADATA_URI = 0x0e89341c; + + /** + * @dev See {_setURI}. + */ + constructor (string memory uri) public { + _setURI(uri); + // register the supported interfaces to conform to ERC1155 via ERC165 _registerInterface(_INTERFACE_ID_ERC1155); + + // register the supported interfaces to conform to ERC1155MetadataURI via ERC165 + _registerInterface(_INTERFACE_ID_ERC1155_METADATA_URI); + } + + /** + * @dev See {IERC1155MetadataURI-uri}. + * + * This implementation returns the same URI for *all* token types. It relies + * on the token type ID substituion mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * Clients calling this function must replace the `{id}` substring with the + * actual token type ID. + */ + function uri(uint256) external view override returns (string memory) { + return _uri; } /** @@ -195,6 +225,29 @@ contract ERC1155 is ERC165, IERC1155 _doSafeBatchTransferAcceptanceCheck(msg.sender, from, to, ids, values, data); } + /** + * @dev Sets a new URI for all token types, by relying on the token type ID + * substituion mechanism + * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP]. + * + * By this mechanism, any occurence of the `{id}` substring in either the + * URI or any of the values in the JSON file at said URI will be replaced by + * clients with the token type ID. + * + * For example, the `https://token-cdn-domain/{id}.json` URI would be + * interpreted by clients as + * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json` + * for token type ID 0x4cce0. + * + * See {uri}. + * + * Because these URIs cannot be meaningfully represented by the {URI} event, + * this function emits no events. + */ + function _setURI(string memory newuri) internal virtual { + _uri = newuri; + } + /** * @dev Internal function to mint an amount of a token with the given ID * @param to The address that will own the minted token diff --git a/contracts/token/ERC1155/IERC1155.sol b/contracts/token/ERC1155/IERC1155.sol index 0b76cf06463..07c7f169384 100644 --- a/contracts/token/ERC1155/IERC1155.sol +++ b/contracts/token/ERC1155/IERC1155.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.6.0; +pragma solidity ^0.6.2; import "../../introspection/IERC165.sol"; @@ -8,7 +8,7 @@ import "../../introspection/IERC165.sol"; @title ERC-1155 Multi Token Standard basic interface @dev See https://eips.ethereum.org/EIPS/eip-1155 */ -abstract contract IERC1155 is IERC165 { +interface IERC1155 is IERC165 { event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value); event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values); @@ -17,15 +17,15 @@ abstract contract IERC1155 is IERC165 { event URI(string value, uint256 indexed id); - function balanceOf(address account, uint256 id) public view virtual returns (uint256); + function balanceOf(address account, uint256 id) external view returns (uint256); - function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view virtual returns (uint256[] memory); + function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory); - function setApprovalForAll(address operator, bool approved) external virtual; + function setApprovalForAll(address operator, bool approved) external; - function isApprovedForAll(address account, address operator) external view virtual returns (bool); + function isApprovedForAll(address account, address operator) external view returns (bool); - function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external virtual; + function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external; - function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external virtual; + function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external; } diff --git a/contracts/token/ERC1155/IERC1155MetadataURI.sol b/contracts/token/ERC1155/IERC1155MetadataURI.sol new file mode 100644 index 00000000000..aff9ac96add --- /dev/null +++ b/contracts/token/ERC1155/IERC1155MetadataURI.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.6.2; + +import "./IERC1155.sol"; + +/** + * @dev Interface of the optional ERC1155MetadataExtension interface, as defined + * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP]. + */ +interface IERC1155MetadataURI is IERC1155 { + function uri(uint256 id) external view returns (string memory); +} diff --git a/contracts/token/ERC1155/README.adoc b/contracts/token/ERC1155/README.adoc new file mode 100644 index 00000000000..4a35f19b4cd --- /dev/null +++ b/contracts/token/ERC1155/README.adoc @@ -0,0 +1,21 @@ += ERC 1155 + +This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-1155[ERC1155 Multi Token Standard]. + +The EIP consists of three interfaces which fulfill different roles, found here as `IERC1155`, `IERC1155MetadataURI` and `IERC1155Receiver`. + +`ERC1155` implement the mandatory `IERC1155` interface, as well as the optional extension `IERC1155MetadataURI` by relying on the substition mechanism to use the same URI for all token types, dramatically reducing gas costs. + +`ERC1155Holder` implements the `IERC1155Receiver` interface for contracts that can receive (and hold) ERC1155 tokens. + +== Core + +{{IERC1155}} + +{{IERC1155MetadataURI}} + +{{ERC1155}} + +{{IERC1155Receiver}} + +{{ERC1155Holder}} diff --git a/contracts/token/ERC1155/README.md b/contracts/token/ERC1155/README.md deleted file mode 100644 index c729f69688d..00000000000 --- a/contracts/token/ERC1155/README.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -sections: - - title: Core - contracts: - - IERC1155 - - ERC1155 - - IERC1155Receiver ---- - -This set of interfaces and contracts are all related to the [ERC1155 Multi Token Standard](https://eips.ethereum.org/EIPS/eip-1155). - -The EIP consists of two interfaces which fulfill different roles, found here as `IERC1155` and `IERC1155Receiver`. Only `IERC1155` is required for a contract to be ERC1155 compliant. The basic functionality is implemented in `ERC1155`. diff --git a/test/token/ERC1155/ERC1155.test.js b/test/token/ERC1155/ERC1155.test.js index 343e3d50c88..0debd971296 100644 --- a/test/token/ERC1155/ERC1155.test.js +++ b/test/token/ERC1155/ERC1155.test.js @@ -11,8 +11,10 @@ const ERC1155Mock = contract.fromArtifact('ERC1155Mock'); describe('ERC1155', function () { const [creator, tokenHolder, tokenBatchHolder, ...otherAccounts] = accounts; + const initialURI = 'https://token-cdn-domain/{id}.json'; + beforeEach(async function () { - this.token = await ERC1155Mock.new({ from: creator }); + this.token = await ERC1155Mock.new(initialURI, { from: creator }); }); shouldBehaveLikeERC1155(otherAccounts); @@ -216,4 +218,35 @@ describe('ERC1155', function () { }); }); }); + + describe('ERC1155MetadataURI', function () { + const firstTokenID = new BN('42'); + const secondTokenID = new BN('1337'); + + it('emits no URI event in constructor', async function () { + await expectEvent.notEmitted.inConstruction(this.token, 'URI'); + }); + + it('sets the initial URI for all token types', async function () { + expect(await this.token.uri(firstTokenID)).to.be.equal(initialURI); + expect(await this.token.uri(secondTokenID)).to.be.equal(initialURI); + }); + + describe('_setURI', function () { + const newURI = 'https://token-cdn-domain/{locale}/{id}.json'; + + it('emits no URI event', async function () { + const receipt = await this.token.setURI(newURI); + + expectEvent.notEmitted(receipt, 'URI'); + }); + + it('sets the new URI for all token types', async function () { + await this.token.setURI(newURI); + + expect(await this.token.uri(firstTokenID)).to.be.equal(newURI); + expect(await this.token.uri(secondTokenID)).to.be.equal(newURI); + }); + }); + }); }); diff --git a/test/token/ERC1155/ERC1155Holder.test.js b/test/token/ERC1155/ERC1155Holder.test.js index eb1c6715ada..912abcc1b51 100644 --- a/test/token/ERC1155/ERC1155Holder.test.js +++ b/test/token/ERC1155/ERC1155Holder.test.js @@ -10,7 +10,9 @@ describe('ERC1155Holder', function () { const [creator] = accounts; it('receives ERC1155 tokens', async function () { - const multiToken = await ERC1155Mock.new({ from: creator }); + const uri = 'https://token-cdn-domain/{id}.json'; + + const multiToken = await ERC1155Mock.new(uri, { from: creator }); const multiTokenIds = [new BN(1), new BN(2), new BN(3)]; const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)]; await multiToken.mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x', { from: creator });