parent
cb88e15b33
commit
4390b8df12
@ -0,0 +1,51 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../token/ERC721/extensions/ERC721URIStorage.sol"; |
||||
|
||||
/** |
||||
* @title ERC721Mock |
||||
* This mock just provides a public safeMint, mint, and burn functions for testing purposes |
||||
*/ |
||||
contract ERC721URIStorageMock is ERC721URIStorage { |
||||
string private _baseTokenURI; |
||||
|
||||
constructor (string memory name, string memory symbol) ERC721(name, symbol) { } |
||||
|
||||
function _baseURI() internal view virtual override returns (string memory) { |
||||
return _baseTokenURI; |
||||
} |
||||
|
||||
function setBaseURI(string calldata newBaseTokenURI) public { |
||||
_baseTokenURI = newBaseTokenURI; |
||||
} |
||||
|
||||
function baseURI() public view returns (string memory) { |
||||
return _baseURI(); |
||||
} |
||||
|
||||
function setTokenURI(uint256 tokenId, string memory _tokenURI) public { |
||||
_setTokenURI(tokenId, _tokenURI); |
||||
} |
||||
|
||||
function exists(uint256 tokenId) public view returns (bool) { |
||||
return _exists(tokenId); |
||||
} |
||||
|
||||
function mint(address to, uint256 tokenId) public { |
||||
_mint(to, tokenId); |
||||
} |
||||
|
||||
function safeMint(address to, uint256 tokenId) public { |
||||
_safeMint(to, tokenId); |
||||
} |
||||
|
||||
function safeMint(address to, uint256 tokenId, bytes memory _data) public { |
||||
_safeMint(to, tokenId, _data); |
||||
} |
||||
|
||||
function burn(uint256 tokenId) public { |
||||
_burn(tokenId); |
||||
} |
||||
} |
@ -0,0 +1,66 @@ |
||||
// SPDX-License-Identifier: MIT |
||||
|
||||
pragma solidity ^0.8.0; |
||||
|
||||
import "../ERC721.sol"; |
||||
|
||||
/** |
||||
* @dev ERC721 token with storage based token uri management. |
||||
*/ |
||||
abstract contract ERC721URIStorage is ERC721 { |
||||
using Strings for uint256; |
||||
|
||||
// Optional mapping for token URIs |
||||
mapping (uint256 => string) private _tokenURIs; |
||||
|
||||
/** |
||||
* @dev See {IERC721Metadata-tokenURI}. |
||||
*/ |
||||
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { |
||||
require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token"); |
||||
|
||||
string memory _tokenURI = _tokenURIs[tokenId]; |
||||
string memory base = _baseURI(); |
||||
|
||||
// If there is no base URI, return the token URI. |
||||
if (bytes(base).length == 0) { |
||||
return _tokenURI; |
||||
} |
||||
// If both are set, concatenate the baseURI and tokenURI (via abi.encodePacked). |
||||
if (bytes(_tokenURI).length > 0) { |
||||
return string(abi.encodePacked(base, _tokenURI)); |
||||
} |
||||
|
||||
return super.tokenURI(tokenId); |
||||
} |
||||
|
||||
/** |
||||
* @dev Sets `_tokenURI` as the tokenURI of `tokenId`. |
||||
* |
||||
* Requirements: |
||||
* |
||||
* - `tokenId` must exist. |
||||
*/ |
||||
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual { |
||||
require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token"); |
||||
_tokenURIs[tokenId] = _tokenURI; |
||||
} |
||||
|
||||
/** |
||||
* @dev Destroys `tokenId`. |
||||
* The approval is cleared when the token is burned. |
||||
* |
||||
* Requirements: |
||||
* |
||||
* - `tokenId` must exist. |
||||
* |
||||
* Emits a {Transfer} event. |
||||
*/ |
||||
function _burn(uint256 tokenId) internal virtual override { |
||||
super._burn(tokenId); |
||||
|
||||
if (bytes(_tokenURIs[tokenId]).length != 0) { |
||||
delete _tokenURIs[tokenId]; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,87 @@ |
||||
const { BN, expectRevert } = require('@openzeppelin/test-helpers'); |
||||
|
||||
const { expect } = require('chai'); |
||||
|
||||
const ERC721URIStorageMock = artifacts.require('ERC721URIStorageMock'); |
||||
|
||||
contract('ERC721URIStorage', function (accounts) { |
||||
const [ owner ] = accounts; |
||||
|
||||
const name = 'Non Fungible Token'; |
||||
const symbol = 'NFT'; |
||||
|
||||
const firstTokenId = new BN('5042'); |
||||
const nonExistentTokenId = new BN('13'); |
||||
|
||||
beforeEach(async function () { |
||||
this.token = await ERC721URIStorageMock.new(name, symbol); |
||||
}); |
||||
|
||||
describe('token URI', function () { |
||||
beforeEach(async function () { |
||||
await this.token.mint(owner, firstTokenId); |
||||
}); |
||||
|
||||
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), 'ERC721URIStorage: 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 for non existent token id', async function () { |
||||
await expectRevert( |
||||
this.token.setTokenURI(nonExistentTokenId, sampleUri), 'ERC721URIStorage: URI set of nonexistent token', |
||||
); |
||||
}); |
||||
|
||||
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); |
||||
|
||||
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('tokenId is appended to base URI for tokens with no URI', async function () { |
||||
await this.token.setBaseURI(baseURI); |
||||
|
||||
expect(await this.token.tokenURI(firstTokenId)).to.be.equal(baseURI + firstTokenId); |
||||
}); |
||||
|
||||
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), 'ERC721URIStorage: URI query for nonexistent token', |
||||
); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue