Skip to content

Commit b674258

Browse files
authored
Revamp SPG with NFT collection (#5)
* nit: config changes * feat: New SPG and NFT collection * feat: Modify deploy script and upgrade * refactor: remove unused tests & add proxy helper * lint: solhint * feat: More wrapper fns and natspec commentswith interface * feat: SPGNFT roles & error * fix: SPGNFT code structure & natspec comment * feat: more features, sig-based actions, interfaces * feat: more features, upgrades, errors, token payment * feat(test): set up new tests & remove unused tests * feat(script): Script updates * feat: allow special mint NFT by SPG * nit: config * feat: create3 for script & test, local core deployment for non-fork test * fix: fn return order * fix: base setup for tests * fix: caller PKs and registered modules * feat(test): more tests * feat: fix functions, remove unused code, constructor variable, error * feat(test): More tests for SPG & base setup * fix(script): deployment order & seed, read core chain json * nit: remove receive() in SPGNFT * fix: makefile commands * feat: new deployment addresses on sepolia * fix(test): reorder deploy & salt * feat: more wrappers, metadata in fns, structs for params * feat: more tests for new features & changes * nit: add comments * feat: add event for collection creation * feat: remove AccessControlled inheritance * fix: mint fee name, interface for mint fee token, test * lint: solhint * fix: LT transfer owner check, metadata check
1 parent 768d9fb commit b674258

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+2146
-3070
lines changed

Makefile

+5-12
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
-include .env
22

3-
.PHONY: all test clean coverage typechain deploy-main
3+
.PHONY: all test clean coverage typechain deploy-main abi
44

55
all: clean install build
66

@@ -51,16 +51,10 @@ coverage:
5151
genhtml coverage/lcov.info -o coverage --rc branch_coverage=1 --ignore-errors category
5252

5353
abi:
54+
rm -rf abi
5455
mkdir -p abi
55-
@$(call generate_abi,"AccessController",".")
56-
@$(call generate_abi,"DisputeModule","./modules/dispute-module")
57-
@$(call generate_abi,"RoyaltyModule","./modules/royalty-module")
58-
@$(call generate_abi,"TaggingModule","./modules/tagging")
59-
@$(call generate_abi,"IPAccountRegistry","./registries")
60-
@$(call generate_abi,"IPRecordRegistry","./registries")
61-
@$(call generate_abi,"LicenseRegistry","./registries")
62-
@$(call generate_abi,"ModuleRegistry","./registries")
63-
@$(call generate_abi,"IPMetadataResolver","./resolvers")
56+
@$(call generate_abi,"StoryProtocolGateway",".")
57+
@$(call generate_abi,"SPGNFT",".")
6458

6559
# typechain:
6660
# make abi
@@ -71,8 +65,7 @@ typechain :; npx hardhat typechain
7165
# solhint should be installed globally
7266
lint :; npx solhint contracts/**/*.sol && npx solhint contracts/*.sol
7367

74-
deploy-goerli :; npx hardhat run ./script/deploy-reveal-engine.js --network goerli
75-
verify-goerli :; npx hardhat verify --network goerli ${contract}
68+
deploy-sepolia :; forge script script/Main.s.sol:Main --rpc-url ${RPC_URL} -vvvv --broadcast --etherscan-api-key ${ETHERSCAN_API_KEY} --verify --priority-gas-price 1
7669

7770
anvil :; anvil -m 'test test test test test test test test test test test junk'
7871

contracts/SPGNFT.sol

+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
// SPDX-License-Identifier: BUSL-1.1
2+
pragma solidity ^0.8.23;
3+
4+
import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
5+
import { ERC721Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
6+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8+
9+
import { ISPGNFT } from "./interfaces/ISPGNFT.sol";
10+
import { Errors } from "./lib/Errors.sol";
11+
import { SPGNFTLib } from "./lib/SPGNFTLib.sol";
12+
13+
contract SPGNFT is ISPGNFT, ERC721Upgradeable, AccessControlUpgradeable {
14+
/// @dev Storage structure for the SPGNFTSotrage.
15+
/// @param maxSupply The maximum supply of the collection.
16+
/// @param totalSupply The total minted supply of the collection.
17+
/// @param mintFee The fee to mint an NFT from the collection.
18+
/// @param mintFeeToken The token to pay for minting.
19+
/// @custom:storage-location erc7201:story-protocol-periphery.SPGNFT
20+
struct SPGNFTStorage {
21+
uint32 maxSupply;
22+
uint32 totalSupply;
23+
uint256 mintFee;
24+
address mintFeeToken;
25+
}
26+
27+
// keccak256(abi.encode(uint256(keccak256("story-protocol-periphery.SPGNFT")) - 1)) & ~bytes32(uint256(0xff));
28+
bytes32 private constant SPGNFTStorageLocation = 0x66c08f80d8d0ae818983b725b864514cf274647be6eb06de58ff94d1defb6d00;
29+
30+
/// @dev The address of the SPG contract.
31+
address public immutable SPG_ADDRESS;
32+
33+
/// @notice Modifier to restrict access to the SPG contract.
34+
modifier onlySPG() {
35+
if (msg.sender != SPG_ADDRESS) revert Errors.SPGNFT__CallerNotSPG();
36+
_;
37+
}
38+
39+
/// @custom:oz-upgrades-unsafe-allow constructor
40+
constructor(address spg) {
41+
SPG_ADDRESS = spg;
42+
43+
_disableInitializers();
44+
}
45+
46+
/// @dev Initializes the NFT collection.
47+
/// @dev If mint fee is non-zero, mint token must be set.
48+
/// @param name The name of the collection.
49+
/// @param symbol The symbol of the collection.
50+
/// @param maxSupply The maximum supply of the collection.
51+
/// @param mintFee The fee to mint an NFT from the collection.
52+
/// @param mintFeeToken The token to pay for minting.
53+
/// @param owner The owner of the collection.
54+
function initialize(
55+
string memory name,
56+
string memory symbol,
57+
uint32 maxSupply,
58+
uint256 mintFee,
59+
address mintFeeToken,
60+
address owner
61+
) public initializer {
62+
if (owner == address(0) || (mintFee > 0 && mintFeeToken == address(0)))
63+
revert Errors.SPGNFT__ZeroAddressParam();
64+
if (maxSupply == 0) revert Errors.SPGNFT_ZeroMaxSupply();
65+
66+
_grantRole(SPGNFTLib.ADMIN_ROLE, owner);
67+
_grantRole(SPGNFTLib.MINTER_ROLE, owner);
68+
69+
// grant roles to SPG
70+
if (owner != SPG_ADDRESS) {
71+
_grantRole(SPGNFTLib.ADMIN_ROLE, SPG_ADDRESS);
72+
_grantRole(SPGNFTLib.MINTER_ROLE, SPG_ADDRESS);
73+
}
74+
75+
SPGNFTStorage storage $ = _getSPGNFTStorage();
76+
$.maxSupply = maxSupply;
77+
$.mintFee = mintFee;
78+
$.mintFeeToken = mintFeeToken;
79+
80+
__ERC721_init(name, symbol);
81+
}
82+
83+
/// @notice Returns the total minted supply of the collection.
84+
function totalSupply() public view returns (uint256) {
85+
return uint256(_getSPGNFTStorage().totalSupply);
86+
}
87+
88+
/// @notice Returns the current mint token of the collection.
89+
function mintFeeToken() public view returns (address) {
90+
return _getSPGNFTStorage().mintFeeToken;
91+
}
92+
93+
/// @notice Returns the current mint fee of the collection.
94+
function mintFee() public view returns (uint256) {
95+
return _getSPGNFTStorage().mintFee;
96+
}
97+
98+
/// @notice Sets the mint token for the collection.
99+
/// @dev Only callable by the admin role.
100+
/// @param token The new mint token for mint payment.
101+
function setMintFeeToken(address token) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
102+
_getSPGNFTStorage().mintFeeToken = token;
103+
}
104+
105+
/// @notice Sets the fee to mint an NFT from the collection. Payment is in the designated currency.
106+
/// @dev Only callable by the admin role.
107+
/// @param fee The new mint fee paid in the mint token.
108+
function setMintFee(uint256 fee) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
109+
_getSPGNFTStorage().mintFee = fee;
110+
}
111+
112+
/// @notice Mints an NFT from the collection. Only callable by the minter role.
113+
/// @param to The address of the recipient of the minted NFT.
114+
/// @return tokenId The ID of the minted NFT.
115+
function mint(address to) public onlyRole(SPGNFTLib.MINTER_ROLE) returns (uint256 tokenId) {
116+
tokenId = _mintFeeToken({ to: to, payer: msg.sender });
117+
}
118+
119+
/// @notice Mints an NFT from the collection. Only callable by the SPG.
120+
/// @param to The address of the recipient of the minted NFT.
121+
/// @param payer The address of the payer for the mint fee.
122+
/// @return tokenId The ID of the minted NFT.
123+
function mintBySPG(address to, address payer) public onlySPG returns (uint256 tokenId) {
124+
tokenId = _mintFeeToken({ to: to, payer: payer });
125+
}
126+
127+
/// @dev Withdraws the contract's token balance to the recipient.
128+
/// @param recipient The token to withdraw.
129+
/// @param recipient The address to receive the withdrawn balance.
130+
function withdrawToken(address token, address recipient) public onlyRole(SPGNFTLib.ADMIN_ROLE) {
131+
IERC20(token).transfer(recipient, IERC20(token).balanceOf(address(this)));
132+
}
133+
134+
/// @dev Supports ERC165 interface.
135+
/// @param interfaceId The interface identifier.
136+
function supportsInterface(
137+
bytes4 interfaceId
138+
) public view virtual override(AccessControlUpgradeable, ERC721Upgradeable, IERC165) returns (bool) {
139+
return interfaceId == type(ISPGNFT).interfaceId || super.supportsInterface(interfaceId);
140+
}
141+
142+
/// @dev Mints an NFT from the collection.
143+
/// @param to The address of the recipient of the minted NFT.
144+
/// @param payer The address of the payer for the mint fee.
145+
/// @return tokenId The ID of the minted NFT.
146+
function _mintFeeToken(address to, address payer) internal returns (uint256 tokenId) {
147+
SPGNFTStorage storage $ = _getSPGNFTStorage();
148+
if ($.totalSupply + 1 > $.maxSupply) revert Errors.SPGNFT__MaxSupplyReached();
149+
150+
IERC20($.mintFeeToken).transferFrom(payer, address(this), $.mintFee);
151+
152+
tokenId = ++$.totalSupply;
153+
_mint(to, tokenId);
154+
}
155+
156+
//
157+
// Upgrade
158+
//
159+
160+
/// @dev Returns the storage struct of SPGNFT.
161+
function _getSPGNFTStorage() private pure returns (SPGNFTStorage storage $) {
162+
assembly {
163+
$.slot := SPGNFTStorageLocation
164+
}
165+
}
166+
}

0 commit comments

Comments
 (0)