From 9e89c97c4c80b78d8cde01e5a35cabb23c87ecd3 Mon Sep 17 00:00:00 2001 From: Kingster Date: Wed, 6 Nov 2024 18:53:48 -0800 Subject: [PATCH 1/3] Support automatic cache --- contracts/story-nft/CachableNFT.sol | 34 +++++++++++++++++++++++------ test/story-nft/StoryBadgeNFT.t.sol | 28 ++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 9 deletions(-) diff --git a/contracts/story-nft/CachableNFT.sol b/contracts/story-nft/CachableNFT.sol index ed01950..fea7584 100644 --- a/contracts/story-nft/CachableNFT.sol +++ b/contracts/story-nft/CachableNFT.sol @@ -12,12 +12,21 @@ import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableM import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; abstract contract CachableNFT is OwnableUpgradeable { using EnumerableMap for EnumerableMap.UintToAddressMap; + + enum CacheMode { + Passthrough, + Cache, + Auto + } + /// @dev Storage structure for the CacheableNFT /// @custom:storage-location erc7201:story-protocol-periphery.CacheableNFT struct CacheableNFTStorage { // tokenId => ipId EnumerableMap.UintToAddressMap cache; - bool cacheMode; + bool DEPRECATED_cacheMode; + CacheMode mode; + uint256 autoCacheBaseFeeThreshold; } // keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.CacheableNFT")) - 1)) & ~bytes32(uint256(0xff)); @@ -25,10 +34,15 @@ abstract contract CachableNFT is OwnableUpgradeable { 0xb2c28ba4bb2a3f74a63ac2785b5af0c41313804d8b65acc69c0b2736a57e5f00; /// @notice Sets the cache mode. - /// @param useCache The new cache mode, true for cache mode, false for passthrough mode. - function setCacheMode(bool useCache) external onlyOwner { + /// @param mode The new cache mode. + function setCacheMode(CacheMode mode) external onlyOwner { + CacheableNFTStorage storage $ = _getCacheableNFTStorage(); + $.mode = mode; + } + + function setCacheModeAutoThreshold(uint256 threshold) external onlyOwner { CacheableNFTStorage storage $ = _getCacheableNFTStorage(); - $.cacheMode = useCache; + $.autoCacheBaseFeeThreshold = threshold; } /// @notice Mints NFTs to the cache. @@ -51,8 +65,8 @@ abstract contract CachableNFT is OwnableUpgradeable { /// @notice Returns the cache mode. /// @return The cache mode, true for cache mode, false for passthrough mode. - function getCacheMode() external view returns (bool) { - return _getCacheableNFTStorage().cacheMode; + function getCacheMode() external view returns (CacheMode) { + return _getCacheableNFTStorage().mode; } /// @notice Returns the NFT at the given index in the cache. @@ -69,9 +83,15 @@ abstract contract CachableNFT is OwnableUpgradeable { /// @return ipId The IP ID of the transferred NFT. function _transferFromCache(address recipient) internal returns (uint256 tokenId, address ipId) { CacheableNFTStorage storage $ = _getCacheableNFTStorage(); - if (!$.cacheMode || $.cache.length() == 0) { + if ($.mode == CacheMode.Passthrough || $.cache.length() == 0) { return (0, address(0)); + + } else if($.mode == CacheMode.Auto) { + if (block.basefee <= $.autoCacheBaseFeeThreshold * 1 gwei) { + return (0, address(0)); + } } + (tokenId, ipId) = $.cache.at(0); $.cache.remove(tokenId); diff --git a/test/story-nft/StoryBadgeNFT.t.sol b/test/story-nft/StoryBadgeNFT.t.sol index 5ac4090..35f2cc7 100644 --- a/test/story-nft/StoryBadgeNFT.t.sol +++ b/test/story-nft/StoryBadgeNFT.t.sol @@ -13,6 +13,7 @@ import { IStoryBadgeNFT } from "../../contracts/interfaces/story-nft/IStoryBadge import { IOrgStoryNFT } from "../../contracts/interfaces/story-nft/IOrgStoryNFT.sol"; import { IStoryNFT } from "../../contracts/interfaces/story-nft/IStoryNFT.sol"; import { StoryBadgeNFT } from "../../contracts/story-nft/StoryBadgeNFT.sol"; +import { CachableNFT } from "../../contracts/story-nft/CachableNFT.sol"; // test import { BaseTest } from "../utils/BaseTest.t.sol"; @@ -224,7 +225,7 @@ contract StoryBadgeNFTTest is BaseTest { assertEq(rootOrgStoryNft.cacheSize(), 1); // 1 cached rootOrgStoryNft.mintToCache(100); assertEq(rootOrgStoryNft.cacheSize(), 101); // 100 cached + 1 minted - rootOrgStoryNft.setCacheMode(true); // enable cache mode + rootOrgStoryNft.setCacheMode(CachableNFT.CacheMode.Cache); // enable cache mode vm.stopPrank(); signature = _signAddress(rootOrgStoryNftSignerSk, u.carl); @@ -235,7 +236,7 @@ contract StoryBadgeNFTTest is BaseTest { assertEq(rootOrgStoryNft.cacheSize(), 100); // cache size is reduced by 1 vm.startPrank(rootOrgStoryNftOwner); - rootOrgStoryNft.setCacheMode(false); // disable cache mode + rootOrgStoryNft.setCacheMode(CachableNFT.CacheMode.Passthrough); // disable cache mode vm.stopPrank(); signature = _signAddress(rootOrgStoryNftSignerSk, u.bob); @@ -244,6 +245,29 @@ contract StoryBadgeNFTTest is BaseTest { assertEq(rootOrgStoryNft.ownerOf(tokenId), u.bob); // minted directly vm.stopPrank(); assertEq(rootOrgStoryNft.cacheSize(), 100); // cache size is unchanged + + vm.startPrank(rootOrgStoryNftOwner); + rootOrgStoryNft.setCacheMode(CachableNFT.CacheMode.Auto); // disable cache mode + rootOrgStoryNft.setCacheModeAutoThreshold(100); + vm.stopPrank(); + + vm.fee(20 gwei); + signature = _signAddress(rootOrgStoryNftSignerSk, u.dan); + vm.startPrank(u.dan); + (tokenId, ) = rootOrgStoryNft.mint(u.dan, signature); + assertEq(rootOrgStoryNft.ownerOf(tokenId), u.dan); // minted directly + vm.stopPrank(); + assertEq(rootOrgStoryNft.cacheSize(), 100); // cache size is unchanged + + vm.fee(200 gwei); + address eva = vm.addr(0x123456); + signature = _signAddress(rootOrgStoryNftSignerSk, eva); + vm.startPrank(eva); + (tokenId, ) = rootOrgStoryNft.mint(eva, signature); + assertEq(rootOrgStoryNft.ownerOf(tokenId), eva); // minted from cache + vm.stopPrank(); + assertEq(rootOrgStoryNft.cacheSize(), 99); // cache size is unchanged + } function test_StoryBadgeNFT_revert_setSigner_CallerIsNotOwner() public { From cdd7dfe233ca6b30483f3635232d9783b1392bb5 Mon Sep 17 00:00:00 2001 From: Kingster Date: Wed, 6 Nov 2024 18:54:46 -0800 Subject: [PATCH 2/3] fix lint --- contracts/story-nft/CachableNFT.sol | 3 +-- test/story-nft/StoryBadgeNFT.t.sol | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/story-nft/CachableNFT.sol b/contracts/story-nft/CachableNFT.sol index fea7584..bfead3f 100644 --- a/contracts/story-nft/CachableNFT.sol +++ b/contracts/story-nft/CachableNFT.sol @@ -85,8 +85,7 @@ abstract contract CachableNFT is OwnableUpgradeable { CacheableNFTStorage storage $ = _getCacheableNFTStorage(); if ($.mode == CacheMode.Passthrough || $.cache.length() == 0) { return (0, address(0)); - - } else if($.mode == CacheMode.Auto) { + } else if ($.mode == CacheMode.Auto) { if (block.basefee <= $.autoCacheBaseFeeThreshold * 1 gwei) { return (0, address(0)); } diff --git a/test/story-nft/StoryBadgeNFT.t.sol b/test/story-nft/StoryBadgeNFT.t.sol index 35f2cc7..eb92a75 100644 --- a/test/story-nft/StoryBadgeNFT.t.sol +++ b/test/story-nft/StoryBadgeNFT.t.sol @@ -267,7 +267,6 @@ contract StoryBadgeNFTTest is BaseTest { assertEq(rootOrgStoryNft.ownerOf(tokenId), eva); // minted from cache vm.stopPrank(); assertEq(rootOrgStoryNft.cacheSize(), 99); // cache size is unchanged - } function test_StoryBadgeNFT_revert_setSigner_CallerIsNotOwner() public { From f5a858eca5ce9e636b9b3aaeada56b587db1f4a4 Mon Sep 17 00:00:00 2001 From: Kingster Date: Thu, 7 Nov 2024 02:04:55 -0800 Subject: [PATCH 3/3] Add more comments. --- contracts/story-nft/CachableNFT.sol | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/contracts/story-nft/CachableNFT.sol b/contracts/story-nft/CachableNFT.sol index bfead3f..b447044 100644 --- a/contracts/story-nft/CachableNFT.sol +++ b/contracts/story-nft/CachableNFT.sol @@ -3,16 +3,19 @@ pragma solidity ^0.8.26; // two mode passthrough and cache // passthrough will just forward the call to the nft contract -// cache contrat has two modes +// cache contrat has three modes // 1. cache mode // 2. passthrough mode +// 3. auto mode // cache mode will cache the nft data and return it // passthrough mode will forward the call to the nft contract +// auto mode will cache the nft data if the basefee is greater than the threshold import { EnumerableMap } from "@openzeppelin/contracts/utils/structs/EnumerableMap.sol"; import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; abstract contract CachableNFT is OwnableUpgradeable { using EnumerableMap for EnumerableMap.UintToAddressMap; + /// @dev Enum for cache mode. enum CacheMode { Passthrough, Cache, @@ -40,6 +43,8 @@ abstract contract CachableNFT is OwnableUpgradeable { $.mode = mode; } + /// @notice Sets the auto cache base fee threshold. + /// @param threshold The new auto cache base fee threshold. function setCacheModeAutoThreshold(uint256 threshold) external onlyOwner { CacheableNFTStorage storage $ = _getCacheableNFTStorage(); $.autoCacheBaseFeeThreshold = threshold;