Skip to content

Commit

Permalink
feat(spg): add public minting support (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
sebsadface authored Sep 14, 2024
1 parent 802f82f commit 22f208b
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 26 deletions.
11 changes: 7 additions & 4 deletions contracts/BaseWorkflow.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,14 @@ abstract contract BaseWorkflow {
PIL_TEMPLATE = IPILicenseTemplate(pilTemplate);
}

/// @notice Check that the caller has the minter role for the provided SPG NFT.
/// @notice Check that the caller is authorized to mint for the provided SPG NFT.
/// @notice The caller must have the MINTER_ROLE for the SPG NFT or the SPG NFT must has public minting enabled.
/// @param spgNftContract The address of the SPG NFT.
modifier onlyCallerWithMinterRole(address spgNftContract) {
if (!ISPGNFT(spgNftContract).hasRole(SPGNFTLib.MINTER_ROLE, msg.sender))
revert Errors.SPG__CallerNotMinterRole();
modifier onlyMintAuthorized(address spgNftContract) {
if (
!ISPGNFT(spgNftContract).hasRole(SPGNFTLib.MINTER_ROLE, msg.sender) &&
!ISPGNFT(spgNftContract).publicMinting()
) revert Errors.SPG__CallerNotAuthorizedToMint();
_;
}
}
2 changes: 1 addition & 1 deletion contracts/GroupingWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ contract GroupingWorkflows is
uint256 licenseTermsId,
ISPG.IPMetadata calldata ipMetadata,
ISPG.SignatureData calldata sigAddToGroup
) external onlyCallerWithMinterRole(spgNftContract) returns (address ipId, uint256 tokenId) {
) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId) {
tokenId = ISPGNFT(spgNftContract).mintByPeriphery({
to: address(this),
payer: msg.sender,
Expand Down
1 change: 1 addition & 0 deletions contracts/SPGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ contract SPGNFT is ISPGNFT, ERC721URIStorageUpgradeable, AccessControlUpgradeabl
/// @param mintFeeToken The token to pay for minting.
/// @param mintFeeRecipient The address to receive mint fees.
/// @param mintOpen The status of minting, whether it is open or not.
/// @param publicMinting True if the collection is open for everyone to mint.
/// @custom:storage-location erc7201:story-protocol-periphery.SPGNFT
struct SPGNFTStorage {
uint32 maxSupply;
Expand Down
12 changes: 4 additions & 8 deletions contracts/StoryProtocolGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ contract StoryProtocolGateway is
address spgNftContract,
address recipient,
IPMetadata calldata ipMetadata
) external onlyCallerWithMinterRole(spgNftContract) returns (address ipId, uint256 tokenId) {
) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId) {
tokenId = ISPGNFT(spgNftContract).mintByPeriphery({
to: address(this),
payer: msg.sender,
Expand Down Expand Up @@ -230,11 +230,7 @@ contract StoryProtocolGateway is
address recipient,
IPMetadata calldata ipMetadata,
PILTerms calldata terms
)
external
onlyCallerWithMinterRole(spgNftContract)
returns (address ipId, uint256 tokenId, uint256 licenseTermsId)
{
) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId, uint256 licenseTermsId) {
tokenId = ISPGNFT(spgNftContract).mintByPeriphery({
to: address(this),
payer: msg.sender,
Expand Down Expand Up @@ -312,7 +308,7 @@ contract StoryProtocolGateway is
MakeDerivative calldata derivData,
IPMetadata calldata ipMetadata,
address recipient
) external onlyCallerWithMinterRole(spgNftContract) returns (address ipId, uint256 tokenId) {
) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId) {
tokenId = ISPGNFT(spgNftContract).mintByPeriphery({
to: address(this),
payer: msg.sender,
Expand Down Expand Up @@ -409,7 +405,7 @@ contract StoryProtocolGateway is
bytes calldata royaltyContext,
IPMetadata calldata ipMetadata,
address recipient
) external onlyCallerWithMinterRole(spgNftContract) returns (address ipId, uint256 tokenId) {
) external onlyMintAuthorized(spgNftContract) returns (address ipId, uint256 tokenId) {
LicensingHelper.collectLicenseTokens(licenseTokenIds, address(LICENSE_TOKEN));

tokenId = ISPGNFT(spgNftContract).mintByPeriphery({
Expand Down
4 changes: 2 additions & 2 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ library Errors {
/// @notice Zero address provided as a param to SPG.
error SPG__ZeroAddressParam();

/// @notice Caller does not have the minter role.
error SPG__CallerNotMinterRole();
/// @notice Caller is not authorized to mint.
error SPG__CallerNotAuthorizedToMint();

/// @notice License token list is empty.
error SPG__EmptyLicenseTokens();
Expand Down
62 changes: 51 additions & 11 deletions test/StoryProtocolGateway.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,21 +42,61 @@ contract StoryProtocolGatewayTest is BaseTest {
assertFalse(nftContract.publicMinting());
}

modifier whenCallerDoesNotHaveMinterRole() {
caller = bob;
_;
}
function test_SPG_revert_mintAndRegisterIp_callerNotAuthorizedToMint() public {
vm.prank(alice); // minter and admin of nftContract
nftContract = ISPGNFT(
spg.createCollection({
name: "Test Private Collection",
symbol: "TESTPRIV",
maxSupply: 100,
mintFee: 100 * 10 ** mockToken.decimals(),
mintFeeToken: address(mockToken),
mintFeeRecipient: feeRecipient,
owner: minter,
mintOpen: true,
isPublicMinting: false // not public minting
})
);

function test_SPG_revert_mintAndRegisterIp_callerNotMinterRole()
public
withCollection
whenCallerDoesNotHaveMinterRole
{
vm.expectRevert(Errors.SPG__CallerNotMinterRole.selector);
vm.prank(caller);
vm.expectRevert(Errors.SPG__CallerNotAuthorizedToMint.selector);
vm.prank(bob); // caller does not have minter role
spg.mintAndRegisterIp({ spgNftContract: address(nftContract), recipient: bob, ipMetadata: ipMetadataEmpty });
}

function test_SPG_mintAndRegisterIp_publicMint() public {
vm.prank(alice); // minter and admin of nftContract
nftContract = ISPGNFT(
spg.createCollection({
name: "Test Public Collection",
symbol: "TESTPUB",
maxSupply: 100,
mintFee: 1 * 10 ** mockToken.decimals(),
mintFeeToken: address(mockToken),
mintFeeRecipient: feeRecipient,
owner: minter,
mintOpen: true,
isPublicMinting: true // public minting is enabled
})
);

vm.startPrank(bob); // caller does not have minter role
mockToken.mint(address(bob), 1000 * 10 ** mockToken.decimals());
mockToken.approve(address(nftContract), 1000 * 10 ** mockToken.decimals());

// caller has minter role and public minting is enabled
(address ipId, uint256 tokenId) = spg.mintAndRegisterIp({
spgNftContract: address(nftContract),
recipient: bob,
ipMetadata: ipMetadataEmpty
});
vm.stopPrank();

assertTrue(ipAssetRegistry.isRegistered(ipId));
assertEq(tokenId, 1);
assertSPGNFTMetadata(tokenId, ipMetadataEmpty.nftMetadataURI);
assertMetadata(ipId, ipMetadataEmpty);
}

function test_SPG_mintAndRegisterIp() public withCollection whenCallerHasMinterRole {
mockToken.mint(address(caller), 1000 * 10 ** mockToken.decimals());
mockToken.approve(address(nftContract), 1000 * 10 ** mockToken.decimals());
Expand Down

0 comments on commit 22f208b

Please sign in to comment.