Skip to content

Commit

Permalink
add pay royalty payment restrictions to tagged ip assets
Browse files Browse the repository at this point in the history
  • Loading branch information
Spablob committed Apr 5, 2024
1 parent 7993cde commit 809f9f3
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 8 deletions.
9 changes: 9 additions & 0 deletions contracts/interfaces/modules/royalty/IRoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,18 @@ interface IRoyaltyModule is IModule {
/// @param amount The amount paid
event LicenseMintingFeePaid(address receiverIpId, address payerAddress, address token, uint256 amount);

/// @notice Sets the license registry
/// @dev Enforced to be only callable by the protocol admin
/// @param licensing The address of the license registry
/// @param dispute The address of the dispute module
function setLicensingAndDisputeModules(address licensing, address dispute) external;

/// @notice Returns the licensing module address
function licensingModule() external view returns (address);

/// @notice Returns the dispute module address
function disputeModule() external view returns (address);

/// @notice Indicates if a royalty policy is whitelisted
/// @param royaltyPolicy The address of the royalty policy
/// @return isWhitelisted True if the royalty policy is whitelisted
Expand Down
20 changes: 18 additions & 2 deletions contracts/modules/royalty/RoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { BaseModule } from "../BaseModule.sol";
import { GovernableUpgradeable } from "../../governance/GovernableUpgradeable.sol";
import { IRoyaltyModule } from "../../interfaces/modules/royalty/IRoyaltyModule.sol";
import { IRoyaltyPolicy } from "../../interfaces/modules/royalty/policies/IRoyaltyPolicy.sol";
import { IDisputeModule } from "../../interfaces/modules/dispute/IDisputeModule.sol";
import { Errors } from "../../lib/Errors.sol";
import { ROYALTY_MODULE_KEY } from "../../lib/modules/Module.sol";
import { BaseModule } from "../BaseModule.sol";
Expand All @@ -27,12 +28,14 @@ contract RoyaltyModule is
using ERC165Checker for address;

/// @dev Storage structure for the RoyaltyModule
/// @param disputeModule The address of the dispute module
/// @param licensingModule The address of the licensing module
/// @param isWhitelistedRoyaltyPolicy Indicates if a royalty policy is whitelisted
/// @param isWhitelistedRoyaltyToken Indicates if a royalty token is whitelisted
/// @param royaltyPolicies Indicates the royalty policy for a given IP asset
/// @custom:storage-location erc7201:story-protocol.RoyaltyModule
struct RoyaltyModuleStorage {
address disputeModule;
address licensingModule;
mapping(address royaltyPolicy => bool isWhitelisted) isWhitelistedRoyaltyPolicy;
mapping(address token => bool) isWhitelistedRoyaltyToken;
Expand Down Expand Up @@ -69,9 +72,13 @@ contract RoyaltyModule is
/// @notice Sets the license registry
/// @dev Enforced to be only callable by the protocol admin
/// @param licensing The address of the license registry
function setLicensingModule(address licensing) external onlyProtocolAdmin {
/// @param dispute The address of the dispute module
function setLicensingAndDisputeModules(address licensing, address dispute) external onlyProtocolAdmin {
if (licensing == address(0)) revert Errors.RoyaltyModule__ZeroLicensingModule();
if (dispute == address(0)) revert Errors.RoyaltyModule__ZeroDisputeModule();

_getRoyaltyModuleStorage().licensingModule = licensing;
_getRoyaltyModuleStorage().disputeModule = dispute;
}

/// @notice Whitelist a royalty policy
Expand Down Expand Up @@ -175,6 +182,10 @@ contract RoyaltyModule is
RoyaltyModuleStorage storage $ = _getRoyaltyModuleStorage();
if (!$.isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken();

IDisputeModule dispute = IDisputeModule($.disputeModule);
if (dispute.isIpTagged(receiverIpId)) revert Errors.RoyaltyModule__IpIsTagged();
if (dispute.isIpTagged(payerIpId)) revert Errors.RoyaltyModule__IpIsTagged();

address payerRoyaltyPolicy = $.royaltyPolicies[payerIpId];
// if the payer does not have a royalty policy set, then the payer is not a derivative ip and does not pay
// royalties while the receiver ip can have a zero royalty policy since that could mean it is an ip a root
Expand Down Expand Up @@ -202,7 +213,7 @@ contract RoyaltyModule is
) external onlyLicensingModule {
RoyaltyModuleStorage storage $ = _getRoyaltyModuleStorage();
if (!$.isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken();

if (IDisputeModule($.disputeModule).isIpTagged(receiverIpId)) revert Errors.RoyaltyModule__IpIsTagged();
if (licenseRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet();
if (!$.isWhitelistedRoyaltyPolicy[licenseRoyaltyPolicy])
revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();
Expand All @@ -217,6 +228,11 @@ contract RoyaltyModule is
return _getRoyaltyModuleStorage().licensingModule;
}

/// @notice Returns the dispute module address
function disputeModule() external view returns (address) {
return _getRoyaltyModuleStorage().disputeModule;
}

/// @notice Indicates if a royalty policy is whitelisted
/// @param royaltyPolicy The address of the royalty policy
/// @return isWhitelisted True if the royalty policy is whitelisted
Expand Down
111 changes: 105 additions & 6 deletions test/foundry/modules/royalty/RoyaltyModule.t.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

import { ERC6551AccountLib } from "erc6551/lib/ERC6551AccountLib.sol";

// contracts
import { Errors } from "../../../../contracts/lib/Errors.sol";
import { RoyaltyModule } from "../../../../contracts/modules/royalty/RoyaltyModule.sol";
import { RoyaltyPolicyLAP } from "../../../../contracts/modules/royalty/policies/RoyaltyPolicyLAP.sol";
import { PILPolicy } from "contracts/modules/licensing/PILPolicyFrameworkManager.sol";
import { TestProxyHelper } from "test/foundry/utils/TestProxyHelper.sol";

// tests
Expand All @@ -19,6 +22,9 @@ contract TestRoyaltyModule is BaseTest {

address internal ipAccount1 = address(0x111000aaa);
address internal ipAccount2 = address(0x111000bbb);
address internal ipAddr;
address internal arbitrationRelayer;

struct InitParams {
address[] targetAncestors;
uint32[] targetRoyaltyAmount;
Expand All @@ -39,9 +45,9 @@ contract TestRoyaltyModule is BaseTest {
function setUp() public override {
super.setUp();
buildDeployModuleCondition(
DeployModuleCondition({ disputeModule: false, royaltyModule: true, licensingModule: false })
DeployModuleCondition({ disputeModule: true, royaltyModule: true, licensingModule: false })
);
buildDeployPolicyCondition(DeployPolicyCondition({ arbitrationPolicySP: false, royaltyPolicyLAP: true }));
buildDeployPolicyCondition(DeployPolicyCondition({ arbitrationPolicySP: true, royaltyPolicyLAP: true }));
deployConditionally();
postDeploymentSetup();

Expand All @@ -52,6 +58,8 @@ contract TestRoyaltyModule is BaseTest {
TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyPolicyLAP.initialize, (getGovernance())))
);

arbitrationRelayer = u.admin;

vm.startPrank(u.admin);
// whitelist royalty policy
royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), true);
Expand All @@ -64,6 +72,51 @@ contract TestRoyaltyModule is BaseTest {
// split made to avoid stack too deep error
_setupTree();
vm.stopPrank();

USDC.mint(ipAccount1, 1000 * 10 ** 6);

_setPILPolicyFrameworkManager();
_addPILPolicy(
"cheap_flexible",
true,
address(royaltyPolicyLAP),
PILPolicy({
attribution: false,
commercialUse: true,
commercialAttribution: true,
commercializerChecker: address(0),
commercializerCheckerData: "",
commercialRevShare: 10,
derivativesAllowed: true,
derivativesAttribution: true,
derivativesApproval: false,
derivativesReciprocal: false,
territories: new string[](0),
distributionChannels: new string[](0),
contentRestrictions: new string[](0)
})
);

mockNFT.mintId(u.alice, 0);

address expectedAddr = ERC6551AccountLib.computeAddress(
address(erc6551Registry),
address(ipAccountImpl),
ipAccountRegistry.IP_ACCOUNT_SALT(),
block.chainid,
address(mockNFT),
0
);
vm.label(expectedAddr, "IPAccount0");

vm.startPrank(u.alice);
ipAddr = ipAssetRegistry.register(address(mockNFT), 0);
licensingModule.addPolicyToIp(ipAddr, policyIds["pil_cheap_flexible"]);

// set arbitration policy
vm.startPrank(ipAddr);
disputeModule.setArbitrationPolicy(ipAddr, address(arbitrationPolicySP));
vm.stopPrank();
}

function _setupTree() internal {
Expand Down Expand Up @@ -119,24 +172,35 @@ contract TestRoyaltyModule is BaseTest {
royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, encodedBytes);
}

function test_RoyaltyModule_setLicensingModule_revert_ZeroLicensingModule() public {
function test_RoyaltyModule_setLicensingAndDisputeModules_revert_ZeroLicensingModule() public {
address impl = address(new RoyaltyModule());
RoyaltyModule testRoyaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, (address(getGovernance()))))
);
vm.expectRevert(Errors.RoyaltyModule__ZeroLicensingModule.selector);
vm.prank(u.admin);
testRoyaltyModule.setLicensingModule(address(0));
testRoyaltyModule.setLicensingAndDisputeModules(address(0), address(1));
}

function test_RoyaltyModule_setLicensingAndDisputeModules_revert_ZeroDisputeModule() public {
address impl = address(new RoyaltyModule());
RoyaltyModule testRoyaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, (address(getGovernance()))))
);
vm.expectRevert(Errors.RoyaltyModule__ZeroDisputeModule.selector);
vm.prank(u.admin);
testRoyaltyModule.setLicensingAndDisputeModules(address(1), address(0));
}

