Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ERC1155: Add a simple catch-all implementation of the metadata URI interface #2029

Merged
merged 31 commits into from
Jun 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
20642cc
Initial ERC1155 implementation with some tests (#1803)
cag Nov 3, 2019
f57f16e
Merge branch 'master' into feature-erc1155
nventuro Jan 28, 2020
727512e
Migrate tests to @openzeppelin/test-environment
nventuro Jan 28, 2020
c3f4ae3
Merge branch 'master' into feature-erc1155
nventuro Mar 30, 2020
a7493e9
port ERC1155 to Solidity 0.6
KaiRo-at Mar 16, 2020
067d278
make ERC1155 constructor more similar to ERC721 one
KaiRo-at Mar 16, 2020
267700f
also migrate mock contracts to Solidity 0.6
KaiRo-at Mar 16, 2020
7910e8c
mark all non-view functions as virtual
KaiRo-at Apr 10, 2020
e574206
add simple catch-all implementation for the metadata URI interface
KaiRo-at Dec 13, 2019
6acec05
include an internal function to set the URI so users can implement fu…
KaiRo-at Dec 19, 2019
132728c
add tests for ERC1155 metadata URI
KaiRo-at Jan 27, 2020
85283ab
fix nits, mostly pointed out by linter
KaiRo-at Jan 29, 2020
7e0f247
convert ERC1155 metadata URI work to Solidity 0.6
KaiRo-at Apr 1, 2020
82c7bd7
mark all non-view functions as virtual
KaiRo-at Apr 10, 2020
5fd36e7
Port ERC 1155 branch to Solidity 0.6 (and current master) (#2130)
KaiRo-at Apr 27, 2020
7c2052f
Merge remote-tracking branch 'upstream/feature-erc1155' into feature-…
frangio Apr 27, 2020
fa4e185
Merge branch 'master' into feature-erc1155
nventuro May 8, 2020
77db2a1
Update contracts/token/ERC1155/IERC1155MetadataURI.sol
KaiRo-at May 11, 2020
4457b39
Merge branch 'master' into kairo-uri
nventuro Jun 2, 2020
23cb821
Fix compile errors
nventuro Jun 2, 2020
bc8887b
Merge branch 'master' into kairo-uri
nventuro Jun 2, 2020
473d0ce
Remove URI event
nventuro Jun 2, 2020
d55b138
Merge MetadataCatchAll into ERC1155
nventuro Jun 2, 2020
4a5108c
Improve documentation.
nventuro Jun 2, 2020
56b50f3
Simplify tests
nventuro Jun 2, 2020
22c4cd3
Move tests into ERC1155 tests
nventuro Jun 2, 2020
fd50e27
Update documentation
nventuro Jun 2, 2020
6653018
Bump minimum compiler version for inteface inheritance
nventuro Jun 2, 2020
fa92e6a
Fix holder tests
nventuro Jun 2, 2020
4763213
Improve setUri docs
nventuro Jun 3, 2020
d52171a
Fix docs generation
nventuro Jun 3, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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