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

Set the metadata params in the initilization #293

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
5 changes: 4 additions & 1 deletion deploy/08_countdown_erc721.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,14 @@ const func: DeployFunction = async function (hre1: HardhatRuntimeEnvironment) {

// Deploy the CountdownERC721 custom contract source
const CountdownERC721InitCode = generateInitCode(
['tuple(string,string,string,string,uint40,uint32,uint24,address,address,address,string,tuple(uint104,uint24))'],
[
'tuple(string,string,string,string,string,uint40,uint32,uint24,address,address,address,string,tuple(uint104,uint24))',
],
[
[
'', // Description
'', // imageURI
'', // animationURI
'', // externalLink
'', // encryptedMediaURI
1718822400, // Epoch time for June 3, 2024
Expand Down
5 changes: 4 additions & 1 deletion deploy/20_register_templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,11 +527,14 @@ const func: DeployFunction = async function (hre1: HardhatRuntimeEnvironment) {

// Deploy the CountdownERC721 custom contract source
const CountdownERC721InitCode = generateInitCode(
['tuple(string,string,string,string,uint40,uint32,uint24,address,address,address,string,tuple(uint104,uint24))'],
[
'tuple(string,string,string,string,string,uint40,uint32,uint24,address,address,address,string,tuple(uint104,uint24))',
],
[
[
'', // Description
'', // imageURI
'', // animationURI
'', // externalLink
'', // encryptedMediaURI
1718822400, // Epoch time for June 3, 2024
Expand Down
12 changes: 1 addition & 11 deletions src/struct/CountdownERC721Initializer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,10 @@ pragma solidity 0.8.13;

import {CustomERC721SalesConfiguration} from "src/struct/CustomERC721SalesConfiguration.sol";

/// @title A struct for initializing a CountdownERC721 contract
/// @dev This struct is used during the deployment of a CountdownERC721 to set initial configuration parameters.
/// @param description The description of the token.
/// @param startDate The maximum start date
/// @param initialMaxSupply The maximum initial supply.
/// @param mintInterval The maximum interval between mints
/// @param initialOwner The user that owns the contract, can mint tokens, receives royalty and sales payouts, and can update the base URL if needed.
/// @param initialMinter The user that is allowed to mint tokens on behalf of others, typically for offchain purchasers.
/// @param fundsRecipient The wallet or user that receives funds from token sales.
/// @param contractURI The URI for the contract metadata.
/// @param salesConfiguration The initial sales configuration settings, defining how tokens are sold.
struct CountdownERC721Initializer {
string description; // The description of the token.
string imageURI; // The URI for the image associated with this contract.
string animationURI; // The URI for the animation associated with this contract.
string externalLink; // The URI for the external metadata associated with this contract.
string encryptedMediaURI; // The URI for the encrypted media associated with this contract.
uint40 startDate; // The starting date for the countdown
Expand Down
126 changes: 91 additions & 35 deletions src/token/CountdownERC721.sol
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,11 @@ import {NFTMetadataRenderer} from "../library/NFTMetadataRenderer.sol";
contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC721 {
using Strings for uint256;

// TODO: Update the base image URI
string private constant BASE_IMAGE_URI = "ipfs://QmNMraA4KcB1epgWfqN6krn2WUyT4qpaQzbEpMhXjBCNCW/nft.png";
string private constant BASE_ANIMATION_URI = ""; // Define if you have a specific animation URI

/* -------------------------------------------------------------------------- */
/* CONTRACT VARIABLES */
/* all variables, without custom storage slots, are defined here */
/* -------------------------------------------------------------------------- */

/// @notice Getter for the description
/// @dev This storage variable is set only once in the init and can be considered as immutable
string public DESCRIPTION;

/// @notice Getter for the purchase start date
/// @dev This storage variable is set only once in the init and can be considered as immutable
uint256 public START_DATE;
Expand Down Expand Up @@ -73,6 +65,44 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
/// @dev This account tokens on behalf of those that purchase them offchain
address public minter;

/* -------------------------------------------------------------------------- */
/* METADATA VARAIBLES */
/* -------------------------------------------------------------------------- */

/// @notice Getter for the description
/// @dev This storage variable is set only once in the init and can be considered as immutable
string public DESCRIPTION;

/// @notice Getter for the base image URI
/// @dev This storage variable is set during the init and can be updated by the owner
string public IMAGE_URI;

/// @notice Getter for the base animation URI
/// @dev This storage variable is set during the init and can be updated by the owner
string public ANIMATION_URI;

/// @notice Getter for the external url
/// @dev This storage variable is set during the init and can be updated by the owner
string public EXTERNAL_URL;

/// @notice Getter for the encrypted media URI
/// @dev This storage variable is set during the init and can be updated by the owner
string public ENCRYPTED_MEDIA_URL;

/// @notice Getter for the decryption key
/// @dev This storage variable is set during the init and can be updated by the owner
string public DECRYPTION_KEY;

/// @notice Getter for the hash
/// @dev This storage variable is set during the init and can be updated by the owner
string public HASH;

/// @notice Getter for the decrypted media URI
/// @dev This storage variable is set during the init and can be updated by the owner
string public DECRYPTED_MEDIA_URI;

/* -------------------------------------------------------------------------- */

/**
* @dev Address of the price oracle proxy
*/
Expand All @@ -83,11 +113,6 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
*/
uint224 private _currentTokenId;

/**
* @dev Internal reference to the base URI
*/
string private _baseURI;

/// @dev Gas limit for transferring funds
uint256 private constant STATIC_GAS_LIMIT = 210_000;

Expand Down Expand Up @@ -164,20 +189,19 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
// Decode the initializer payload to get the CountdownERC721Initializer struct
CountdownERC721Initializer memory initializer = abi.decode(initPayload, (CountdownERC721Initializer));

_setupContractURI(initializer.contractURI);
/* -------------------------------------------------------------------------- */
/* ADMIN */
/* -------------------------------------------------------------------------- */

// Setup the owner role
_setOwner(initializer.initialOwner);

// Setup the minter role
_setMinter(initializer.initialMinter);

// Setup the contract URI

// Set the description
/// @dev The description is a human-readable description of the token.
/// The description is used like an immutable.
DESCRIPTION = initializer.description;
/* -------------------------------------------------------------------------- */
/* SALES CONFIG */
/* -------------------------------------------------------------------------- */

// Set the sale start date.
/// @dev The sale start date represents the date when the public sale starts.
Expand Down Expand Up @@ -210,6 +234,32 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
// Set the sales configuration
salesConfig = initializer.salesConfiguration;

/* -------------------------------------------------------------------------- */
/* METADATA */
/* -------------------------------------------------------------------------- */

_setupContractURI(initializer.contractURI);

// Set the description
/// @dev The description is a human-readable description of the token.
/// The description is used like an immutable.
DESCRIPTION = initializer.description;

// Set the image URI
/// @dev The image URI is the base URI for the images associated with the tokens.
IMAGE_URI = initializer.imageURI;

// Set the animation URI
/// @dev The animation URI is the base URI for the animations associated with the tokens.
ANIMATION_URI = initializer.animationURI;

// Set the external link
/// @dev The external link is the base URI for the external metadata associated with the tokens.
EXTERNAL_URL = initializer.externalLink;

// Set the hash
/// @dev The hash is a unique hash associated with the tokens.

setStatus(1);

return _init(initPayload);
Expand Down Expand Up @@ -318,13 +368,13 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
MetadataParams memory params = MetadataParams({
name: _name,
description: DESCRIPTION,
imageURI: BASE_IMAGE_URI,
animationURI: BASE_ANIMATION_URI,
externalUrl: "https://your-nft-project.com", // This should be dynamically set or fetched
encryptedMediaUrl: "ar://encryptedMediaUriHere", // This should be dynamically set or fetched
decryptionKey: "decryptionKeyHere", // This should be dynamically set or fetched
hash: "uniqueNftHashHere", // This should be dynamically set or fetched
decryptedMediaUrl: "ar://decryptedMediaUriHere", // This should be dynamically set or fetched
imageURI: IMAGE_URI,
animationURI: ANIMATION_URI,
externalUrl: EXTERNAL_URL,
encryptedMediaUrl: ENCRYPTED_MEDIA_URL,
decryptionKey: DECRYPTION_KEY,
hash: HASH,
decryptedMediaUrl: DECRYPTED_MEDIA_URI,
tokenOfEdition: tokenId,
editionSize: 0 // Set or fetch dynamically if applicable
});
Expand Down Expand Up @@ -413,6 +463,20 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
/* admin only */
/* -------------------------------------------------------------------------- */

/**
* @dev Returns the metadata params for the contract
* @notice This only sets the subset of metadata params that are settable by the owner
*/
function setMetadataParams(MetadataParams memory params) external onlyOwner {
IMAGE_URI = params.imageURI;
ANIMATION_URI = params.animationURI;
EXTERNAL_URL = params.externalUrl;
ENCRYPTED_MEDIA_URL = params.encryptedMediaUrl;
DECRYPTION_KEY = params.decryptionKey;
HASH = params.hash;
DECRYPTED_MEDIA_URI = params.decryptedMediaUrl;
}

/**
* @notice Minter account mints tokens to a recipient that has paid offchain
* @param recipient recipient to mint to
Expand Down Expand Up @@ -488,14 +552,6 @@ contract CountdownERC721 is NonReentrant, ContractMetadata, ERC721H, ICustomERC7
/* non state changing */
/* -------------------------------------------------------------------------- */

/**
* @dev Internal function to return the base URI stored in the contract.
* Used to construct the URI for each token.
*/
function baseURI() internal view returns (string memory) {
return _baseURI;
}

/// @notice Checks whether contract metadata can be set in the given execution context.
function _canSetContractURI() internal view override returns (bool) {
return msgSender() == _getOwner();
Expand Down
76 changes: 70 additions & 6 deletions test/foundry/CountdownERC721/CountdownERC721.tokenUri.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {CountdownERC721Fixture} from "test/foundry/fixtures/CountdownERC721Fixtu
import {Vm} from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";

import {MetadataParams} from "src/struct/MetadataParams.sol";

import {DEFAULT_BASE_URI, DEFAULT_PLACEHOLDER_URI, DEFAULT_ENCRYPT_DECRYPT_KEY, DEFAULT_MAX_SUPPLY} from "test/foundry/CountdownERC721/utils/Constants.sol";

import {ICountdownERC721} from "src/interface/ICountdownERC721.sol";
Expand Down Expand Up @@ -38,24 +40,86 @@ contract CountdownERC721PurchaseTest is CountdownERC721Fixture, ICustomERC721Err
// {
// "name": "Contract Name 115792089183396302089269705419353877679230723318366275194376439045705909141505",
// "description": "Description of the token",
// "external_url": "https://your-nft-project.com",
// "image": "ipfs://QmNMraA4KcB1epgWfqN6krn2WUyT4qpaQzbEpMhXjBCNCW/nft.png",
// "external_url": "https://example.com",
// "image": ar://o8eyC27OuSZF0z-zIen5NTjJOKTzOQzKJzIe3F7Lmg0/1.png",
// "encrypted_media_url": "",
// "decryption_key": "",
// "hash": "",
// "decrypted_media_url": "",
// "animation_url": "",
// "properties": {
// "number": 115792089183396302089269705419353877679230723318366275194376439045705909141505,
// "name": "Contract Name"
// }
// }
string memory expectedTokenUri = NFTMetadataRenderer.encodeMetadataJSON(
'{"name": "Contract Name 115792089183396302089269705419353877679230723318366275194376439045705909141505", "description": "Description of the token", "external_url": "https://example.com", "image": "ar://o8eyC27OuSZF0z-zIen5NTjJOKTzOQzKJzIe3F7Lmg0/1.png", "encrypted_media_url": "", "decryption_key": "", "hash": "", "decrypted_media_url": "", "animation_url": "", "properties": {"number": 115792089183396302089269705419353877679230723318366275194376439045705909141505, "name": "Contract Name"}}'
);

string memory base64TokenUri = countdownErc721.tokenURI(tokenId);

console.log("base64TokenUri: ", base64TokenUri);

assertEq(base64TokenUri, expectedTokenUri, "Incorrect tokenURI for newly minted token");
}

function test_setMetadataParams() public setupTestCountdownErc721(DEFAULT_MAX_SUPPLY) setUpPurchase {
/* -------------------------------- Purchase -------------------------------- */
vm.prank(address(TEST_ACCOUNT));
vm.deal(address(TEST_ACCOUNT), totalCost);
uint256 tokenId = countdownErc721.purchase{value: totalCost}(1);

// First token ID is this long number due to the chain id prefix
require(erc721Enforcer.ownerOf(tokenId) == address(TEST_ACCOUNT), "Incorrect owner for newly minted token");
assertEq(address(sourceContractAddress).balance, nativePrice);

/* ----------------------------- Check tokenURI ----------------------------- */

// assertEq(base64TokenUri, expectedTokenUri, "Incorrect tokenURI for newly minted token");

/* ----------------------------- Set Metadata Params ----------------------------- */

// Expected token URI for newly minted token
// {
// "name": "Contract Name 115792089183396302089269705419353877679230723318366275194376439045705909141505",
// "description": "Description of the token",
// "external_url": "https://example.com",
// "image": ar://o8eyC27OuSZF0z-zIen5NTjJOKTzOQzKJzIe3F7Lmg0/1.png",
// "encrypted_media_url": "ar://encryptedMediaUriHere",
// "decryption_key": "decryptionKeyHere",
// "hash": "uniqueNftHashHere",
// "hash": "uniqueHashHere",
// "decrypted_media_url": "ar://decryptedMediaUriHere",
// "animation_url": "",
// "animation_url": "ar://animationUriHere",
// "properties": {
// "number": 115792089183396302089269705419353877679230723318366275194376439045705909141505,
// "name": "Contract Name"
// }
// }
string memory expectedTokenUri = NFTMetadataRenderer.encodeMetadataJSON(
'{"name": "Contract Name 115792089183396302089269705419353877679230723318366275194376439045705909141505", "description": "Description of the token", "external_url": "https://your-nft-project.com", "image": "ipfs://QmNMraA4KcB1epgWfqN6krn2WUyT4qpaQzbEpMhXjBCNCW/nft.png", "encrypted_media_url": "ar://encryptedMediaUriHere", "decryption_key": "decryptionKeyHere", "hash": "uniqueNftHashHere", "decrypted_media_url": "ar://decryptedMediaUriHere", "animation_url": "", "properties": {"number": 115792089183396302089269705419353877679230723318366275194376439045705909141505, "name": "Contract Name"}}'
'{"name": "Contract Name 115792089183396302089269705419353877679230723318366275194376439045705909141505", "description": "Description of the token", "external_url": "https://example.com", "image": "ar://o8eyC27OuSZF0z-zIen5NTjJOKTzOQzKJzIe3F7Lmg0/1.png", "encrypted_media_url": "ar://encryptedMediaUriHere", "decryption_key": "decryptionKeyHere", "hash": "uniqueHashHere", "decrypted_media_url": "ar://decryptedMediaUriHere", "animation_url": "ar://animationUriHere", "properties": {"number": 115792089183396302089269705419353877679230723318366275194376439045705909141505, "name": "Contract Name"}}'
);

string memory base64TokenUri = countdownErc721.tokenURI(tokenId);
// NOTE: The metadata params struct needs to have all it's values set,
// but the setMetadataParams function only sets the imageURI, externalUrl,
// encryptedMediaUrl, decryptionKey, hash, and decryptedMediaUrl
MetadataParams memory metadataParams = MetadataParams({
name: "Contract Name", // NOT USED
description: "Description of the token", // NOT USED
tokenOfEdition: 0, // NOT USED
editionSize: 0, // NOT USED
imageURI: "ar://o8eyC27OuSZF0z-zIen5NTjJOKTzOQzKJzIe3F7Lmg0/1.png",
animationURI: "ar://animationUriHere",
externalUrl: "https://example.com",
encryptedMediaUrl: "ar://encryptedMediaUriHere",
decryptionKey: "decryptionKeyHere",
hash: "uniqueHashHere",
decryptedMediaUrl: "ar://decryptedMediaUriHere"
});

vm.prank(address(DEFAULT_OWNER_ADDRESS));
countdownErc721.setMetadataParams(metadataParams);

string memory base64TokenUri = countdownErc721.tokenURI(tokenId);
console.log("base64TokenUri: ", base64TokenUri);

assertEq(base64TokenUri, expectedTokenUri, "Incorrect tokenURI for newly minted token");
Expand Down
5 changes: 3 additions & 2 deletions test/foundry/fixtures/CountdownERC721Fixture.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -178,8 +178,9 @@ contract CountdownERC721Fixture is Test {
// Create initializer
CountdownERC721Initializer memory initializer = CountdownERC721Initializer({
description: "Description of the token",
imageURI: "https://example.com/image.png",
externalLink: "https://example.com/",
imageURI: "ar://o8eyC27OuSZF0z-zIen5NTjJOKTzOQzKJzIe3F7Lmg0/1.png",
animationURI: "",
externalLink: "https://example.com",
encryptedMediaURI: "xxx",
startDate: DEFAULT_START_DATE,
initialMaxSupply: maxSupply,
Expand Down