From 4b5f34839cc28089bbfa6d1c8badb1cb3f300f8b Mon Sep 17 00:00:00 2001 From: Ignacio Mazzara Date: Sun, 27 Oct 2019 17:15:28 -0300 Subject: [PATCH 01/14] feat: add baseTokenURI --- contracts/mocks/ERC721FullMock.sol | 8 ++++++ contracts/token/ERC721/ERC721Metadata.sol | 30 ++++++++++++++++++++++- test/token/ERC721/ERC721Full.test.js | 23 ++++++++++++++++- 3 files changed, 59 insertions(+), 2 deletions(-) diff --git a/contracts/mocks/ERC721FullMock.sol b/contracts/mocks/ERC721FullMock.sol index b0f00918bdf..3a5b7bdbfa3 100644 --- a/contracts/mocks/ERC721FullMock.sol +++ b/contracts/mocks/ERC721FullMock.sol @@ -26,4 +26,12 @@ contract ERC721FullMock is ERC721Full, ERC721Mintable, ERC721MetadataMintable, E function setTokenURI(uint256 tokenId, string memory uri) public { _setTokenURI(tokenId, uri); } + + function setBaseTokenURI(string memory baseURI) public { + _setBaseTokenURI(baseURI); + } + + function baseTokenURI() public view returns (string memory) { + return _baseTokenURI(); + } } diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 26f4706bd91..f686eb2b7bd 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -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; @@ -56,7 +59,16 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * Throws if the token ID does not exist. May return an empty string. * @param tokenId uint256 ID of the token to query */ - function tokenURI(uint256 tokenId) external view returns (string memory) { + function tokenURI(uint256 tokenId) public view returns (string memory) { + return string(abi.encodePacked(_baseTokenURI(), _tokenURI(tokenId))); + } + + /** + * @dev Internal 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 + */ + function _tokenURI(uint256 tokenId) internal view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); return _tokenURIs[tokenId]; } @@ -72,6 +84,22 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { _tokenURIs[tokenId] = uri; } + /** + * @dev Internal function to get the base token URI. + * @return string representing the base token URI + */ + function _baseTokenURI() internal view returns (string memory) { + return _baseURI; + } + + /** + * @dev Internal function to set the base token URI. + * @param uri string base URI to assign + */ + function _setBaseTokenURI(string memory uri) internal { + _baseURI = uri; + } + /** * @dev Internal function to burn a specific token. * Reverts if the token does not exist. diff --git a/test/token/ERC721/ERC721Full.test.js b/test/token/ERC721/ERC721Full.test.js index dbbf9af6a83..4d3d043a3d8 100644 --- a/test/token/ERC721/ERC721Full.test.js +++ b/test/token/ERC721/ERC721Full.test.js @@ -72,8 +72,8 @@ contract('ERC721Full', function ([ }); describe('metadata', function () { + const baseURI = 'https://api.com/v1/'; const sampleUri = 'mock://mytoken'; - it('has a name', async function () { expect(await this.token.name()).to.be.equal(name); }); @@ -82,11 +82,32 @@ contract('ERC721Full', function ([ expect(await this.token.symbol()).to.be.equal(symbol); }); + it('sets and returns the base token URI', async function () { + await this.token.setBaseTokenURI(baseURI); + expect(await this.token.baseTokenURI()).to.be.equal(baseURI); + }); + it('sets and returns metadata for a token id', async function () { await this.token.setTokenURI(firstTokenId, sampleUri); expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri); }); + it('sets and returns metadata for a token id with base token URI set', async function () { + await this.token.setBaseTokenURI(baseURI); + await this.token.setTokenURI(firstTokenId, sampleUri); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); + }); + + it('changes base token URI set', async function () { + await this.token.setBaseTokenURI(baseURI); + await this.token.setTokenURI(firstTokenId, sampleUri); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); + + const newBaseURI = 'https://api.com/v2/'; + await this.token.setBaseTokenURI(baseURI); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri); + }); + it('reverts when setting metadata for non existent token id', async function () { await expectRevert( this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721Metadata: URI set of nonexistent token' From c593cdc4f8a95c7ec29bcbf3aca6e43d231416a0 Mon Sep 17 00:00:00 2001 From: Ignacio Mazzara Date: Sun, 27 Oct 2019 17:52:22 -0300 Subject: [PATCH 02/14] fix: tests --- test/token/ERC721/ERC721Full.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/token/ERC721/ERC721Full.test.js b/test/token/ERC721/ERC721Full.test.js index 4d3d043a3d8..83a48181173 100644 --- a/test/token/ERC721/ERC721Full.test.js +++ b/test/token/ERC721/ERC721Full.test.js @@ -104,7 +104,7 @@ contract('ERC721Full', function ([ expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); const newBaseURI = 'https://api.com/v2/'; - await this.token.setBaseTokenURI(baseURI); + await this.token.setBaseTokenURI(newBaseURI); expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri); }); From 5b80b35170aac24f43183a1dcf8165bf43f5285b Mon Sep 17 00:00:00 2001 From: Ignacio Mazzara Date: Wed, 30 Oct 2019 11:33:07 -0300 Subject: [PATCH 03/14] chore: dev notation --- contracts/token/ERC721/ERC721Metadata.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index f686eb2b7bd..7b1d1a70acf 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -55,8 +55,9 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { } /** - * @dev Returns an URI for a given token ID. + * @dev Returns the concatenation of the base token URI with the URI for a given token ID. * Throws if the token ID does not exist. May return an empty string. + * The usage of concatenation here reduces gas costs when setting a token URI. * @param tokenId uint256 ID of the token to query */ function tokenURI(uint256 tokenId) public view returns (string memory) { @@ -86,6 +87,7 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { /** * @dev Internal function to get the base token URI. + * @notice that each token URI will be concatenated to this value when calling tokenURI. * @return string representing the base token URI */ function _baseTokenURI() internal view returns (string memory) { From b5a28b9d635879cf091c0d3d5f599c96fe690f2c Mon Sep 17 00:00:00 2001 From: Ignacio Mazzara Date: Wed, 30 Oct 2019 11:38:54 -0300 Subject: [PATCH 04/14] chore: changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2b85952a20..f737e7020c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ ### 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)) + * Add flexbility and save gass when setting a token URI by adding a base token URI to the ERC721Metadata.sol implementation. + +### Improvements: + * `tokenURI` from `external` to `public` making possible calling it with `super`. ## 2.4.0 (unreleased) From b041cc27abb035b7ff22fce567b1a31c09d8b4f6 Mon Sep 17 00:00:00 2001 From: Ignacio Mazzara Date: Wed, 30 Oct 2019 11:44:09 -0300 Subject: [PATCH 05/14] chore: typo --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15e3f287a1e..1a2b5092b03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +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)) - * Add flexbility and save gass when setting a token URI by adding a base token URI to the ERC721Metadata.sol implementation. + * Add flexibility and save gas when setting a token URI by adding a base token URI to the ERC721Metadata.sol implementation. ### Improvements: * `tokenURI` from `external` to `public` making possible calling it with `super`. From bdfb630bd96026eea063ec4d3996a4f41e3c7f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 11 Nov 2019 19:52:32 -0300 Subject: [PATCH 06/14] Remove extra getters, return empty URI by default --- contracts/mocks/ERC721FullMock.sol | 4 - contracts/token/ERC721/ERC721Metadata.sol | 28 +++---- test/token/ERC721/ERC721Full.test.js | 89 +++++++++++++---------- 3 files changed, 58 insertions(+), 63 deletions(-) diff --git a/contracts/mocks/ERC721FullMock.sol b/contracts/mocks/ERC721FullMock.sol index 3a5b7bdbfa3..a5d2a432dde 100644 --- a/contracts/mocks/ERC721FullMock.sol +++ b/contracts/mocks/ERC721FullMock.sol @@ -30,8 +30,4 @@ contract ERC721FullMock is ERC721Full, ERC721Mintable, ERC721MetadataMintable, E function setBaseTokenURI(string memory baseURI) public { _setBaseTokenURI(baseURI); } - - function baseTokenURI() public view returns (string memory) { - return _baseTokenURI(); - } } diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 7b1d1a70acf..9aaa16add88 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -61,17 +61,16 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * @param tokenId uint256 ID of the token to query */ function tokenURI(uint256 tokenId) public view returns (string memory) { - return string(abi.encodePacked(_baseTokenURI(), _tokenURI(tokenId))); - } - - /** - * @dev Internal 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 - */ - function _tokenURI(uint256 tokenId) internal view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - return _tokenURIs[tokenId]; + + string memory uri = _tokenURIs[tokenId]; + + // Even if there is a base URI, it is only appended to non-empty token-specific URIs + if (bytes(uri).length == 0) { + return ""; + } else { + return string(abi.encodePacked(_baseURI, uri)); + } } /** @@ -85,15 +84,6 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { _tokenURIs[tokenId] = uri; } - /** - * @dev Internal function to get the base token URI. - * @notice that each token URI will be concatenated to this value when calling tokenURI. - * @return string representing the base token URI - */ - function _baseTokenURI() internal view returns (string memory) { - return _baseURI; - } - /** * @dev Internal function to set the base token URI. * @param uri string base URI to assign diff --git a/test/token/ERC721/ERC721Full.test.js b/test/token/ERC721/ERC721Full.test.js index 83a48181173..9e92a77839f 100644 --- a/test/token/ERC721/ERC721Full.test.js +++ b/test/token/ERC721/ERC721Full.test.js @@ -72,8 +72,6 @@ contract('ERC721Full', function ([ }); describe('metadata', function () { - const baseURI = 'https://api.com/v1/'; - const sampleUri = 'mock://mytoken'; it('has a name', async function () { expect(await this.token.name()).to.be.equal(name); }); @@ -82,52 +80,63 @@ contract('ERC721Full', function ([ expect(await this.token.symbol()).to.be.equal(symbol); }); - it('sets and returns the base token URI', async function () { - await this.token.setBaseTokenURI(baseURI); - expect(await this.token.baseTokenURI()).to.be.equal(baseURI); - }); + describe('token URI', function () { + const baseURI = 'https://api.com/v1/'; + const sampleUri = 'mock://mytoken'; - it('sets and returns metadata for a token id', async function () { - await this.token.setTokenURI(firstTokenId, sampleUri); - expect(await this.token.tokenURI(firstTokenId)).to.be.equal(sampleUri); - }); + it('it is empty by default', async function () { + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(''); + }); - it('sets and returns metadata for a token id with base token URI set', async function () { - await this.token.setBaseTokenURI(baseURI); - await this.token.setTokenURI(firstTokenId, sampleUri); - expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); - }); + it('reverts when queried for non existent token id', async function () { + await expectRevert( + this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token' + ); + }); - it('changes base token URI set', async function () { - await this.token.setBaseTokenURI(baseURI); - await this.token.setTokenURI(firstTokenId, sampleUri); - expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); + 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); + }); - const newBaseURI = 'https://api.com/v2/'; - await this.token.setBaseTokenURI(newBaseURI); - expect(await this.token.tokenURI(firstTokenId)).to.be.equal(newBaseURI + sampleUri); - }); + 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('reverts when setting metadata for non existent token id', async function () { - await expectRevert( - this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721Metadata: URI set of nonexistent token' - ); - }); + it('base URI is added as a prefix to the token URI', async function () { + await this.token.setBaseTokenURI(baseURI); + await this.token.setTokenURI(firstTokenId, sampleUri); - 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); - }); + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + sampleUri); + }); - it('returns empty metadata for token', async function () { - expect(await this.token.tokenURI(firstTokenId)).to.be.equal(''); - }); + it('token URI can be changed by changing the base URI', async function () { + await this.token.setBaseTokenURI(baseURI); + await this.token.setTokenURI(firstTokenId, sampleUri); - it('reverts when querying metadata for non existent token id', async function () { - await expectRevert( - this.token.tokenURI(nonExistentTokenId), 'ERC721Metadata: URI query for nonexistent token' - ); + const newBaseURI = 'https://api.com/v2/'; + await this.token.setBaseTokenURI(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.setBaseTokenURI(baseURI); + + expect(await this.token.tokenURI(firstTokenId)).to.be.equal(''); + }); + + 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(firstTokenId), 'ERC721Metadata: URI query for nonexistent token' + ); + }); }); }); From b8bfa4a2a5acdd1247874c01907a09f9e53425a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Mon, 11 Nov 2019 20:09:06 -0300 Subject: [PATCH 07/14] Update docs --- contracts/token/ERC721/ERC721Metadata.sol | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 9aaa16add88..9bfb58a2869 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -55,10 +55,12 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { } /** - * @dev Returns the concatenation of the base token URI with the URI for a given token ID. - * Throws if the token ID does not exist. May return an empty string. - * The usage of concatenation here reduces gas costs when setting a token URI. - * @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 + * {_setBaseTokenURI}), 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) public view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); @@ -75,9 +77,12 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { /** * @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/`), use {_setBaseTokenURI} to store + * it and save gas. */ function _setTokenURI(uint256 tokenId, string memory uri) internal { require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); @@ -85,8 +90,8 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { } /** - * @dev Internal function to set the base token URI. - * @param uri string base URI to assign + * @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}. */ function _setBaseTokenURI(string memory uri) internal { _baseURI = uri; From 2667872b756ba39be735cfd5f9703d88d421c059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:30:18 -0300 Subject: [PATCH 08/14] Rename baseTokenURI to baseURI --- contracts/token/ERC721/ERC721Metadata.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 9bfb58a2869..6ae866b2ca9 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -58,7 +58,7 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * @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 - * {_setBaseTokenURI}), it will be added to the token ID's URI as a prefix. + * {_setBaseURI}), it will be added to the token ID's URI as a prefix. * * Reverts if the token ID does not exist. */ @@ -81,7 +81,7 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * Reverts if the token ID does not exist. * * TIP: if all token IDs share a prefix (e.g. if your URIs look like - * `http://api.myproject.com/token/`), use {_setBaseTokenURI} to store + * `http://api.myproject.com/token/`), use {_setBaseURI} to store * it and save gas. */ function _setTokenURI(uint256 tokenId, string memory uri) internal { @@ -93,7 +93,7 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * @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}. */ - function _setBaseTokenURI(string memory uri) internal { + function _setBaseURI(string memory uri) internal { _baseURI = uri; } From ba9a364b8bcb5710bdf8afb44abfdc139e383c88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:30:41 -0300 Subject: [PATCH 09/14] Roll back visibility change of tokenURI --- contracts/token/ERC721/ERC721Metadata.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 6ae866b2ca9..aa43ad97977 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -62,7 +62,7 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * * Reverts if the token ID does not exist. */ - function tokenURI(uint256 tokenId) public view returns (string memory) { + function tokenURI(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); string memory uri = _tokenURIs[tokenId]; From 0a4290f06c27590d3338a7a1221900793473ae75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:30:54 -0300 Subject: [PATCH 10/14] Update changelog entry --- CHANGELOG.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a2b5092b03..70cae66112a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,10 +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)) - * Add flexibility and save gas when setting a token URI by adding a base token URI to the ERC721Metadata.sol implementation. - -### Improvements: - * `tokenURI` from `external` to `public` making possible calling it with `super`. + * `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/`). ([#1970](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1970)) ### Breaking changes: * `ERC165Checker` now requires a minimum Solidity compiler version of 0.5.10. ([#1829](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1829)) From b49c5a82c9a5dd783021817b9cad0e779931400b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:32:27 -0300 Subject: [PATCH 11/14] Version setBaseURI docs --- contracts/token/ERC721/ERC721Metadata.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index aa43ad97977..40cf07877bb 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -92,6 +92,8 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { /** * @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 uri) internal { _baseURI = uri; From 472a1ccc8aac369b0aa6a2832267bf94bf4244db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:36:02 -0300 Subject: [PATCH 12/14] Improve internal names and comments --- contracts/token/ERC721/ERC721Metadata.sol | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 40cf07877bb..f870bdbf310 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -65,13 +65,14 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { function tokenURI(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - string memory uri = _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(uri).length == 0) { + if (bytes(tokenURI).length == 0) { return ""; } else { - return string(abi.encodePacked(_baseURI, uri)); + // abi.encodePacked is being used to concatenate strings + return string(abi.encodePacked(_baseURI, tokenURI)); } } @@ -84,9 +85,9 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * `http://api.myproject.com/token/`), 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; } /** @@ -95,8 +96,8 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * * _Available since v2.5.0._ */ - function _setBaseURI(string memory uri) internal { - _baseURI = uri; + function _setBaseURI(string memory baseURI) internal { + _baseURI = baseURI; } /** From c07df9d3d5e392036e5dabc65728e924b58fb09e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:38:03 -0300 Subject: [PATCH 13/14] Fix compilation errors --- contracts/mocks/ERC721FullMock.sol | 4 ++-- contracts/token/ERC721/ERC721Metadata.sol | 10 +++++----- test/token/ERC721/ERC721Full.test.js | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/mocks/ERC721FullMock.sol b/contracts/mocks/ERC721FullMock.sol index a5d2a432dde..bbd1e32bc33 100644 --- a/contracts/mocks/ERC721FullMock.sol +++ b/contracts/mocks/ERC721FullMock.sol @@ -27,7 +27,7 @@ contract ERC721FullMock is ERC721Full, ERC721Mintable, ERC721MetadataMintable, E _setTokenURI(tokenId, uri); } - function setBaseTokenURI(string memory baseURI) public { - _setBaseTokenURI(baseURI); + function setBaseURI(string memory baseURI) public { + _setBaseURI(baseURI); } } diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index f870bdbf310..31466c0086b 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -65,14 +65,14 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { function tokenURI(uint256 tokenId) external view returns (string memory) { require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); - string memory tokenURI = _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) { + if (bytes(_tokenURI).length == 0) { return ""; } else { // abi.encodePacked is being used to concatenate strings - return string(abi.encodePacked(_baseURI, tokenURI)); + return string(abi.encodePacked(_baseURI, _tokenURI)); } } @@ -85,9 +85,9 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { * `http://api.myproject.com/token/`), use {_setBaseURI} to store * it and save gas. */ - function _setTokenURI(uint256 tokenId, string memory tokenURI) internal { + function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal { require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token"); - _tokenURIs[tokenId] = tokenURI; + _tokenURIs[tokenId] = _tokenURI; } /** diff --git a/test/token/ERC721/ERC721Full.test.js b/test/token/ERC721/ERC721Full.test.js index 9e92a77839f..2a3bfb9b033 100644 --- a/test/token/ERC721/ERC721Full.test.js +++ b/test/token/ERC721/ERC721Full.test.js @@ -106,23 +106,23 @@ contract('ERC721Full', function ([ }); it('base URI is added as a prefix to the token URI', async function () { - await this.token.setBaseTokenURI(baseURI); + 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.setBaseTokenURI(baseURI); + await this.token.setBaseURI(baseURI); await this.token.setTokenURI(firstTokenId, sampleUri); const newBaseURI = 'https://api.com/v2/'; - await this.token.setBaseTokenURI(newBaseURI); + 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.setBaseTokenURI(baseURI); + await this.token.setBaseURI(baseURI); expect(await this.token.tokenURI(firstTokenId)).to.be.equal(''); }); From ef9395ba9861a84e7ea3fed07d037f6a0f523725 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Venturo?= Date: Wed, 13 Nov 2019 15:42:16 -0300 Subject: [PATCH 14/14] Add an external getter for baseURI --- contracts/token/ERC721/ERC721Metadata.sol | 9 +++++++++ test/token/ERC721/ERC721Full.test.js | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/contracts/token/ERC721/ERC721Metadata.sol b/contracts/token/ERC721/ERC721Metadata.sol index 31466c0086b..e0e516065db 100644 --- a/contracts/token/ERC721/ERC721Metadata.sol +++ b/contracts/token/ERC721/ERC721Metadata.sol @@ -76,6 +76,15 @@ contract ERC721Metadata is Context, ERC165, ERC721, IERC721Metadata { } } + /** + * @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. * diff --git a/test/token/ERC721/ERC721Full.test.js b/test/token/ERC721/ERC721Full.test.js index 2a3bfb9b033..878488f6dd6 100644 --- a/test/token/ERC721/ERC721Full.test.js +++ b/test/token/ERC721/ERC721Full.test.js @@ -105,6 +105,11 @@ contract('ERC721Full', function ([ ); }); + it('base URI can be set', async function () { + await this.token.setBaseURI(baseURI); + expect(await this.token.baseURI()).to.equal(baseURI); + }); + 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);