feat: add baseTokenURI to ERC721Metadata (#1970)
* feat: add baseTokenURI * fix: tests * chore: dev notation * chore: changelog * chore: typo * Remove extra getters, return empty URI by default * Update docs * Rename baseTokenURI to baseURI * Roll back visibility change of tokenURI * Update changelog entry * Version setBaseURI docs * Improve internal names and comments * Fix compilation errors * Add an external getter for baseURI
This commit is contained in:
committed by
Francisco Giordano
parent
714f13df7e
commit
49042f2b1a
@ -4,6 +4,7 @@
|
||||
|
||||
### New features
|
||||
* `SafeCast.toUintXX`: new library for integer downcasting, which allows for safe operation on smaller types (e.g. `uint32`) when combined with `SafeMath`. ([#1926](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1926))
|
||||
* `ERC721Metadata`: added `baseURI`, which can be used for dramatic gas savings when all token URIs share a prefix (e.g. `http://api.myapp.com/tokens/<id>`). ([#1970](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1970))
|
||||
|
||||
### Improvements
|
||||
* `ERC777`: `_burn` is now internal, providing more flexibility and making it easier to create tokens that deflate. ([#1908](https://github.com/OpenZeppelin/openzeppelin-contracts/pull/1908))
|
||||
|
||||
@ -26,4 +26,8 @@ contract ERC721FullMock is ERC721Full, ERC721Mintable, ERC721MetadataMintable, E
|
||||
function setTokenURI(uint256 tokenId, string memory uri) public {
|
||||
_setTokenURI(tokenId, uri);
|
||||
}
|
||||
|
||||
function setBaseURI(string memory baseURI) public {
|
||||
_setBaseURI(baseURI);
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,6 +12,9 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata {
|
||||
// Token symbol
|
||||
string private _symbol;
|
||||
|
||||
// Base URI
|
||||
string private _baseURI;
|
||||
|
||||
// Optional mapping for token URIs
|
||||
mapping(uint256 => string) private _tokenURIs;
|
||||
|
||||
@ -52,24 +55,58 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata {
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns an URI for a given token ID.
|
||||
* Throws if the token ID does not exist. May return an empty string.
|
||||
* @param tokenId uint256 ID of the token to query
|
||||
* @dev Returns the URI for a given token ID. May return an empty string.
|
||||
*
|
||||
* If the token's URI is non-empty and a base URI was set (via
|
||||
* {_setBaseURI}), it will be added to the token ID's URI as a prefix.
|
||||
*
|
||||
* Reverts if the token ID does not exist.
|
||||
*/
|
||||
function tokenURI(uint256 tokenId) external view returns (string memory) {
|
||||
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
|
||||
return _tokenURIs[tokenId];
|
||||
|
||||
string memory _tokenURI = _tokenURIs[tokenId];
|
||||
|
||||
// Even if there is a base URI, it is only appended to non-empty token-specific URIs
|
||||
if (bytes(_tokenURI).length == 0) {
|
||||
return "";
|
||||
} else {
|
||||
// abi.encodePacked is being used to concatenate strings
|
||||
return string(abi.encodePacked(_baseURI, _tokenURI));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the base URI set via {_setBaseURI}. This will be
|
||||
* automatically added as a preffix in {tokenURI} to each token's URI, when
|
||||
* they are non-empty.
|
||||
*/
|
||||
function baseURI() external view returns (string memory) {
|
||||
return _baseURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to set the token URI for a given token.
|
||||
*
|
||||
* Reverts if the token ID does not exist.
|
||||
* @param tokenId uint256 ID of the token to set its URI
|
||||
* @param uri string URI to assign
|
||||
*
|
||||
* TIP: if all token IDs share a prefix (e.g. if your URIs look like
|
||||
* `http://api.myproject.com/token/<id>`), use {_setBaseURI} to store
|
||||
* it and save gas.
|
||||
*/
|
||||
function _setTokenURI(uint256 tokenId, string memory uri) internal {
|
||||
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal {
|
||||
require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
|
||||
_tokenURIs[tokenId] = uri;
|
||||
_tokenURIs[tokenId] = _tokenURI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Internal function to set the base URI for all token IDs. It is
|
||||
* automatically added as a prefix to the value returned in {tokenURI}.
|
||||
*
|
||||
* _Available since v2.5.0._
|
||||
*/
|
||||
function _setBaseURI(string memory baseURI) internal {
|
||||
_baseURI = baseURI;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -72,8 +72,6 @@ contract('ERC721Full', function ([
|
||||
});
|
||||
|
||||
describe('metadata', function () {
|
||||
const sampleUri = 'mock://mytoken';
|
||||
|
||||
it('has a name', async function () {
|
||||
expect(await this.token.name()).to.be.equal(name);
|
||||
});
|
||||
@ -82,33 +80,70 @@ contract('ERC721Full', function ([
|
||||
expect(await this.token.symbol()).to.be.equal(symbol);
|
||||
});
|
||||
|
||||
it('sets and returns metadata for a token id', async function () {
|
||||
describe('token URI', function () {
|
||||
const baseURI = 'https://api.com/v1/';
|
||||
const sampleUri = 'mock://mytoken';
|
||||
|
||||
it('it is empty by default', async function () {
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
|
||||
});
|
||||
|
||||
it('reverts when queried for non existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token'
|
||||
);
|
||||
});
|
||||
|
||||
it('can be set for a token id', async function () {
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri);
|
||||
});
|
||||
|
||||
it('reverts when setting metadata for non existent token id', async function () {
|
||||
it('reverts when setting for non existent token id', async function () {
|
||||
await expectRevert(
|
||||
this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721Metadata: URI set of nonexistent token'
|
||||
);
|
||||
});
|
||||
|
||||
it('can burn token with metadata', async function () {
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
await this.token.burn(firstTokenId, { from: owner });
|
||||
expect(await this.token.exists(firstTokenId)).to.equal(false);
|
||||
it('base URI can be set', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
expect(await this.token.baseURI()).to.equal(baseURI);
|
||||
});
|
||||
|
||||
it('returns empty metadata for token', async function () {
|
||||
it('base URI is added as a prefix to the token URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri);
|
||||
});
|
||||
|
||||
it('token URI can be changed by changing the base URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
|
||||
const newBaseURI = 'https://api.com/v2/';
|
||||
await this.token.setBaseURI(newBaseURI);
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri);
|
||||
});
|
||||
|
||||
it('token URI is empty for tokens with no URI but with base URI', async function () {
|
||||
await this.token.setBaseURI(baseURI);
|
||||
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal('');
|
||||
});
|
||||
|
||||
it('reverts when querying metadata for non existent token id', async function () {
|
||||
it('tokens with URI can be burnt ', async function () {
|
||||
await this.token.setTokenURI(firstTokenId, sampleUri);
|
||||
|
||||
await this.token.burn(firstTokenId, { from: owner });
|
||||
|
||||
expect(await this.token.exists(firstTokenId)).to.equal(false);
|
||||
await expectRevert(
|
||||
this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token'
|
||||
this.token.tokenURI(firstTokenId), 'ERC721Metadata: URI query for nonexistent token'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('tokensOfOwner', function () {
|
||||
it('returns total tokens of owner', async function () {
|
||||
|
||||
Reference in New Issue
Block a user