Skip to content

Commit

Permalink
fix(register-derivatives): collect and approve minting fees for comme…
Browse files Browse the repository at this point in the history
…rcial licenses (#23)

* fix(register-derivatives): collect and approve mint fees for commercial licenses
* test(register-derivatives): add tests for commercial license parent derivatives
  • Loading branch information
sebsadface authored Aug 15, 2024
1 parent 0308d83 commit 4a4f718
Show file tree
Hide file tree
Showing 6 changed files with 346 additions and 106 deletions.
1 change: 1 addition & 0 deletions contracts/SPGNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.23;

import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
// solhint-disable-next-line max-line-length
import { ERC721URIStorageUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
Expand Down
141 changes: 141 additions & 0 deletions contracts/StoryProtocolGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,17 @@ import { IAccessController } from "@storyprotocol/core/interfaces/access/IAccess
// solhint-disable-next-line max-line-length
import { IPILicenseTemplate, PILTerms } from "@storyprotocol/core/interfaces/modules/licensing/IPILicenseTemplate.sol";
import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol";
import { ILicenseTemplate } from "@storyprotocol/core/interfaces/modules/licensing/ILicenseTemplate.sol";
import { ILicenseRegistry } from "@storyprotocol/core/interfaces/registries/ILicenseRegistry.sol";
import { ILicensingHook } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingHook.sol";
import { Licensing } from "@storyprotocol/core/lib/Licensing.sol";
import { IRoyaltyModule } from "@storyprotocol/core/interfaces/modules/royalty/IRoyaltyModule.sol";

import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/metadata/ICoreMetadataModule.sol";
import { IIPAssetRegistry } from "@storyprotocol/core/interfaces/registries/IIPAssetRegistry.sol";
import { AccessPermission } from "@storyprotocol/core/lib/AccessPermission.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import { IStoryProtocolGateway } from "./interfaces/IStoryProtocolGateway.sol";
import { ISPGNFT } from "./interfaces/ISPGNFT.sol";
Expand All @@ -24,6 +32,7 @@ import { SPGNFTLib } from "./lib/SPGNFTLib.sol";

contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable, UUPSUpgradeable {
using ERC165Checker for address;
using SafeERC20 for IERC20;

/// @dev Storage structure for the SPG
/// @param nftContractBeacon The address of the NFT contract beacon.
Expand All @@ -44,6 +53,12 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
/// @notice The address of the Licensing Module.
ILicensingModule public immutable LICENSING_MODULE;

/// @notice The address of the License Registry.
ILicenseRegistry public immutable LICENSE_REGISTRY;

/// @notice The address of the Royalty Module.
IRoyaltyModule public immutable ROYALTY_MODULE;

/// @notice The address of the Core Metadata Module.
ICoreMetadataModule public immutable CORE_METADATA_MODULE;

Expand All @@ -65,6 +80,8 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
address accessController,
address ipAssetRegistry,
address licensingModule,
address licenseRegistry,
address royaltyModule,
address coreMetadataModule,
address pilTemplate,
address licenseToken
Expand All @@ -73,13 +90,17 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
accessController == address(0) ||
ipAssetRegistry == address(0) ||
licensingModule == address(0) ||
licenseRegistry == address(0) ||
royaltyModule == address(0) ||
coreMetadataModule == address(0) ||
licenseToken == address(0)
) revert Errors.SPG__ZeroAddressParam();

ACCESS_CONTROLLER = IAccessController(accessController);
IP_ASSET_REGISTRY = IIPAssetRegistry(ipAssetRegistry);
LICENSING_MODULE = ILicensingModule(licensingModule);
LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry);
ROYALTY_MODULE = IRoyaltyModule(royaltyModule);
CORE_METADATA_MODULE = ICoreMetadataModule(coreMetadataModule);
PIL_TEMPLATE = IPILicenseTemplate(pilTemplate);
LICENSE_TOKEN = ILicenseToken(licenseToken);
Expand Down Expand Up @@ -254,6 +275,14 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
ipId = IP_ASSET_REGISTRY.register(block.chainid, nftContract, tokenId);
_setMetadata(ipMetadata, ipId);

_collectMintFeesAndSetApproval(
msg.sender,
ipId,
derivData.parentIpIds,
derivData.licenseTemplate,
derivData.licenseTermsIds
);

LICENSING_MODULE.registerDerivative({
childIpId: ipId,
parentIpIds: derivData.parentIpIds,
Expand Down Expand Up @@ -289,6 +318,15 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
address(LICENSING_MODULE),
ILicensingModule.registerDerivative.selector
);

_collectMintFeesAndSetApproval(
msg.sender,
ipId,
derivData.parentIpIds,
derivData.licenseTemplate,
derivData.licenseTermsIds
);

LICENSING_MODULE.registerDerivative({
childIpId: ipId,
parentIpIds: derivData.parentIpIds,
Expand Down Expand Up @@ -441,6 +479,109 @@ contract StoryProtocolGateway is IStoryProtocolGateway, AccessManagedUpgradeable
_setMetadata(ipMetadata, ipId);
}

/// @dev Collect mint fees for all parent IPs from the payer and set approval for Royalty Module to spend mint fees.
/// @param payerAddress The address of the payer for the license mint fees.
/// @param childIpId The ID of the derivative IP.
/// @param parentIpIds The IDs of all the parent IPs.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
function _collectMintFeesAndSetApproval(
address payerAddress,
address childIpId,
address[] calldata parentIpIds,
address licenseTemplate,
uint256[] calldata licenseTermsIds
) private {
// Get currency token and royalty policy, assumes all parent IPs have the same currency token.
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
(address royaltyPolicy, , , address mintFeeCurrencyToken) = lct.getRoyaltyPolicy(licenseTermsIds[0]);

if (royaltyPolicy != address(0)) {
// Get total mint fee for all parent IPs
uint256 totalMintFee = _aggregateMintFees(parentIpIds, childIpId, licenseTemplate, licenseTermsIds);

if (totalMintFee != 0) {
// Transfer mint fee from payer to this contract
IERC20(mintFeeCurrencyToken).safeTransferFrom(payerAddress, address(this), totalMintFee);

// Approve Royalty Policy to spend mint fee
IERC20(mintFeeCurrencyToken).forceApprove(royaltyPolicy, totalMintFee);
}
}
}

/// @dev Aggregate license mint fees for all parent IPs.
/// @param parentIpIds The IDs of all the parent IPs.
/// @param childIpId The ID of the derivative IP.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsIds The IDs of the license terms for each corresponding parent IP.
/// @return totalMintFee The sum of license mint fees across all parent IPs.
function _aggregateMintFees(
address[] calldata parentIpIds,
address childIpId,
address licenseTemplate,
uint256[] calldata licenseTermsIds
) private returns (uint256 totalMintFee) {
totalMintFee = 0;

for (uint256 i = 0; i < parentIpIds.length; i++) {
totalMintFee += _getMintFeeForSingleParent(
childIpId,
parentIpIds[i],
licenseTemplate,
licenseTermsIds[i],
1
);
}
}

/// @dev Fetch the license token mint fee from the licensing hook or license terms for the given parent IP.
/// @param childIpId The ID of the derivative IP.
/// @param parentIpId The ID of the parent IP.
/// @param licenseTemplate The address of the license template.
/// @param licenseTermsId The ID of the license terms for the parent IP.
/// @param amount The amount of licenses to mint.
function _getMintFeeForSingleParent(
address childIpId,
address parentIpId,
address licenseTemplate,
uint256 licenseTermsId,
uint256 amount
) private returns (uint256) {
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);

// Get mint fee set by license terms
(address royaltyPolicy, , uint256 mintFeeSetByLicenseTerms, ) = lct.getRoyaltyPolicy(licenseTermsId);

// If no royalty policy, return 0
if (royaltyPolicy == address(0)) return 0;

uint256 mintFeeSetByHook = 0;

Licensing.LicensingConfig memory licensingConfig = LICENSE_REGISTRY.getLicensingConfig(
parentIpId,
licenseTemplate,
licenseTermsId
);

// Get mint fee from licensing hook
if (licensingConfig.licensingHook != address(0)) {
mintFeeSetByHook = ILicensingHook(licensingConfig.licensingHook).beforeRegisterDerivative(
address(this),
childIpId,
parentIpId,
licenseTemplate,
licenseTermsId,
licensingConfig.hookData
);
}

if (!licensingConfig.isSet) return mintFeeSetByLicenseTerms * amount;
if (licensingConfig.licensingHook == address(0)) return licensingConfig.mintingFee * amount;

return mintFeeSetByHook;
}

//
// Upgrade
//
Expand Down
2 changes: 2 additions & 0 deletions script/Main.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ contract Main is Script, StoryProtocolCoreAddressManager, BroadcastManager, Json
accessControllerAddr,
ipAssetRegistryAddr,
licensingModuleAddr,
licenseRegistryAddr,
royaltyModuleAddr,
coreMetadataModuleAddr,
pilTemplateAddr,
licenseTokenAddr
Expand Down
4 changes: 4 additions & 0 deletions script/utils/StoryProtocolCoreAddressManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ contract StoryProtocolCoreAddressManager is Script {
address internal protocolAccessManagerAddr;
address internal ipAssetRegistryAddr;
address internal licensingModuleAddr;
address internal licenseRegistryAddr;
address internal royaltyModuleAddr;
address internal coreMetadataModuleAddr;
address internal accessControllerAddr;
address internal pilTemplateAddr;
Expand All @@ -31,6 +33,8 @@ contract StoryProtocolCoreAddressManager is Script {
protocolAccessManagerAddr = json.readAddress(".main.ProtocolAccessManager");
ipAssetRegistryAddr = json.readAddress(".main.IPAssetRegistry");
licensingModuleAddr = json.readAddress(".main.LicensingModule");
licenseRegistryAddr = json.readAddress(".main.LicenseRegistry");
royaltyModuleAddr = json.readAddress(".main.RoyaltyModule");
coreMetadataModuleAddr = json.readAddress(".main.CoreMetadataModule");
accessControllerAddr = json.readAddress(".main.AccessController");
pilTemplateAddr = json.readAddress(".main.PILicenseTemplate");
Expand Down
Loading

0 comments on commit 4a4f718

Please sign in to comment.