Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated inverse projection #309

Merged
merged 6 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/contracts/evm-contracts/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ Core contracts
State-annotated contracts
<ul>
<li>[AnnotatedMintNft](#AnnotatedMintNft): A standard ERC721 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.</li>
<li>[InverseProjectedNft](#InverseProjectedNft): Project game state into a ERC721 NFT on an EVM layer.</li>
<li>[InverseBaseProjectedNft](#InverseBaseProjectedNft): Project game state into a ERC721 NFT on an EVM layer initiated on said base layer.</li>
<li>[InverseAppProjectedNft](#InverseAppProjectedNft): Project game state into a ERC721 NFT on an EVM layer initiated on the app layer.</li>
</ul>
Facilitating monetization
<ul>
Expand All @@ -28,7 +29,10 @@ Facilitating monetization
{{AnnotatedMintNft}}

{{IInverseProjectedNft}}
{{InverseProjectedNft}}
{{IInverseBaseProjectedNft}}
{{InverseBaseProjectedNft}}
{{IInverseAppProjectedNft}}
{{InverseAppProjectedNft}}

## Facilitating monetization

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IInverseProjectedNft} from "./IInverseProjectedNft.sol";

/// @dev A Paima Inverse Projection NFT where initialization is handled by the app-layer.
/// A standard ERC721 that can be freely minted and stores an unique <minter, userTokenId> pair (used in tokenURI) when minted.
interface IInverseAppProjectedNft is IInverseProjectedNft {
/// @dev Emitted when the globally-enforced tokenId in combination with an unique <minter, userTokenId> pair is minted.
event Minted(uint256 indexed tokenId, address indexed minter, uint256 indexed userTokenId);

/// @dev Mints a new token to address `_to`
/// 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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {IInverseProjectedNft} from "./IInverseProjectedNft.sol";

/// @dev A Paima Inverse Projection NFT where initialization is handled by the base-layer.
/// A standard ERC721 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
interface IInverseBaseProjectedNft is IInverseProjectedNft {
/// @dev Emitted when the globally-enforced tokenId is minted, with `initialData` provided in the `mint` function parameters.
event Minted(uint256 indexed tokenId, string initialData);

/// @dev Mints a new token to address `_to`, passing `initialData` to be emitted in the event.
/// 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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,14 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IERC4906} from "@openzeppelin/contracts/interfaces/IERC4906.sol";

/// @dev A standard ERC721 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
/// @dev A standard ERC721 that can be burned and has a special tokenURI function accepting a custom base URI.
interface IInverseProjectedNft is IERC4906 {
/// @dev Emitted when `baseExtension` is updated from `oldBaseExtension` to `newBaseExtension`.
event SetBaseExtension(string oldBaseExtension, string newBaseExtension);

/// @dev Emitted when `baseUri` is updated from `oldUri` to `newUri`.
event SetBaseURI(string oldUri, string newUri);

/// @dev Emitted when a new token with ID `tokenId` is minted, with `initialData` provided in the `mint` function parameters.
event Minted(uint256 indexed tokenId, string initialData);

/// @dev Mints a new token to address `_to`, passing `initialData` to be emitted in the event.
/// Increases the `totalSupply` and `currentTokenId`.
/// Reverts if `totalSupply` is not less than `maxSupply` or if `_to` is a zero address.
/// Emits the `Minted` event.
function mint(address _to, string calldata initialData) external returns (uint256);

/// @dev Burns token of ID `_tokenId`. Callable only by the owner of the specified token.
matejos marked this conversation as resolved.
Show resolved Hide resolved
/// Reverts if `_tokenId` is not existing.
function burn(uint256 _tokenId) external;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
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 {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";

struct MintEntry {
address minter;
uint256 userTokenId;
}

/// @dev A standard ERC721 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
contract InverseAppProjectedNft is IInverseAppProjectedNft, ERC721, Ownable {
using Strings for uint256;

/// @dev tokenId => MintEntry.
mapping(uint256 => MintEntry) public tokenToMint;
mapping(address => uint256) public mintCount;

/// @dev The token ID that will be minted when calling the `mint` function.
uint256 public currentTokenId;
/// @dev Base URI that is used in the `tokenURI` function to form the start of the token URI.
string public baseURI;
/// @dev Total token supply, increased by minting and decreased by burning.
uint256 public totalSupply;
/// @dev Base extension that is used in the `tokenURI` function to form the end of the token URI.
string public baseExtension;

/// @dev Reverts if `msg.sender` is not the specified token's owner.
modifier onlyTokenOwner(uint256 tokenId) {
require(msg.sender == ownerOf(tokenId), "InverseAppProjectedNft: not owner");
_;
}

/// @dev Sets the NFT's `name`, `symbol`, and transfers ownership to `owner`.
/// Also sets `currentTokenId` to 1 and `baseExtension` to `".json"`.
constructor(
string memory name,
string memory symbol,
address owner
) ERC721(name, symbol) Ownable(owner) {
currentTokenId = 1;
baseExtension = ".json";
}

/// @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) {
return
interfaceId == type(IInverseProjectedNft).interfaceId ||
interfaceId == type(IInverseAppProjectedNft).interfaceId ||
interfaceId == bytes4(0x49064906) ||
super.supportsInterface(interfaceId);
}

/// @dev Mints a new token to address `_to`.
/// 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) {
require(_to != address(0), "InverseAppProjectedNft: zero receiver address");

uint256 tokenId = currentTokenId;
_safeMint(_to, tokenId);
mintCount[_to] += 1;
uint256 userTokenId = mintCount[_to];
tokenToMint[tokenId] = MintEntry(_to, userTokenId);

totalSupply++;
currentTokenId++;

emit Minted(tokenId, _to, userTokenId);
return tokenId;
}

/// @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) {
totalSupply--;
_burn(_tokenId);
}

/// @dev Returns the `baseURI` of this NFT.
function _baseURI() internal view 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) {
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) {
_requireOwned(tokenId);
MintEntry memory entry = tokenToMint[tokenId];
string memory URI = bytes(customBaseUri).length > 0
? string.concat(
customBaseUri,
Strings.toHexString(uint160(entry.minter), 20),
"/",
entry.userTokenId.toString()
)
: "";
return string(abi.encodePacked(URI, baseExtension));
}

/// @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 {
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 {
string memory oldBaseExtension = baseExtension;
baseExtension = _newBaseExtension;
emit SetBaseURI(oldBaseExtension, _newBaseExtension);
}

/// @dev Function that emits an event to notify third-parties (e.g. NFT marketplaces) about
/// an update to consecutive range of tokens. Can be overriden in inheriting contract.
function updateMetadataBatch(uint256 _fromTokenId, uint256 _toTokenId) public virtual {
emit BatchMetadataUpdate(_fromTokenId, _toTokenId);
}

/// @dev Function that emits an event to notify third-parties (e.g. NFT marketplaces) about
/// an update to a single token. Can be overriden in inheriting contract.
function updateMetadata(uint256 _tokenId) public virtual {
emit MetadataUpdate(_tokenId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ 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";

/// @dev A standard ERC721 that accepts calldata in the mint function for any initialization data needed in a Paima dApp.
contract InverseProjectedNft is IInverseProjectedNft, ERC721, Ownable {
contract InverseBaseProjectedNft is IInverseBaseProjectedNft, ERC721, Ownable {
using Strings for uint256;

/// @dev The token ID that will be minted when calling the `mint` function.
Expand All @@ -24,7 +25,7 @@ contract InverseProjectedNft is IInverseProjectedNft, ERC721, Ownable {

/// @dev Reverts if `msg.sender` is not the specified token's owner.
modifier onlyTokenOwner(uint256 tokenId) {
require(msg.sender == ownerOf(tokenId), "InverseProjectedNft: not owner");
require(msg.sender == ownerOf(tokenId), "InverseBaseProjectedNft: not owner");
_;
}

Expand All @@ -45,6 +46,7 @@ contract InverseProjectedNft is IInverseProjectedNft, ERC721, Ownable {
) public view override(IERC165, ERC721) returns (bool) {
return
interfaceId == type(IInverseProjectedNft).interfaceId ||
interfaceId == type(IInverseBaseProjectedNft).interfaceId ||
interfaceId == bytes4(0x49064906) ||
super.supportsInterface(interfaceId);
}
Expand All @@ -54,7 +56,7 @@ contract InverseProjectedNft is IInverseProjectedNft, ERC721, Ownable {
/// 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) {
require(_to != address(0), "InverseProjectedNft: zero receiver address");
require(_to != address(0), "InverseBaseProjectedNft: zero receiver address");

uint256 tokenId = currentTokenId;
_safeMint(_to, tokenId);
Expand Down
5 changes: 2 additions & 3 deletions packages/contracts/evm-contracts/docs/templates/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,8 @@ function getAllLinks(items) {
linksCache.set(items, res);
for (const item of items) {
res[`xref-${item.anchor}`] = `xref:${item.__item_context.page}#${item.anchor}`;
res[
slug(item.fullName)
] = `pass:normal[xref:${item.__item_context.page}#${item.anchor}[\`${item.fullName}\`]]`;
res[slug(item.fullName)] =
`pass:normal[xref:${item.__item_context.page}#${item.anchor}[\`${item.fullName}\`]]`;
}
return res;
}
Expand Down
Loading