diff --git a/src/spotlight-token-collection/ISpotlightTokenIPCollection.sol b/src/spotlight-token-collection/ISpotlightTokenIPCollection.sol index 260ceed..8070400 100644 --- a/src/spotlight-token-collection/ISpotlightTokenIPCollection.sol +++ b/src/spotlight-token-collection/ISpotlightTokenIPCollection.sol @@ -11,9 +11,14 @@ interface ISpotlightTokenIPCollection { function totalSupply() external view returns (uint256); /** - * @dev Sets the token URI for the collection. + * @dev Sets the base URI for the collection. */ - function setTokenURI(string memory tokenURI_) external; + function setBaseURI(string memory baseURI_) external; + + /** + * @dev Sets the default token URI for the collection. + */ + function setDefaultTokenURI(string memory defaultTokenURI_) external; /** * @dev Returns wether minting is enabled. diff --git a/src/spotlight-token-collection/SpotlightTokenIPCollection.sol b/src/spotlight-token-collection/SpotlightTokenIPCollection.sol index daccd43..8e8b1a3 100644 --- a/src/spotlight-token-collection/SpotlightTokenIPCollection.sol +++ b/src/spotlight-token-collection/SpotlightTokenIPCollection.sol @@ -3,16 +3,20 @@ pragma solidity ^0.8.13; import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; import {ISpotlightTokenIPCollection} from "./ISpotlightTokenIPCollection.sol"; contract SpotlightTokenIPCollection is Ownable, ERC721, ISpotlightTokenIPCollection { + using Strings for uint256; + uint256 private _totalSupply; bool private _isTransferEnabled = false; bool private _isMintEnabled = false; address private _tokenFactory; - string private _tokenURI = "ipfs://bafkreibqge4t7rsppnarffvrzlfph5rk5ajvupa4oyk4v2h3ieqccty4ye"; + string private _defaultTokenURI = "ipfs://bafkreibqge4t7rsppnarffvrzlfph5rk5ajvupa4oyk4v2h3ieqccty4ye"; + string public __baseURI; - constructor(address tokenFactory_) Ownable(msg.sender) ERC721("Spotlight Meme IP", "Spotlight Meme IP") { + constructor(address tokenFactory_) Ownable(msg.sender) ERC721("Spotlight Token IP", "SPTIP") { _tokenFactory = tokenFactory_; } @@ -34,11 +38,19 @@ contract SpotlightTokenIPCollection is Ownable, ERC721, ISpotlightTokenIPCollect } /** - * @dev See {ISpotlightTokenCollection-setTokenURI}. + * @dev See {ISpotlightTokenCollection-setBaseURI}. + * @notice onlyOwner + */ + function setBaseURI(string memory baseURI_) public onlyOwner { + __baseURI = baseURI_; + } + + /** + * @dev See {ISpotlightTokenCollection-setDefaultTokenURI}. * @notice onlyOwner */ - function setTokenURI(string memory tokenURI_) public onlyOwner { - _tokenURI = tokenURI_; + function setDefaultTokenURI(string memory defaultTokenURI_) public onlyOwner { + _defaultTokenURI = defaultTokenURI_; } /** @@ -107,18 +119,21 @@ contract SpotlightTokenIPCollection is Ownable, ERC721, ISpotlightTokenIPCollect } /** - * @dev See {ERC721-_baseURI}. + * @dev See {ERC721-tokenURI}. */ - function _baseURI() internal view override returns (string memory) { - return _tokenURI; + function tokenURI(uint256 tokenId) public view override returns (string memory) { + _requireOwned(tokenId); + + return bytes(_baseURI()).length > 0 ? string.concat(_baseURI(), tokenId.toString()) : _defaultTokenURI; } /** - * @dev See {ERC721-tokenURI}. + * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each + * token will be the concatenation of the `baseURI` and the `tokenId`. Empty + * by default, can be overridden in child contracts. */ - function tokenURI(uint256 tokenId) public view override returns (string memory) { - _requireOwned(tokenId); - return _tokenURI; + function _baseURI() internal view override returns (string memory) { + return __baseURI; } /** diff --git a/test/SpotlightTokenIPCollectionTest.t.sol b/test/SpotlightTokenIPCollectionTest.t.sol index a91761d..1e56902 100644 --- a/test/SpotlightTokenIPCollectionTest.t.sol +++ b/test/SpotlightTokenIPCollectionTest.t.sol @@ -11,7 +11,7 @@ contract SpotlightTokenIPCollectionTest is Test { SpotlightTokenIPCollection private _tokenIpCollection; address private _tokenIpCollectionAddr; - string private originalTokenURI = "ipfs://bafkreibqge4t7rsppnarffvrzlfph5rk5ajvupa4oyk4v2h3ieqccty4ye"; + string private defaultTokenURI = "ipfs://bafkreibqge4t7rsppnarffvrzlfph5rk5ajvupa4oyk4v2h3ieqccty4ye"; string private newTokenURI = "https://example.com/new-token/"; function setUp() public { @@ -30,8 +30,8 @@ contract SpotlightTokenIPCollectionTest is Test { assertEq(_tokenIpCollection.totalSupply(), 0); assertEq(_tokenIpCollection.isMintEnabled(), false); assertEq(_tokenIpCollection.isTransferEnabled(), false); - assertEq(_tokenIpCollection.name(), "Spotlight Meme IP"); - assertEq(_tokenIpCollection.symbol(), "Spotlight Meme IP"); + assertEq(_tokenIpCollection.name(), "Spotlight Token IP"); + assertEq(_tokenIpCollection.symbol(), "SPTIP"); } function testNotOwnerSetMintEnabled() public { @@ -80,11 +80,11 @@ contract SpotlightTokenIPCollectionTest is Test { assertEq(_tokenIpCollection.tokenFactory(), newTokenFactory); } - function testNotOwnerSetTokenURI() public { + function testNotOwnerSetDefaultTokenURI() public { address notOwner = makeAddr("notOwner"); vm.startPrank(notOwner); vm.expectRevert(); - _tokenIpCollection.setTokenURI(newTokenURI); + _tokenIpCollection.setDefaultTokenURI(newTokenURI); vm.stopPrank(); } @@ -108,7 +108,7 @@ contract SpotlightTokenIPCollectionTest is Test { assertEq(_tokenIpCollection.totalSupply(), totalSupplyBefore + 1); assertEq(_tokenIpCollection.ownerOf(tokenId), receiver); - assertEq(_tokenIpCollection.tokenURI(tokenId), originalTokenURI); + assertEq(_tokenIpCollection.tokenURI(tokenId), defaultTokenURI); } function testTokenFactoryMint() public { @@ -120,7 +120,7 @@ contract SpotlightTokenIPCollectionTest is Test { assertEq(_tokenIpCollection.totalSupply(), totalSupplyBefore + 1); assertEq(_tokenIpCollection.ownerOf(tokenId), receiver); - assertEq(_tokenIpCollection.tokenURI(tokenId), originalTokenURI); + assertEq(_tokenIpCollection.tokenURI(tokenId), defaultTokenURI); } function testOwnerMint() public { @@ -132,7 +132,7 @@ contract SpotlightTokenIPCollectionTest is Test { assertEq(_tokenIpCollection.totalSupply(), totalSupplyBefore + 1); assertEq(_tokenIpCollection.ownerOf(tokenId), receiver); - assertEq(_tokenIpCollection.tokenURI(tokenId), originalTokenURI); + assertEq(_tokenIpCollection.tokenURI(tokenId), defaultTokenURI); } function testMintWithExistingTokenId() public { @@ -148,18 +148,38 @@ contract SpotlightTokenIPCollectionTest is Test { vm.stopPrank(); } - function testSetTokenURI() public { + function testSetDefaultTokenURI() public { address receiver = makeAddr("receiver"); uint256 tokenId = 0; _mint(_owner, receiver, tokenId); vm.startPrank(_owner); - _tokenIpCollection.setTokenURI(newTokenURI); + _tokenIpCollection.setDefaultTokenURI(newTokenURI); vm.stopPrank(); assertEq(_tokenIpCollection.tokenURI(tokenId), newTokenURI); } + function testNotOwnerSetBaseTokenURI() public { + address notOwner = makeAddr("notOwner"); + vm.startPrank(notOwner); + vm.expectRevert(); + _tokenIpCollection.setBaseURI(newTokenURI); + vm.stopPrank(); + } + + function testSetBaseURI() public { + address receiver = makeAddr("receiver"); + uint256 tokenId = 0; + _mint(_owner, receiver, tokenId); + + vm.startPrank(_owner); + _tokenIpCollection.setBaseURI(newTokenURI); + vm.stopPrank(); + + assertEq(_tokenIpCollection.tokenURI(tokenId), "https://example.com/new-token/0"); + } + function testUserTransferFromBeforeEnabled() public { address sender = makeAddr("sender"); address receiver = makeAddr("receiver");