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 8 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
14 changes: 14 additions & 0 deletions contracts/mocks/ERC1155URIStorageMock.sol
@@ -0,0 +1,14 @@
// 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 setTokenURI(uint256 tokenId, string memory _tokenURI) public {
_setTokenURI(tokenId, _tokenURI);
}
}
50 changes: 50 additions & 0 deletions contracts/token/ERC1155/extensions/ERC1155URIStorage.sol
@@ -0,0 +1,50 @@
// 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";

/**
* @dev ERC1155 token with storage based token URI management.
* Inspired by the ERC721URIStorage extension
*/
abstract contract ERC1155URIStorage is ERC1155 {
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
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 {
_tokenURIs[tokenId] = _tokenURI;
Amxx marked this conversation as resolved.
Show resolved Hide resolved
emit URI(tokenURI(tokenId), tokenId);
}
}
64 changes: 64 additions & 0 deletions test/token/ERC1155/extensions/ERC1155URIStorage.js
@@ -0,0 +1,64 @@
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 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/';
const receipt = await this.token.setTokenURI(tokenId, tokenUri);

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

const expectedUri = `${uri}${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.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/';
const receipt = await this.token.setTokenURI(tokenId, tokenUri);

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

expect(receivedTokenUri).to.be.equal(tokenUri);
expectEvent(receipt, 'URI', { value: tokenUri, id: tokenId });
});
});
});