Skip to content

Commit

Permalink
Add a simple catch-all implementation of the metadata URI interface (#…
Browse files Browse the repository at this point in the history
…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 <[email protected]>

* 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 <[email protected]>
Co-authored-by: Nicolás Venturo <[email protected]>
Co-authored-by: Francisco Giordano <[email protected]>
  • Loading branch information
4 people authored Jun 3, 2020
1 parent ccfd370 commit a81e948
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 25 deletions.
8 changes: 8 additions & 0 deletions contracts/mocks/ERC1155Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
59 changes: 56 additions & 3 deletions contracts/token/ERC1155/ERC1155.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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;

Expand All @@ -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
Expand All @@ -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;
}

/**
Expand Down Expand Up @@ -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
Expand Down
16 changes: 8 additions & 8 deletions contracts/token/ERC1155/IERC1155.sol
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.6.0;
pragma solidity ^0.6.2;

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);
Expand All @@ -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 changes: 13 additions & 0 deletions contracts/token/ERC1155/IERC1155MetadataURI.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions contracts/token/ERC1155/README.adoc
Original file line number Diff line number Diff line change
@@ -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}}
12 changes: 0 additions & 12 deletions contracts/token/ERC1155/README.md

This file was deleted.

35 changes: 34 additions & 1 deletion test/token/ERC1155/ERC1155.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
});
});
});
});
4 changes: 3 additions & 1 deletion test/token/ERC1155/ERC1155Holder.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down

0 comments on commit a81e948

Please sign in to comment.