瀏覽代碼

Add a simple catch-all implementation of the metadata URI interface (#2029)

* Initial ERC1155 implementation with some tests (#1803)

* Initial ERC1155 implementation with some tests

* Remove mocked isERC1155TokenReceiver

* Revert reason edit nit

* Remove parameters associated with isERC1155TokenReceiver call

* Add tests for approvals and single transfers

* Add tests for transferring to contracts

* Add tests for batch transfers

* Make expectEvent.inTransaction tests async

* Renamed "owner" to "account" and "holder"

* Document unspecified balanceOfBatch reversion on zero behavior

* Ensure accounts can't set their own operator status

* Specify descriptive messages for underflow errors

* Bring SafeMath.add calls in line with OZ style

* Explicitly prevent _burn on the zero account

* Implement batch minting/burning

* Refactored operator approval check into isApprovedForAll calls

* Renamed ERC1155TokenReceiver to ERC1155Receiver

* Added ERC1155Holder

* Fix lint issues

* Migrate tests to @openzeppelin/test-environment

* port ERC1155 to Solidity 0.6

* make ERC1155 constructor more similar to ERC721 one

* also migrate mock contracts to Solidity 0.6

* mark all non-view functions as virtual

* add simple catch-all implementation for the metadata URI interface

* include an internal function to set the URI so users can implement functionality to switch URIs

* add tests for ERC1155 metadata URI

* fix nits, mostly pointed out by linter

* convert ERC1155 metadata URI work to Solidity 0.6

* mark all non-view functions as virtual

* Port ERC 1155 branch to Solidity 0.6 (and current master) (#2130)

* port ERC1155 to Solidity 0.6

* make ERC1155 constructor more similar to ERC721 one

* also migrate mock contracts to Solidity 0.6

* mark all non-view functions as virtual

* Update contracts/token/ERC1155/IERC1155MetadataURI.sol

Starting on Solidity v0.6.2, interfaces can now inherit. \o/

Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>

* Fix compile errors

* Remove URI event

* Merge MetadataCatchAll into ERC1155

* Improve documentation.

* Simplify tests

* Move tests into ERC1155 tests

* Update documentation

* Bump minimum compiler version for inteface inheritance

* Fix holder tests

* Improve setUri docs

* Fix docs generation

Co-authored-by: Alan Lu <alanlu1023@gmail.com>
Co-authored-by: Nicolás Venturo <nicolas.venturo@gmail.com>
Co-authored-by: Francisco Giordano <frangio.1@gmail.com>
Robert Kaiser 5 年之前
父節點
當前提交
a81e948fc9

+ 8 - 0
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);
     }

+ 56 - 3
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

+ 8 - 8
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;
 }

+ 13 - 0
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);
+}

+ 21 - 0
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}}

+ 0 - 12
contracts/token/ERC1155/README.md

@@ -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`.

+ 34 - 1
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);
+      });
+    });
+  });
 });

+ 3 - 1
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 });