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

Add ERC1155URIStorage #3210

Merged
merged 21 commits into from Mar 29, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Expand Up @@ -7,7 +7,8 @@
* `ERC1155`: Add a `_afterTokenTransfer` hook for improved extensibility. ([#3166](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3166))
* `DoubleEndedQueue`: a new data structure that supports efficient push and pop to both front and back, useful for FIFO and LIFO queues. ([#3153](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3153))
* `Governor`: improved security of `onlyGovernance` modifier when using an external executor contract (e.g. a timelock) that can operate without necessarily going through the governance protocol. ([#3147](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3147))

* `ERC1155URIStorage`: add a new extension for `ERC1155` that implements a `ERC721`-style `_setTokenURI` and `tokenURI` behavior.
takahser marked this conversation as resolved.
Show resolved Hide resolved

## 4.5.0 (2022-02-09)

* `ERC2981`: add implementation of the royalty standard, and the respective extensions for `ERC721` and `ERC1155`. ([#3012](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/3012))
Expand Down
26 changes: 26 additions & 0 deletions contracts/mocks/ERC1155URIStorageMock.sol
@@ -0,0 +1,26 @@
// 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) {}

// fixes: Derived contract must override function "_beforeTokenTransfer". Two or more base classes define function with same name and parameter types.
function _beforeTokenTransfer(
address operator,
address from,
address to,
uint256[] memory ids,
uint256[] memory amounts,
bytes memory data
) internal virtual override(ERC1155, ERC1155Supply) {
super._beforeTokenTransfer(operator, from, to, ids, amounts, data);
}

function setTokenURI(uint256 tokenId, string memory _tokenURI) public {
_setTokenURI(tokenId, _tokenURI);
}
}
53 changes: 53 additions & 0 deletions contracts/token/ERC1155/extensions/ERC1155URIStorage.sol
@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/ERC1155URIStorage.sol)

pragma solidity ^0.8.0;

import "../../../utils/Strings.sol";
import "../ERC1155.sol";
import "./ERC1155Supply.sol";

/**
* @dev ERC1155 token with storage based token URI management.
* Inspired by the ERC721URIStorage extension
*/
abstract contract ERC1155URIStorage is ERC1155Supply {
using Strings for uint256;

// Optional mapping for token URIs
mapping(uint256 => string) private _tokenURIs;

/**
* @dev {IERC721Metadata-tokenURI} token URIs for ERC1155 tokens.
*/
function tokenURI(uint256 tokenId) public view virtual returns (string memory) {
frangio marked this conversation as resolved.
Show resolved Hide resolved
require(exists(tokenId), "ERC1155URIStorage: URI query for nonexistent token");

string memory _tokenURI = _tokenURIs[tokenId];
string memory base = uri(0);

// If there is no base URI, return the token URI.
if (bytes(base).length == 0) {
return _tokenURI;
}
// If both are set, concatenate the base URI and tokenURI (via abi.encodePacked).
if (bytes(_tokenURI).length > 0) {
return string(abi.encodePacked(base, _tokenURI));
}

// If there is a base URI set but no tokenURI, return the base URI.
return base;
}

/**
* @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
*
* Requirements:
*
* - `tokenId` must exist.
*/
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
require(exists(tokenId), "ERC1155URIStorage: URI set of nonexistent token");
_tokenURIs[tokenId] = _tokenURI;
Amxx marked this conversation as resolved.
Show resolved Hide resolved
}
}
61 changes: 61 additions & 0 deletions test/token/ERC1155/extensions/ERC1155URIStorage.js
@@ -0,0 +1,61 @@
const { BN } = 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 uri = '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(uri);

await this.token.mint(holder, tokenId, amount, '0x');
});

it('can request the token uri, returning the base uri if no token uri was set', async function () {
const receivedTokenUri = await this.token.tokenURI(tokenId);

expect(receivedTokenUri).to.be.equal(uri);
});

it('can request the token uri, returning the concatenated uri if a token uri was set', async function () {
const tokenUri = '1234/';
await this.token.setTokenURI(tokenId, tokenUri);

const receivedTokenUri = await this.token.tokenURI(tokenId);

expect(receivedTokenUri).to.be.equal(`${uri}${tokenUri}`);
});
});

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.tokenURI(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/';
await this.token.setTokenURI(tokenId, tokenUri);

const receivedTokenUri = await this.token.tokenURI(tokenId);

expect(receivedTokenUri).to.be.equal(tokenUri);
});
});
});