Skip to content
This repository has been archived by the owner on Apr 30, 2024. It is now read-only.

Fix Licensing Minting Payment to Account for Mint Amount #129

Merged
merged 4 commits into from
Feb 23, 2024
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
2 changes: 2 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,8 @@ library Errors {
error LicensingModule__RegisterPolicyFrameworkMismatch();
error LicensingModule__RoyaltyPolicyNotWhitelisted();
error LicensingModule__MintingFeeTokenNotWhitelisted();
error LicensingModule__ReceiverZeroAddress();
error LicensingModule__MintAmountZero();
/// @notice emitted when trying to interact with an IP that has been disputed in the DisputeModule
error LicensingModule__DisputedIpId();
/// @notice emitted when linking a license from a licensor that has been disputed in the DisputeModule
Expand Down
9 changes: 8 additions & 1 deletion contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -183,9 +183,16 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
address receiver,
bytes calldata royaltyContext
) external nonReentrant returns (uint256 licenseId) {
_verifyPolicy(_policies[policyId]);
if (!IP_ACCOUNT_REGISTRY.isIpAccount(licensorIpId)) {
revert Errors.LicensingModule__LicensorNotRegistered();
}
if (amount == 0) {
revert Errors.LicensingModule__MintAmountZero();
}
if (receiver == address(0)) {
revert Errors.LicensingModule__ReceiverZeroAddress();
}
_verifyIpNotDisputed(licensorIpId);

bool isInherited = _policySetups[licensorIpId][policyId].isInherited;
Expand Down Expand Up @@ -219,7 +226,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
msg.sender,
pol.royaltyPolicy,
pol.mintingFeeToken,
pol.mintingFee
pol.mintingFee * amount
jdubpark marked this conversation as resolved.
Show resolved Hide resolved
);
}
}
Expand Down
20 changes: 14 additions & 6 deletions test/foundry/integration/big-bang/SingleNftCollection.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {

uint32 internal constant derivCheapFlexibleRevShare = 10;

uint256 internal constant mintingFee = 100 ether;

function setUp() public override {
super.setUp();

Expand All @@ -45,7 +47,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
"com_deriv_cheap_flexible", // ==> policyIds["pil_com_deriv_cheap_flexible"]
true,
address(royaltyPolicyLAP),
100 ether, // mint payment (100 * 10^18)
mintingFee,
address(mockToken),
PILPolicy({
attribution: false,
Expand Down Expand Up @@ -161,7 +163,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
// (verified by the mockTokenGatedHook commercializer checker)
mockGatedNft.mint(u.carl);

mockToken.approve(address(royaltyPolicyLAP), 100 ether);
mockToken.approve(address(royaltyPolicyLAP), mintingFee);

uint256[] memory carl_license_from_root_alice = new uint256[](1);
carl_license_from_root_alice[0] = licensingModule.mintLicense(
Expand Down Expand Up @@ -248,7 +250,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
mockNFT.mintId(u.alice, 2);
uint256 mintAmount = 2;

mockToken.approve(address(royaltyPolicyLAP), mintAmount * 100 ether);
mockToken.approve(address(royaltyPolicyLAP), mintAmount * mintingFee);

// Alice needs to hold an NFT from mockGatedNFT collection to mint license on pil_com_deriv_cheap_flexible
// (verified by the mockTokenGatedHook commercializer checker)
Expand Down Expand Up @@ -309,10 +311,12 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
{
vm.startPrank(u.carl);

uint256 license0_mintAmount = 1000;
uint256 tokenId = 70000; // dummy number that shouldn't conflict with any other token IDs used in this test
mockNFT.mintId(u.carl, tokenId);

mockToken.approve(address(royaltyPolicyLAP), 200 ether);
mockToken.mint(u.carl, mintingFee * license0_mintAmount);
mockToken.approve(address(royaltyPolicyLAP), mintingFee * license0_mintAmount);

IP.MetadataV1 memory metadata = IP.MetadataV1({
name: "IP NAME",
Expand All @@ -327,7 +331,7 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
carl_licenses[0] = licensingModule.mintLicense(
policyIds["pil_com_deriv_cheap_flexible"], // ipAcct[1] has this policy attached
ipAcct[1],
100, // mint 100 licenses
license0_mintAmount,
u.carl,
emptyRoyaltyPolicyLAPInitParams
);
Expand Down Expand Up @@ -355,11 +359,15 @@ contract BigBang_Integration_SingleNftCollection is BaseIntegration {
""
);

uint256 license1_mintAmount = 500;
mockToken.mint(u.carl, mintingFee * license1_mintAmount);
mockToken.approve(address(royaltyPolicyLAP), mintingFee * license1_mintAmount);

// Modify license[1] to a Commercial license
carl_licenses[1] = licensingModule.mintLicense(
policyIds["pil_com_deriv_cheap_flexible"], // ipAcct[300] has this policy attached
ipAcct[300],
1,
license1_mintAmount,
u.carl,
emptyRoyaltyPolicyLAPInitParams
);
Expand Down
30 changes: 26 additions & 4 deletions test/foundry/integration/flows/royalty/Royalty.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";

// contract
import { IRoyaltyModule } from "contracts/interfaces/modules/royalty/IRoyaltyModule.sol";
import { IRoyaltyPolicyLAP } from "contracts/interfaces/modules/royalty/policies/IRoyaltyPolicyLAP.sol";

// test
Expand All @@ -30,21 +31,29 @@ contract Flows_Integration_Disputes is BaseIntegration {
})
);

address internal royaltyPolicyAddr; // must be assigned AFTER super.setUp()
address internal mintingFeeToken; // must be assigned AFTER super.setUp()
uint32 internal defaultCommRevShare = 100;
uint256 internal mintingFee = 7 ether;

function setUp() public override {
super.setUp();

// Register PIL Framework
_deployLFM_PIL();

royaltyPolicyAddr = address(royaltyPolicyLAP);
mintingFeeToken = address(erc20);

// Register a License
_mapPILPolicySimple({
_mapPILPolicyCommercial({
name: "commercial-remix",
commercial: true,
derivatives: true,
reciprocal: true,
commercialRevShare: defaultCommRevShare
commercialRevShare: defaultCommRevShare,
royaltyPolicy: royaltyPolicyAddr,
mintingFeeToken: mintingFeeToken,
mintingFee: mintingFee
});
_registerPILPolicyFromMapping("commercial-remix");

Expand Down Expand Up @@ -74,11 +83,17 @@ contract Flows_Integration_Disputes is BaseIntegration {
ipAcct[2] = _getIpId(mockNFT, 2);
vm.label(ipAcct[2], "IPAccount2");

uint256 mintAmount = 3;
erc20.approve(address(royaltyPolicyAddr), mintAmount * mintingFee);

uint256[] memory licenseIds = new uint256[](1);

vm.expectEmit(address(royaltyModule));
emit IRoyaltyModule.LicenseMintingFeePaid(ipAcct[1], u.bob, address(erc20), mintAmount * mintingFee);
licenseIds[0] = licensingModule.mintLicense(
_getPilPolicyId("commercial-remix"),
ipAcct[1],
1,
mintAmount,
u.bob,
emptyRoyaltyPolicyLAPInitParams
);
Expand Down Expand Up @@ -116,8 +131,13 @@ contract Flows_Integration_Disputes is BaseIntegration {
ipAcct[3] = _getIpId(mockNFT, 3);
vm.label(ipAcct[3], "IPAccount3");

uint256 mintAmount = 1;
uint256[] memory licenseIds = new uint256[](2);

erc20.approve(address(royaltyPolicyAddr), 2 * mintAmount * mintingFee);

vm.expectEmit(address(royaltyModule));
emit IRoyaltyModule.LicenseMintingFeePaid(ipAcct[1], u.carl, address(erc20), mintAmount * mintingFee);
licenseIds[0] = licensingModule.mintLicense(
_getPilPolicyId("commercial-remix"),
ipAcct[1], // grandparent, root IP
Expand All @@ -137,6 +157,8 @@ contract Flows_Integration_Disputes is BaseIntegration {
params1.targetAncestors[0] = ipAcct[1];
params1.targetRoyaltyAmount[0] = defaultCommRevShare;

vm.expectEmit(address(royaltyModule));
emit IRoyaltyModule.LicenseMintingFeePaid(ipAcct[2], u.carl, address(erc20), mintAmount * mintingFee);
licenseIds[1] = licensingModule.mintLicense(
_getPilPolicyId("commercial-remix"),
ipAcct[2], // parent, is child IP of ipAcct[1]
Expand Down
11 changes: 10 additions & 1 deletion test/foundry/modules/licensing/LicensingModule.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ contract LicensingModuleTest is BaseTest {
return licenseId;
}

function test_LicensingModule_mintLicense_revert_licensorNotRegistered() public {
function test_LIcensingModule_mintLicense_revert_inputValidations() public {
licensingModule.registerPolicyFrameworkManager(address(mockPFM));

vm.prank(address(mockPFM));
Expand All @@ -391,8 +391,17 @@ contract LicensingModuleTest is BaseTest {
vm.prank(ipOwner);
licensingModule.addPolicyToIp(ipId1, policyId);

vm.expectRevert(Errors.LicensingModule__PolicyNotFound.selector);
licensingModule.mintLicense(9483928387183923004983928394, address(0), 2, licenseHolder, "");

vm.expectRevert(Errors.LicensingModule__LicensorNotRegistered.selector);
licensingModule.mintLicense(policyId, address(0), 2, licenseHolder, "");

vm.expectRevert(Errors.LicensingModule__MintAmountZero.selector);
licensingModule.mintLicense(policyId, ipId1, 0, licenseHolder, "");

vm.expectRevert(Errors.LicensingModule__ReceiverZeroAddress.selector);
licensingModule.mintLicense(policyId, ipId1, 2, address(0), "");
}

function test_LicensingModule_mintLicense_revert_callerNotLicensorAndIpIdHasNoPolicy() public {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ contract PILPolicyFrameworkMultiParentTest is BaseTest {
inputA.policy.commercialUse = true;
inputB.policy.commercialUse = false;
inputB.policy.commercialRevShare = 0;
inputB.mintingFee = 0;
inputB.mintingFeeToken = address(0);
inputB.royaltyPolicy = address(0x0);
// TODO: passing in two different royaltyPolicy addresses
// solhint-disable-next-line max-line-length
Expand Down
3 changes: 2 additions & 1 deletion test/foundry/utils/BaseTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ contract BaseTest is Test, DeployHelper, LicensingHelper {
address(ipAccountRegistry),
getLicensingModule(),
getRoyaltyModule(),
isMockRoyaltyPolicyLAP ? address(mockRoyaltyPolicyLAP) : address(royaltyPolicyLAP)
isMockRoyaltyPolicyLAP ? address(mockRoyaltyPolicyLAP) : address(royaltyPolicyLAP),
address(erc20)
);

// Set aliases
Expand Down
46 changes: 42 additions & 4 deletions test/foundry/utils/LicensingHelper.t.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// contract
import { IAccessController } from "../../../contracts/interfaces/IAccessController.sol";
import { IIPAccountRegistry } from "../../../contracts/interfaces/registries/IIPAccountRegistry.sol";
Expand All @@ -26,6 +28,8 @@ contract LicensingHelper {

IRoyaltyPolicyLAP private ROYALTY_POLICY_LAP; // keep private to avoid collision with `BaseIntegration`

IERC20 private erc20;

mapping(string frameworkName => uint256 frameworkId) internal frameworkIds;

mapping(string policyName => uint256 globalPolicyId) internal policyIds;
Expand All @@ -41,13 +45,15 @@ contract LicensingHelper {
address _ipAccountRegistry,
address _licensingModule,
address _royaltyModule,
address _royaltyPolicy
address _royaltyPolicy,
address _erc20
) public {
ACCESS_CONTROLLER = IAccessController(_accessController);
IP_ACCOUNT_REGISTRY = IIPAccountRegistry(_ipAccountRegistry);
LICENSING_MODULE = ILicensingModule(_licensingModule);
ROYALTY_MODULE = IRoyaltyModule(_royaltyModule);
ROYALTY_POLICY_LAP = IRoyaltyPolicyLAP(_royaltyPolicy);
erc20 = IERC20(_erc20);
}

/*//////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -121,10 +127,9 @@ contract LicensingHelper {
string memory pName = string(abi.encodePacked("pil_", name));
policies[pName] = RegisterPILPolicyParams({
transferable: true,
// TODO: use mock or real based on condition
royaltyPolicy: commercial ? address(ROYALTY_POLICY_LAP) : address(0),
mintingFee: 0,
mintingFeeToken: address(0),
mintingFee: commercial ? 1 ether : 0,
mintingFeeToken: commercial ? address(erc20) : address(0),
policy: PILPolicy({
attribution: true,
commercialUse: commercial,
Expand All @@ -143,6 +148,39 @@ contract LicensingHelper {
});
}

function _mapPILPolicyCommercial(
string memory name,
bool derivatives,
bool reciprocal,
uint32 commercialRevShare,
address royaltyPolicy,
uint256 mintingFee,
address mintingFeeToken
) internal {
string memory pName = string(abi.encodePacked("pil_", name));
policies[pName] = RegisterPILPolicyParams({
transferable: true,
royaltyPolicy: royaltyPolicy,
mintingFee: mintingFee,
mintingFeeToken: mintingFeeToken,
policy: PILPolicy({
attribution: true,
commercialUse: true,
commercialAttribution: false,
commercializerChecker: address(0),
commercializerCheckerData: "",
commercialRevShare: commercialRevShare,
derivativesAllowed: derivatives,
derivativesAttribution: false,
derivativesApproval: false,
derivativesReciprocal: reciprocal,
territories: emptyStringArray,
distributionChannels: emptyStringArray,
contentRestrictions: emptyStringArray
})
});
}

function _addPILPolicyFromMapping(string memory name, address pilFramework) internal returns (uint256) {
string memory pName = string(abi.encodePacked("pil_", name));
policyIds[pName] = PILPolicyFrameworkManager(pilFramework).registerPolicy(policies[pName]);
Expand Down
Loading