function test_RoyaltyModule_setLicensingModule() public {
function test_RoyaltyModule_setLicensingAndDisputeModules() public {
vm.startPrank(u.admin);
address impl = address(new RoyaltyModule());
RoyaltyModule testRoyaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(impl, abi.encodeCall(RoyaltyModule.initialize, (address(getGovernance()))))
);
testRoyaltyModule.setLicensingModule(address(licensingModule));
testRoyaltyModule.setLicensingAndDisputeModules(address(licensingModule), address(disputeModule));
assertEq(testRoyaltyModule.licensingModule(), address(licensingModule));
assertEq(testRoyaltyModule.disputeModule(), address(disputeModule));
}

function test_RoyaltyModule_whitelistRoyaltyPolicy_revert_ZeroRoyaltyToken() public {
Expand Down Expand Up @@ -449,6 +513,24 @@ contract TestRoyaltyModule is BaseTest {
royaltyModule.payRoyaltyOnBehalf(receiverIpId, payerIpId, address(1), royaltyAmount);
}

function test_RoyaltyModule_payRoyaltyOnBehalf_revert_IpIsTagged() public {
// raise dispute
vm.startPrank(ipAccount1);
USDC.approve(address(arbitrationPolicySP), ARBITRATION_PRICE);
disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", "");
vm.stopPrank();

// set dispute judgement
vm.startPrank(arbitrationRelayer);
disputeModule.setDisputeJudgement(1, true, "");

vm.expectRevert(Errors.RoyaltyModule__IpIsTagged.selector);
royaltyModule.payRoyaltyOnBehalf(ipAddr, ipAccount1, address(USDC), 100);

vm.expectRevert(Errors.RoyaltyModule__IpIsTagged.selector);
royaltyModule.payRoyaltyOnBehalf(ipAccount1, ipAddr, address(USDC), 100);
}

function test_RoyaltyModule_payRoyaltyOnBehalf_revert_NotWhitelistedRoyaltyPolicy() public {
uint256 royaltyAmount = 100 * 10 ** 6;
address receiverIpId = address(7);
Expand Down Expand Up @@ -487,6 +569,23 @@ contract TestRoyaltyModule is BaseTest {
assertEq(ipRoyaltyVaultUSDCBalAfter - ipRoyaltyVaultUSDCBalBefore, royaltyAmount);
}

function test_RoyaltyModule_payLicenseMintingFee_revert_IpIsTagged() public {
// raise dispute
vm.startPrank(ipAccount1);
USDC.approve(address(arbitrationPolicySP), ARBITRATION_PRICE);
disputeModule.raiseDispute(ipAddr, string("urlExample"), "PLAGIARISM", "");
vm.stopPrank();

// set dispute judgement
vm.startPrank(arbitrationRelayer);
disputeModule.setDisputeJudgement(1, true, "");

vm.startPrank(address(licensingModule));

vm.expectRevert(Errors.RoyaltyModule__IpIsTagged.selector);
royaltyModule.payLicenseMintingFee(ipAddr, ipAccount1, address(royaltyPolicyLAP), address(USDC), 100);
}

function test_RoyaltyModule_payLicenseMintingFee() public {
uint256 royaltyAmount = 100 * 10 ** 6;
address receiverIpId = address(7);
Expand Down

0 comments on commit 809f9f3

Please sign in to comment.