Add ERC1155URIStorage (#3210)
* Add ERC721URIStorage-like extension for ERC1155 * Add tests for ERC1155URIStorage extension * add changelog entry for ERC721URIStorage * Fix linting errors * Emit URI event in ERC1155URIStorage * Remove exists check and ERC1155Supply dependency * Fix lint error * Overwrite ERC1155 uri method * Update ERC1155URIStorage specs * Fix ERC1155URIStorageMock * Rename _setTokenURI => _setURI in ERC1155URIStorage * Add baseURI to ERC1155URIStorage * Move super.uri call in ERC1155URIStorage * Clearify ERC1155URIStorage description in change log * reorder changelog & add documentation * improve documentation * fix typo Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>pull/3303/head
parent
ae270b0d89
commit
02fcc75bb7
@ -0,0 +1,22 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "./ERC1155Mock.sol"; |
||||
import "../token/ERC1155/extensions/ERC1155URIStorage.sol"; |
||||
|
||||
contract ERC1155URIStorageMock is ERC1155Mock, ERC1155URIStorage { |
||||
constructor(string memory _uri) ERC1155Mock(_uri) {} |
||||
|
||||
function uri(uint256 tokenId) public view virtual override(ERC1155, ERC1155URIStorage) returns (string memory) { |
||||
return ERC1155URIStorage.uri(tokenId); |
||||
} |
||||
|
||||
function setURI(uint256 tokenId, string memory _tokenURI) public { |
||||
_setURI(tokenId, _tokenURI); |
||||
} |
||||
|
||||
function setBaseURI(string memory baseURI) public { |
||||
_setBaseURI(baseURI); |
||||
} |
||||
} |
@ -0,0 +1,62 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../../../utils/Strings.sol"; |
||||
import "../ERC1155.sol"; |
||||
|
||||
/** |
||||
* @dev ERC1155 token with storage based token URI management. |
||||
* Inspired by the ERC721URIStorage extension |
||||
* |
||||
* _Available since v4.6._ |
||||
*/ |
||||
abstract contract ERC1155URIStorage is ERC1155 { |
||||
using Strings for uint256; |
||||
|
||||
// Optional base URI |
||||
string private _baseURI = ""; |
||||
|
||||
// Optional mapping for token URIs |
||||
mapping(uint256 => string) private _tokenURIs; |
||||
|
||||
/** |
||||
* @dev See {IERC1155MetadataURI-uri}. |
||||
* |
||||
* This implementation returns the concatenation of the `_baseURI` |
||||
* and the token-specific uri if the latter is set |
||||
* |
||||
* This enables the following behaviors: |
||||
* |
||||
* - if `_tokenURIs[tokenId]` is set, then the result is the concatenation |
||||
* of `_baseURI` and `_tokenURIs[tokenId]` (keep in mind that `_baseURI` |
||||
* is empty per default); |
||||
* |
||||
* - if `_tokenURIs[tokenId]` is NOT set then we fallback to `super.uri()` |
||||
* which in most cases will contain `ERC1155._uri`; |
||||
* |
||||
* - if `_tokenURIs[tokenId]` is NOT set, and if the parents do not have a |
||||
* uri value set, then the result is empty. |
||||
*/ |
||||
function uri(uint256 tokenId) public view virtual override returns (string memory) { |
||||
string memory tokenURI = _tokenURIs[tokenId]; |
||||
|
||||
// If token URI is set, concatenate base URI and tokenURI (via abi.encodePacked). |
||||
return bytes(tokenURI).length > 0 ? string(abi.encodePacked(_baseURI, tokenURI)) : super.uri(tokenId); |
||||
} |
||||
|
||||
/** |
||||
* @dev Sets `tokenURI` as the tokenURI of `tokenId`. |
||||
*/ |
||||
function _setURI(uint256 tokenId, string memory tokenURI) internal virtual { |
||||
_tokenURIs[tokenId] = tokenURI; |
||||
emit URI(uri(tokenId), tokenId); |
||||
} |
||||
|
||||
/** |
||||
* @dev Sets `baseURI` as the `_baseURI` for all tokens |
||||
*/ |
||||
function _setBaseURI(string memory baseURI) internal virtual { |
||||
_baseURI = baseURI; |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
const { BN, expectEvent } = require('@openzeppelin/test-helpers'); |
||||
|
||||
const { expect } = require('chai'); |
||||
const { artifacts } = require('hardhat'); |
||||
|
||||
const ERC1155URIStorageMock = artifacts.require('ERC1155URIStorageMock'); |
||||
|
||||
contract(['ERC1155URIStorage'], function (accounts) { |
||||
const [ holder ] = accounts; |
||||
|
||||
const erc1155Uri = 'https://token.com/nfts/'; |
||||
const baseUri = 'https://token.com/'; |
||||
|
||||
const tokenId = new BN('1'); |
||||
const amount = new BN('3000'); |
||||
|
||||
describe('with base uri set', function () { |
||||
beforeEach(async function () { |
||||
this.token = await ERC1155URIStorageMock.new(erc1155Uri); |
||||
this.token.setBaseURI(baseUri); |
||||
|
||||
await this.token.mint(holder, tokenId, amount, '0x'); |
||||
}); |
||||
|
||||
it('can request the token uri, returning the erc1155 uri if no token uri was set', async function () { |
||||
const receivedTokenUri = await this.token.uri(tokenId); |
||||
|
||||
expect(receivedTokenUri).to.be.equal(erc1155Uri); |
||||
}); |
||||
|
||||
it('can request the token uri, returning the concatenated uri if a token uri was set', async function () { |
||||
const tokenUri = '1234/'; |
||||
const receipt = await this.token.setURI(tokenId, tokenUri); |
||||
|
||||
const receivedTokenUri = await this.token.uri(tokenId); |
||||
|
||||
const expectedUri = `${baseUri}${tokenUri}`; |
||||
expect(receivedTokenUri).to.be.equal(expectedUri); |
||||
expectEvent(receipt, 'URI', { value: expectedUri, id: tokenId }); |
||||
}); |
||||
}); |
||||
|
||||
describe('with base uri set to the empty string', function () { |
||||
beforeEach(async function () { |
||||
this.token = await ERC1155URIStorageMock.new(''); |
||||
|
||||
await this.token.mint(holder, tokenId, amount, '0x'); |
||||
}); |
||||
|
||||
it('can request the token uri, returning an empty string if no token uri was set', async function () { |
||||
const receivedTokenUri = await this.token.uri(tokenId); |
||||
|
||||
expect(receivedTokenUri).to.be.equal(''); |
||||
}); |
||||
|
||||
it('can request the token uri, returning the token uri if a token uri was set', async function () { |
||||
const tokenUri = 'ipfs://1234/'; |
||||
const receipt = await this.token.setURI(tokenId, tokenUri); |
||||
|
||||
const receivedTokenUri = await this.token.uri(tokenId); |
||||
|
||||
expect(receivedTokenUri).to.be.equal(tokenUri); |
||||
expectEvent(receipt, 'URI', { value: tokenUri, id: tokenId }); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue