Skip to content

Commit

Permalink
PRC3 changes (#335)
Browse files Browse the repository at this point in the history
* Add virtual to functions

* Append `eip155:chainId` in tokenURI

* Add IERC721Metadata req, add tokenURI function with contract call, polish stuff
  • Loading branch information
matejos authored Mar 30, 2024
1 parent b182760 commit 04714b1
Show file tree
Hide file tree
Showing 7 changed files with 130 additions and 64 deletions.
3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
"[typescriptreact]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[solidity]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"files.associations": {
".env.*": "properties"
},
Expand Down
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 @@ -52,7 +52,7 @@ contract InverseAppProjectedNft is IInverseAppProjectedNft, ERC721, Ownable {
/// @dev Returns true if this contract implements the interface defined by `interfaceId`. See EIP165.
function supportsInterface(
bytes4 interfaceId
) public view override(IERC165, ERC721) returns (bool) {
) public view virtual override(IERC165, ERC721) returns (bool) {
return
interfaceId == type(IInverseProjectedNft).interfaceId ||
interfaceId == type(IInverseAppProjectedNft).interfaceId ||
Expand All @@ -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 returns (uint256) {
function mint(address _to) public virtual returns (uint256) {
require(_to != address(0), "InverseAppProjectedNft: zero receiver address");

uint256 tokenId = currentTokenId;
Expand All @@ -82,31 +82,36 @@ 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 onlyTokenOwner(_tokenId) {
function burn(uint256 _tokenId) public virtual onlyTokenOwner(_tokenId) {
totalSupply--;
_burn(_tokenId);
}

/// @dev Returns the `baseURI` of this NFT.
function _baseURI() internal view override returns (string memory) {
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}

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

/// @dev Returns the token URI of specified `tokenId` using a custom base URI.
function tokenURI(
uint256 tokenId,
string memory customBaseUri
) public view returns (string memory) {
) public view virtual returns (string memory) {
_requireOwned(tokenId);
MintEntry memory entry = tokenToMint[tokenId];
string memory URI = bytes(customBaseUri).length > 0
? string.concat(
customBaseUri,
"eip155:",
block.chainid.toString(),
"/",
Strings.toHexString(uint160(entry.minter), 20),
"/",
entry.userTokenId.toString()
Expand All @@ -115,18 +120,26 @@ 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 onlyOwner {
function setBaseURI(string memory _URI) public virtual onlyOwner {
string memory oldURI = baseURI;
baseURI = _URI;
emit SetBaseURI(oldURI, _URI);
}

/// @dev Sets `_newBaseExtension` as the `baseExtension` of the NFT.
/// Callable only by the contract owner.
function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
function setBaseExtension(string memory _newBaseExtension) public virtual onlyOwner {
string memory oldBaseExtension = baseExtension;
baseExtension = _newBaseExtension;
emit SetBaseURI(oldBaseExtension, _newBaseExtension);
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 @@ -43,7 +43,7 @@ contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {
/// @dev Returns true if this contract implements the interface defined by `interfaceId`. See EIP165.
function supportsInterface(
bytes4 interfaceId
) public view override(IERC165, ERC721) returns (bool) {
) public view virtual override(IERC165, ERC721) returns (bool) {
return
interfaceId == type(IInverseProjectedNft).interfaceId ||
interfaceId == type(IInverseBaseProjectedNft).interfaceId ||
Expand All @@ -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 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,45 +70,61 @@ 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 onlyTokenOwner(_tokenId) {
function burn(uint256 _tokenId) public virtual onlyTokenOwner(_tokenId) {
totalSupply--;
_burn(_tokenId);
}

/// @dev Returns the `baseURI` of this NFT.
function _baseURI() internal view override returns (string memory) {
function _baseURI() internal view virtual override returns (string memory) {
return baseURI;
}

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

/// @dev Returns the token URI of specified `tokenId` using a custom base URI.
function tokenURI(
uint256 tokenId,
string memory customBaseUri
) public view returns (string memory) {
) public view virtual returns (string memory) {
_requireOwned(tokenId);
string memory URI = bytes(customBaseUri).length > 0
? string.concat(customBaseUri, tokenId.toString())
? string.concat(
customBaseUri,
"eip155:",
block.chainid.toString(),
"/",
tokenId.toString()
)
: "";
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 onlyOwner {
function setBaseURI(string memory _URI) public virtual onlyOwner {
string memory oldURI = baseURI;
baseURI = _URI;
emit SetBaseURI(oldURI, _URI);
}

/// @dev Sets `_newBaseExtension` as the `baseExtension` of the NFT.
/// Callable only by the contract owner.
function setBaseExtension(string memory _newBaseExtension) public onlyOwner {
function setBaseExtension(string memory _newBaseExtension) public virtual onlyOwner {
string memory oldBaseExtension = baseExtension;
baseExtension = _newBaseExtension;
emit SetBaseURI(oldBaseExtension, _newBaseExtension);
Expand Down
48 changes: 29 additions & 19 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 @@ -44,7 +57,7 @@ contract InverseAppProjectedNftTest is CTest {
assertEq(
result,
string.concat(
"192.168.0.1/",
"192.168.0.1/eip155:31337/",
Strings.toHexString(uint160(address(this)), 20),
"/",
"1.json"
Expand All @@ -57,14 +70,20 @@ contract InverseAppProjectedNftTest is CTest {
assertEq(
result,
string.concat(
"1.1.0.0/",
"1.1.0.0/eip155:31337/",
Strings.toHexString(uint160(address(this)), 20),
"/",
"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 @@ -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;
}
}
Loading

0 comments on commit 04714b1

Please sign in to comment.