Skip to content

Commit a81e948

Browse files
KaiRo-atcagnventurofrangio
authored
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 <[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]>
1 parent ccfd370 commit a81e948

File tree

8 files changed

+143
-25
lines changed

8 files changed

+143
-25
lines changed

contracts/mocks/ERC1155Mock.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ import "../token/ERC1155/ERC1155.sol";
99
* This mock just publicizes internal functions for testing purposes
1010
*/
1111
contract ERC1155Mock is ERC1155 {
12+
constructor (string memory uri) public ERC1155(uri) {
13+
// solhint-disable-previous-line no-empty-blocks
14+
}
15+
16+
function setURI(string memory newuri) public {
17+
_setURI(newuri);
18+
}
19+
1220
function mint(address to, uint256 id, uint256 value, bytes memory data) public {
1321
_mint(to, id, value, data);
1422
}

contracts/token/ERC1155/ERC1155.sol

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
pragma solidity ^0.6.0;
44

55
import "./IERC1155.sol";
6+
import "./IERC1155MetadataURI.sol";
67
import "./IERC1155Receiver.sol";
78
import "../../math/SafeMath.sol";
89
import "../../utils/Address.sol";
@@ -15,8 +16,7 @@ import "../../introspection/ERC165.sol";
1516
* See https://eips.ethereum.org/EIPS/eip-1155
1617
* Originally based on code by Enjin: https://github.com/enjin/erc-1155
1718
*/
18-
contract ERC1155 is ERC165, IERC1155
19-
{
19+
contract ERC1155 is ERC165, IERC1155, IERC1155MetadataURI {
2020
using SafeMath for uint256;
2121
using Address for address;
2222

@@ -26,6 +26,9 @@ contract ERC1155 is ERC165, IERC1155
2626
// Mapping from account to operator approvals
2727
mapping (address => mapping(address => bool)) private _operatorApprovals;
2828

29+
// Used as the URI for all token types by relying on ID substition, e.g. https://token-cdn-domain/{id}.json
30+
string private _uri;
31+
2932
/*
3033
* bytes4(keccak256('balanceOf(address,uint256)')) == 0x00fdd58e
3134
* bytes4(keccak256('balanceOfBatch(address[],uint256[])')) == 0x4e1273f4
@@ -39,9 +42,36 @@ contract ERC1155 is ERC165, IERC1155
3942
*/
4043
bytes4 private constant _INTERFACE_ID_ERC1155 = 0xd9b67a26;
4144

42-
constructor() public {
45+
/*
46+
* bytes4(keccak256('uri(uint256)')) == 0x0e89341c
47+
*/
48+
bytes4 private constant _INTERFACE_ID_ERC1155_METADATA_URI = 0x0e89341c;
49+
50+
/**
51+
* @dev See {_setURI}.
52+
*/
53+
constructor (string memory uri) public {
54+
_setURI(uri);
55+
4356
// register the supported interfaces to conform to ERC1155 via ERC165
4457
_registerInterface(_INTERFACE_ID_ERC1155);
58+
59+
// register the supported interfaces to conform to ERC1155MetadataURI via ERC165
60+
_registerInterface(_INTERFACE_ID_ERC1155_METADATA_URI);
61+
}
62+
63+
/**
64+
* @dev See {IERC1155MetadataURI-uri}.
65+
*
66+
* This implementation returns the same URI for *all* token types. It relies
67+
* on the token type ID substituion mechanism
68+
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
69+
*
70+
* Clients calling this function must replace the `{id}` substring with the
71+
* actual token type ID.
72+
*/
73+
function uri(uint256) external view override returns (string memory) {
74+
return _uri;
4575
}
4676

4777
/**
@@ -195,6 +225,29 @@ contract ERC1155 is ERC165, IERC1155
195225
_doSafeBatchTransferAcceptanceCheck(msg.sender, from, to, ids, values, data);
196226
}
197227

228+
/**
229+
* @dev Sets a new URI for all token types, by relying on the token type ID
230+
* substituion mechanism
231+
* https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
232+
*
233+
* By this mechanism, any occurence of the `{id}` substring in either the
234+
* URI or any of the values in the JSON file at said URI will be replaced by
235+
* clients with the token type ID.
236+
*
237+
* For example, the `https://token-cdn-domain/{id}.json` URI would be
238+
* interpreted by clients as
239+
* `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
240+
* for token type ID 0x4cce0.
241+
*
242+
* See {uri}.
243+
*
244+
* Because these URIs cannot be meaningfully represented by the {URI} event,
245+
* this function emits no events.
246+
*/
247+
function _setURI(string memory newuri) internal virtual {
248+
_uri = newuri;
249+
}
250+
198251
/**
199252
* @dev Internal function to mint an amount of a token with the given ID
200253
* @param to The address that will own the minted token

contracts/token/ERC1155/IERC1155.sol

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// SPDX-License-Identifier: MIT
22

3-
pragma solidity ^0.6.0;
3+
pragma solidity ^0.6.2;
44

55
import "../../introspection/IERC165.sol";
66

77
/**
88
@title ERC-1155 Multi Token Standard basic interface
99
@dev See https://eips.ethereum.org/EIPS/eip-1155
1010
*/
11-
abstract contract IERC1155 is IERC165 {
11+
interface IERC1155 is IERC165 {
1212
event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
1313

1414
event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
@@ -17,15 +17,15 @@ abstract contract IERC1155 is IERC165 {
1717

1818
event URI(string value, uint256 indexed id);
1919

20-
function balanceOf(address account, uint256 id) public view virtual returns (uint256);
20+
function balanceOf(address account, uint256 id) external view returns (uint256);
2121

22-
function balanceOfBatch(address[] memory accounts, uint256[] memory ids) public view virtual returns (uint256[] memory);
22+
function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids) external view returns (uint256[] memory);
2323

24-
function setApprovalForAll(address operator, bool approved) external virtual;
24+
function setApprovalForAll(address operator, bool approved) external;
2525

26-
function isApprovedForAll(address account, address operator) external view virtual returns (bool);
26+
function isApprovedForAll(address account, address operator) external view returns (bool);
2727

28-
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external virtual;
28+
function safeTransferFrom(address from, address to, uint256 id, uint256 value, bytes calldata data) external;
2929

30-
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external virtual;
30+
function safeBatchTransferFrom(address from, address to, uint256[] calldata ids, uint256[] calldata values, bytes calldata data) external;
3131
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.6.2;
4+
5+
import "./IERC1155.sol";
6+
7+
/**
8+
* @dev Interface of the optional ERC1155MetadataExtension interface, as defined
9+
* in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
10+
*/
11+
interface IERC1155MetadataURI is IERC1155 {
12+
function uri(uint256 id) external view returns (string memory);
13+
}

contracts/token/ERC1155/README.adoc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
= ERC 1155
2+
3+
This set of interfaces and contracts are all related to the https://eips.ethereum.org/EIPS/eip-1155[ERC1155 Multi Token Standard].
4+
5+
The EIP consists of three interfaces which fulfill different roles, found here as `IERC1155`, `IERC1155MetadataURI` and `IERC1155Receiver`.
6+
7+
`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.
8+
9+
`ERC1155Holder` implements the `IERC1155Receiver` interface for contracts that can receive (and hold) ERC1155 tokens.
10+
11+
== Core
12+
13+
{{IERC1155}}
14+
15+
{{IERC1155MetadataURI}}
16+
17+
{{ERC1155}}
18+
19+
{{IERC1155Receiver}}
20+
21+
{{ERC1155Holder}}

contracts/token/ERC1155/README.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

test/token/ERC1155/ERC1155.test.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ const ERC1155Mock = contract.fromArtifact('ERC1155Mock');
1111
describe('ERC1155', function () {
1212
const [creator, tokenHolder, tokenBatchHolder, ...otherAccounts] = accounts;
1313

14+
const initialURI = 'https://token-cdn-domain/{id}.json';
15+
1416
beforeEach(async function () {
15-
this.token = await ERC1155Mock.new({ from: creator });
17+
this.token = await ERC1155Mock.new(initialURI, { from: creator });
1618
});
1719

1820
shouldBehaveLikeERC1155(otherAccounts);
@@ -216,4 +218,35 @@ describe('ERC1155', function () {
216218
});
217219
});
218220
});
221+
222+
describe('ERC1155MetadataURI', function () {
223+
const firstTokenID = new BN('42');
224+
const secondTokenID = new BN('1337');
225+
226+
it('emits no URI event in constructor', async function () {
227+
await expectEvent.notEmitted.inConstruction(this.token, 'URI');
228+
});
229+
230+
it('sets the initial URI for all token types', async function () {
231+
expect(await this.token.uri(firstTokenID)).to.be.equal(initialURI);
232+
expect(await this.token.uri(secondTokenID)).to.be.equal(initialURI);
233+
});
234+
235+
describe('_setURI', function () {
236+
const newURI = 'https://token-cdn-domain/{locale}/{id}.json';
237+
238+
it('emits no URI event', async function () {
239+
const receipt = await this.token.setURI(newURI);
240+
241+
expectEvent.notEmitted(receipt, 'URI');
242+
});
243+
244+
it('sets the new URI for all token types', async function () {
245+
await this.token.setURI(newURI);
246+
247+
expect(await this.token.uri(firstTokenID)).to.be.equal(newURI);
248+
expect(await this.token.uri(secondTokenID)).to.be.equal(newURI);
249+
});
250+
});
251+
});
219252
});

test/token/ERC1155/ERC1155Holder.test.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ describe('ERC1155Holder', function () {
1010
const [creator] = accounts;
1111

1212
it('receives ERC1155 tokens', async function () {
13-
const multiToken = await ERC1155Mock.new({ from: creator });
13+
const uri = 'https://token-cdn-domain/{id}.json';
14+
15+
const multiToken = await ERC1155Mock.new(uri, { from: creator });
1416
const multiTokenIds = [new BN(1), new BN(2), new BN(3)];
1517
const multiTokenAmounts = [new BN(1000), new BN(2000), new BN(3000)];
1618
await multiToken.mintBatch(creator, multiTokenIds, multiTokenAmounts, '0x', { from: creator });

0 commit comments

Comments
 (0)