Skip to content

Commit

Permalink
Add IERC721Metadata req, add tokenURI function with contract call, po…
Browse files Browse the repository at this point in the history
…lish stuff
  • Loading branch information
matejos committed Mar 29, 2024
1 parent f2185dc commit 24584f0
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 51 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {ITokenUri} from "./ITokenUri.sol";

/// @dev A standard ERC721 that can be burned and has a special tokenURI function accepting a custom base URI.
interface IInverseProjectedNft is IERC4906 {
interface IInverseProjectedNft is IERC4906, IERC721Metadata {
/// @dev Emitted when `baseExtension` is updated from `oldBaseExtension` to `newBaseExtension`.
event SetBaseExtension(string oldBaseExtension, string newBaseExtension);

Expand All @@ -33,4 +31,10 @@ interface IInverseProjectedNft is IERC4906 {
uint256 tokenId,
string memory customBaseUri
) external view returns (string memory);

/// @dev Returns the token URI of specified `tokenId` using a call to contract implementing `ITokenUri`.
function tokenURI(
uint256 tokenId,
ITokenUri customUriInterface
) external view returns (string memory);
}
10 changes: 10 additions & 0 deletions packages/contracts/evm-contracts/contracts/token/ITokenUri.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

/// @dev An interface exposing the `tokenURI` function from IERC721Metadata.
interface ITokenUri {
/**
* @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
*/
function tokenURI(uint256 tokenId) external view returns (string memory);
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ pragma solidity ^0.8.13;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {IInverseProjectedNft} from "./IInverseProjectedNft.sol";
import {IInverseAppProjectedNft} from "./IInverseAppProjectedNft.sol";
import {ITokenUri} from "./ITokenUri.sol";

struct MintEntry {
address minter;
Expand Down Expand Up @@ -64,7 +64,7 @@ contract InverseAppProjectedNft is IInverseAppProjectedNft, ERC721, Ownable {
/// Increases the `totalSupply` and `currentTokenId`.
/// Reverts if `_to` is a zero address or if it refers to smart contract but does not implement IERC721Receiver-onERC721Received.
/// Emits the `Minted` event.
function mint(address _to) external virtual returns (uint256) {
function mint(address _to) public virtual returns (uint256) {
require(_to != address(0), "InverseAppProjectedNft: zero receiver address");

uint256 tokenId = currentTokenId;
Expand All @@ -82,7 +82,7 @@ contract InverseAppProjectedNft is IInverseAppProjectedNft, ERC721, Ownable {

/// @dev Burns token of ID `_tokenId`. Callable only by the owner of the specified token.
/// Reverts if `_tokenId` does not exist.
function burn(uint256 _tokenId) external virtual onlyTokenOwner(_tokenId) {
function burn(uint256 _tokenId) public virtual onlyTokenOwner(_tokenId) {
totalSupply--;
_burn(_tokenId);
}
Expand All @@ -93,7 +93,9 @@ contract InverseAppProjectedNft is IInverseAppProjectedNft, ERC721, Ownable {
}

/// @dev Returns the token URI of specified `tokenId` using the default set base URI.
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
function tokenURI(
uint256 tokenId
) public view virtual override(ERC721, IERC721Metadata) returns (string memory) {
return tokenURI(tokenId, _baseURI());
}

Expand All @@ -118,10 +120,18 @@ contract InverseAppProjectedNft is IInverseAppProjectedNft, ERC721, Ownable {
return string(abi.encodePacked(URI, baseExtension));
}

/// @dev Returns the token URI of specified `tokenId` using a call to contract implementing `ITokenUri`.
function tokenURI(
uint256 tokenId,
ITokenUri customUriInterface
) public view returns (string memory) {
return customUriInterface.tokenURI(tokenId);
}

/// @dev Sets `_URI` as the `baseURI` of the NFT.
/// Callable only by the contract owner.
/// Emits the `SetBaseURI` event.
function setBaseURI(string memory _URI) external virtual onlyOwner {
function setBaseURI(string memory _URI) public virtual onlyOwner {
string memory oldURI = baseURI;
baseURI = _URI;
emit SetBaseURI(oldURI, _URI);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ pragma solidity ^0.8.13;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {IERC721Metadata} from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {IInverseProjectedNft} from "./IInverseProjectedNft.sol";
import {IInverseBaseProjectedNft} from "./IInverseBaseProjectedNft.sol";
import {ITokenUri} from "./ITokenUri.sol";

/// @dev A standard ERC721 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {
Expand Down Expand Up @@ -55,7 +55,7 @@ contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {
/// Increases the `totalSupply` and `currentTokenId`.
/// Reverts if `_to` is a zero address or if it refers to smart contract but does not implement IERC721Receiver-onERC721Received.
/// Emits the `Minted` event.
function mint(address _to, string calldata initialData) external virtual returns (uint256) {
function mint(address _to, string calldata initialData) public virtual returns (uint256) {
require(_to != address(0), "InverseBaseProjectedNft: zero receiver address");

uint256 tokenId = currentTokenId;
Expand All @@ -70,7 +70,7 @@ contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {

/// @dev Burns token of ID `_tokenId`. Callable only by the owner of the specified token.
/// Reverts if `_tokenId` does not exist.
function burn(uint256 _tokenId) external virtual onlyTokenOwner(_tokenId) {
function burn(uint256 _tokenId) public virtual onlyTokenOwner(_tokenId) {
totalSupply--;
_burn(_tokenId);
}
Expand All @@ -81,7 +81,9 @@ contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {
}

/// @dev Returns the token URI of specified `tokenId` using the default set base URI.
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
function tokenURI(
uint256 tokenId
) public view virtual override(ERC721, IERC721Metadata) returns (string memory) {
return tokenURI(tokenId, _baseURI());
}

Expand All @@ -103,10 +105,18 @@ contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {
return string(abi.encodePacked(URI, baseExtension));
}

/// @dev Returns the token URI of specified `tokenId` using a call to contract implementing `ITokenUri`.
function tokenURI(
uint256 tokenId,
ITokenUri customUriInterface
) public view returns (string memory) {
return customUriInterface.tokenURI(tokenId);
}

/// @dev Sets `_URI` as the `baseURI` of the NFT.
/// Callable only by the contract owner.
/// Emits the `SetBaseURI` event.
function setBaseURI(string memory _URI) external virtual onlyOwner {
function setBaseURI(string memory _URI) public virtual onlyOwner {
string memory oldURI = baseURI;
baseURI = _URI;
emit SetBaseURI(oldURI, _URI);
Expand Down
44 changes: 27 additions & 17 deletions packages/contracts/evm-contracts/test/InverseAppProjectedNft.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@ pragma solidity ^0.8.18;
import "../test-lib/cheatcodes.sol";
import "../test-lib/console.sol";
import "../test-lib/ctest.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/interfaces/IERC4906.sol";
import "../contracts/token/InverseAppProjectedNft.sol";
import "../contracts/token/IInverseProjectedNft.sol";

contract InverseAppProjectedNftTest is CTest {
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {InverseAppProjectedNft} from "../contracts/token/InverseAppProjectedNft.sol";
import {IInverseAppProjectedNft} from "../contracts/token/IInverseAppProjectedNft.sol";
import {ITokenUri} from "../contracts/token/ITokenUri.sol";

contract MockTokenUri is ITokenUri {
using Strings for uint256;

function tokenURI(uint256 tokenId) external view override returns (string memory) {
return string.concat("mock://", tokenId.toString());
}
}

contract InverseAppProjectedNftTest is CTest, ERC721Holder {
using Strings for uint256;

CheatCodes vm = CheatCodes(HEVM_ADDRESS);
InverseAppProjectedNft public nft;
uint256 ownedTokenId;
Expand Down Expand Up @@ -65,6 +78,12 @@ contract InverseAppProjectedNftTest is CTest {
);
}

function test_TokenUriUsingCustomUriInterface() public {
ITokenUri tokenUriInterface = new MockTokenUri();
string memory result = nft.tokenURI(ownedTokenId, tokenUriInterface);
assertEq(result, string.concat("mock://", ownedTokenId.toString()));
}

function test_SupportsInterface() public {
assertTrue(nft.supportsInterface(type(IERC165).interfaceId));
assertTrue(nft.supportsInterface(type(IERC721).interfaceId));
Expand Down Expand Up @@ -108,13 +127,4 @@ contract InverseAppProjectedNftTest is CTest {
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice));
nft.setBaseExtension("test");
}

function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}
44 changes: 27 additions & 17 deletions packages/contracts/evm-contracts/test/InverseBaseProjectedNft.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,27 @@ pragma solidity ^0.8.18;
import "../test-lib/cheatcodes.sol";
import "../test-lib/console.sol";
import "../test-lib/ctest.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts/interfaces/IERC4906.sol";
import "../contracts/token/InverseBaseProjectedNft.sol";
import "../contracts/token/IInverseProjectedNft.sol";

contract InverseBaseProjectedNftTest is CTest {
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {ERC721Holder} from "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";
import {InverseBaseProjectedNft} from "../contracts/token/InverseBaseProjectedNft.sol";
import {IInverseBaseProjectedNft} from "../contracts/token/IInverseBaseProjectedNft.sol";
import {ITokenUri} from "../contracts/token/ITokenUri.sol";

contract MockTokenUri is ITokenUri {
using Strings for uint256;

function tokenURI(uint256 tokenId) external view override returns (string memory) {
return string.concat("mock://", tokenId.toString());
}
}

contract InverseBaseProjectedNftTest is CTest, ERC721Holder {
using Strings for uint256;

CheatCodes vm = CheatCodes(HEVM_ADDRESS);
InverseBaseProjectedNft public nft;
uint256 ownedTokenId;
Expand Down Expand Up @@ -49,6 +62,12 @@ contract InverseBaseProjectedNftTest is CTest {
assertEq(result, "1.1.0.0/eip155:31337/1.json");
}

function test_TokenUriUsingCustomUriInterface() public {
ITokenUri tokenUriInterface = new MockTokenUri();
string memory result = nft.tokenURI(ownedTokenId, tokenUriInterface);
assertEq(result, string.concat("mock://", ownedTokenId.toString()));
}

function test_SupportsInterface() public {
assertTrue(nft.supportsInterface(type(IERC165).interfaceId));
assertTrue(nft.supportsInterface(type(IERC721).interfaceId));
Expand Down Expand Up @@ -92,13 +111,4 @@ contract InverseBaseProjectedNftTest is CTest {
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, alice));
nft.setBaseExtension("test");
}

function onERC721Received(
address,
address,
uint256,
bytes calldata
) external pure returns (bytes4) {
return IERC721Receiver.onERC721Received.selector;
}
}

0 comments on commit 24584f0

Please sign in to comment.