Skip to content

Commit

Permalink
add expiration restriction to royalty payments
Browse files Browse the repository at this point in the history
  • Loading branch information
Spablob committed Apr 14, 2024
1 parent dc40dae commit 69427f2
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface IArbitrationPolicy {
/// @param amount The amount withdrawn
event GovernanceWithdrew(uint256 amount);

/// @notice Returns the dispute module address
/// @notice Returns the protocol-wide dispute module address
function DISPUTE_MODULE() external view returns (address);

/// @notice Returns the payment token address
Expand Down
8 changes: 0 additions & 8 deletions contracts/interfaces/modules/royalty/IRoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,9 @@ interface IRoyaltyModule is IModule {
/// @param licensing The address of the license module
function setLicensingModule(address licensing) external;

/// @notice Sets the dispute module
/// @dev Enforced to be only callable by the protocol admin
/// @param dispute The address of the dispute module
function setDisputeModule(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
2 changes: 2 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,8 @@ library Errors {
error RoyaltyModule__CanOnlyMintSelectedPolicy();
error RoyaltyModule__NoParentsOnLinking();
error RoyaltyModule__ZeroDisputeModule();
error RoyaltyModule__ZeroLicenseRegistry();
error RoyaltyModule__IpIsExpired();
error RoyaltyModule__IpIsTagged();
error RoyaltyModule__ZeroAccessManager();

Expand Down
2 changes: 1 addition & 1 deletion contracts/modules/dispute/policies/ArbitrationPolicySP.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Errors } from "../../../lib/Errors.sol";
contract ArbitrationPolicySP is IArbitrationPolicy, AccessManagedUpgradeable, UUPSUpgradeable {
using SafeERC20 for IERC20;

/// @notice Returns the dispute module address
/// @notice Returns the protocol-wide dispute module address
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address public immutable DISPUTE_MODULE;
/// @notice Returns the payment token address
Expand Down
2 changes: 1 addition & 1 deletion contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ contract LicensingModule is
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ILicenseRegistry public immutable LICENSE_REGISTRY;

/// @notice Returns the dispute module
/// @notice Returns the protocol-wide dispute module
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IDisputeModule public immutable DISPUTE_MODULE;

Expand Down
42 changes: 24 additions & 18 deletions contracts/modules/royalty/RoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { BaseModule } from "../BaseModule.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 { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol";
import { Errors } from "../../lib/Errors.sol";
import { ROYALTY_MODULE_KEY } from "../../lib/modules/Module.sol";
import { BaseModule } from "../BaseModule.sol";
Expand All @@ -28,15 +29,21 @@ contract RoyaltyModule is
{
using ERC165Checker for address;

/// @notice Returns the canonical protocol-wide LicenseRegistry
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ILicenseRegistry public immutable LICENSE_REGISTRY;

/// @notice Returns the protocol-wide dispute module
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IDisputeModule public immutable DISPUTE_MODULE;

/// @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 All @@ -50,8 +57,15 @@ contract RoyaltyModule is
string public constant override name = ROYALTY_MODULE_KEY;

/// @notice Constructor
/// @param disputeModule The address of the dispute module
/// @param licenseRegistry The address of the license registry
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
constructor(address disputeModule, address licenseRegistry) {
if (disputeModule == address(0)) revert Errors.RoyaltyModule__ZeroDisputeModule();
if (licenseRegistry == address(0)) revert Errors.RoyaltyModule__ZeroLicenseRegistry();

DISPUTE_MODULE = IDisputeModule(disputeModule);
LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry);
_disableInitializers();
}

Expand Down Expand Up @@ -81,14 +95,6 @@ contract RoyaltyModule is
_getRoyaltyModuleStorage().licensingModule = licensing;
}

/// @notice Sets the dispute module
/// @dev Enforced to be only callable by the protocol admin
/// @param dispute The address of the dispute module
function setDisputeModule(address dispute) external restricted {
if (dispute == address(0)) revert Errors.RoyaltyModule__ZeroDisputeModule();
_getRoyaltyModuleStorage().disputeModule = dispute;
}

/// @notice Whitelist a royalty policy
/// @dev Enforced to be only callable by the protocol admin
/// @param royaltyPolicy The address of the royalty policy
Expand Down Expand Up @@ -190,7 +196,7 @@ contract RoyaltyModule is
RoyaltyModuleStorage storage $ = _getRoyaltyModuleStorage();
if (!$.isWhitelistedRoyaltyToken[token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken();

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

Expand All @@ -201,6 +207,10 @@ contract RoyaltyModule is
if (!$.isWhitelistedRoyaltyPolicy[payerRoyaltyPolicy])
revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();

ILicenseRegistry licenseRegistry = LICENSE_REGISTRY;
if (licenseRegistry.isExpiredNow(receiverIpId) || licenseRegistry.isExpiredNow(payerIpId))
revert Errors.RoyaltyModule__IpIsExpired();

IRoyaltyPolicy(payerRoyaltyPolicy).onRoyaltyPayment(msg.sender, receiverIpId, token, amount);

emit RoyaltyPaid(receiverIpId, payerIpId, msg.sender, token, amount);
Expand All @@ -221,10 +231,11 @@ 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 (DISPUTE_MODULE.isIpTagged(receiverIpId)) revert Errors.RoyaltyModule__IpIsTagged();
if (licenseRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet();
if (!$.isWhitelistedRoyaltyPolicy[licenseRoyaltyPolicy])
revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();
if (LICENSE_REGISTRY.isExpiredNow(receiverIpId)) revert Errors.RoyaltyModule__IpIsExpired();

IRoyaltyPolicy(licenseRoyaltyPolicy).onRoyaltyPayment(payerAddress, receiverIpId, token, amount);

Expand All @@ -236,11 +247,6 @@ 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
25 changes: 12 additions & 13 deletions script/foundry/utils/DeployHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -213,18 +213,6 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag

IPAccountRegistry ipAccountRegistry = IPAccountRegistry(address(ipAssetRegistry));

contractKey = "RoyaltyModule";
_predeploy(contractKey);
impl = address(new RoyaltyModule());
royaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(
impl,
abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))
)
);
impl = address(0);
_postdeploy(contractKey, address(royaltyModule));

contractKey = "LicenseRegistry";
_predeploy(contractKey);
impl = address(new LicenseRegistry());
Expand Down Expand Up @@ -267,6 +255,18 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag
impl = address(0);
_postdeploy(contractKey, address(licenseToken));

contractKey = "RoyaltyModule";
_predeploy(contractKey);
impl = address(new RoyaltyModule(address(disputeModule), address(licenseRegistry)));
royaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(
impl,
abi.encodeCall(RoyaltyModule.initialize, address(protocolAccessManager))
)
);
impl = address(0);
_postdeploy(contractKey, address(royaltyModule));

contractKey = "LicensingModule";
_predeploy(contractKey);
impl = address(
Expand Down Expand Up @@ -396,7 +396,6 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag

// Royalty Module and SP Royalty Policy
royaltyModule.setLicensingModule(address(licensingModule));
royaltyModule.setDisputeModule(address(disputeModule));
royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), true);
royaltyModule.whitelistRoyaltyToken(address(erc20), true);
royaltyPolicyLAP.setSnapshotInterval(7 days);
Expand Down
20 changes: 10 additions & 10 deletions test/foundry/integration/flows/licensing/LicensingIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,6 @@ contract e2e is Test {
)
);

impl = address(new RoyaltyModule());
royaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(
impl,
abi.encodeCall(RoyaltyModule.initialize, (address(protocolAccessManager)))
)
);
vm.label(address(royaltyModule), "RoyaltyModule");

impl = address(
new DisputeModule(address(accessController), address(ipAssetRegistry), address(licenseRegistry))
);
Expand All @@ -136,6 +127,16 @@ contract e2e is Test {
)
);


impl = address(new RoyaltyModule(address(disputeModule), address(licenseRegistry)));
royaltyModule = RoyaltyModule(
TestProxyHelper.deployUUPSProxy(
impl,
abi.encodeCall(RoyaltyModule.initialize, (address(protocolAccessManager)))
)
);
vm.label(address(royaltyModule), "RoyaltyModule");

impl = address(
new LicensingModule(
address(accessController),
Expand Down Expand Up @@ -211,7 +212,6 @@ contract e2e is Test {
moduleRegistry.registerModule(ROYALTY_MODULE_KEY, address(royaltyModule));

royaltyModule.setLicensingModule(address(licensingModule));
royaltyModule.setDisputeModule(address(disputeModule));
royaltyModule.whitelistRoyaltyToken(address(erc20), true);
royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLAP), true);

Expand Down
51 changes: 25 additions & 26 deletions test/foundry/modules/royalty/IpRoyaltyVault.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ contract TestIpRoyaltyVault is BaseTest {
uint32[] memory parentRoyalties1 = new uint32[](2);
bytes[] memory encodedLicenseData = new bytes[](2);

// 3 is child of 7 and 8
// 100 is child of 7 and 8
parents[0] = address(7);
parents[1] = address(8);
parentRoyalties1[0] = 7 * 10 ** 5;
Expand All @@ -55,7 +55,7 @@ contract TestIpRoyaltyVault is BaseTest {
for (uint32 i = 0; i < parentRoyalties1.length; i++) {
encodedLicenseData[i] = abi.encode(parentRoyalties1[i]);
}
royaltyPolicyLAP.onLinkToParents(address(3), parents, encodedLicenseData, "");
royaltyPolicyLAP.onLinkToParents(address(100), parents, encodedLicenseData, "");

// 4 is child of 9 and 10
parents[0] = address(9);
Expand Down Expand Up @@ -91,8 +91,8 @@ contract TestIpRoyaltyVault is BaseTest {
royaltyPolicyLAP.onLinkToParents(address(6), parents, encodedLicenseData, "");

// init 3rd level with children
// 1 is child of 3 and 4
parents[0] = address(3);
// 1 is child of 100 and 4
parents[0] = address(100);
parents[1] = address(4);
parentRoyalties1[0] = 3 * 10 ** 5;
parentRoyalties1[1] = 4 * 10 ** 5;
Expand Down Expand Up @@ -128,8 +128,7 @@ contract TestIpRoyaltyVault is BaseTest {
}

vm.startPrank(address(licensingModule));
royaltyModule.onLinkToParents(address(100), address(royaltyPolicyLAP), parents, encodedLicenseData, "");
//royaltyPolicyLAP.onLinkToParents(address(100), parents, encodedLicenseData, "");
royaltyModule.onLinkToParents(address(3), address(royaltyPolicyLAP), parents, encodedLicenseData, "");
}

function test_IpRoyaltyVault_AddIpRoyaltyVaultTokens_NotRoyaltyPolicyLAP() public {
Expand All @@ -150,10 +149,10 @@ contract TestIpRoyaltyVault is BaseTest {
function test_IpRoyaltyVault_ClaimableRevenue() public {
// payment is made to vault
uint256 royaltyAmount = 100000 * 10 ** 6;
USDC.mint(address(100), 100000 * 10 ** 6); // 100k USDC
vm.startPrank(address(100));
USDC.mint(address(3), 100000 * 10 ** 6); // 100k USDC
vm.startPrank(address(3));
USDC.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(USDC), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(USDC), royaltyAmount);
vm.stopPrank();

// take snapshot
Expand All @@ -172,13 +171,13 @@ contract TestIpRoyaltyVault is BaseTest {
function test_IpRoyaltyVault_ClaimRevenueByTokenBatch() public {
// payment is made to vault
uint256 royaltyAmount = 100000 * 10 ** 6;
USDC.mint(address(100), royaltyAmount); // 100k USDC
LINK.mint(address(100), royaltyAmount); // 100k LINK
vm.startPrank(address(100));
USDC.mint(address(3), royaltyAmount); // 100k USDC
LINK.mint(address(3), royaltyAmount); // 100k LINK
vm.startPrank(address(3));
USDC.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(USDC), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(USDC), royaltyAmount);
LINK.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(LINK), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(LINK), royaltyAmount);
vm.stopPrank();

// take snapshot
Expand Down Expand Up @@ -220,19 +219,19 @@ contract TestIpRoyaltyVault is BaseTest {

function test_IpRoyaltyVault_ClaimRevenueBySnapshotBatch() public {
uint256 royaltyAmount = 100000 * 10 ** 6;
USDC.mint(address(100), royaltyAmount); // 100k USDC
USDC.mint(address(3), royaltyAmount); // 100k USDC

// 1st payment is made to vault
vm.startPrank(address(100));
vm.startPrank(address(3));
USDC.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(USDC), royaltyAmount / 2);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(USDC), royaltyAmount / 2);

// take snapshot
vm.warp(block.timestamp + 7 days + 1);
ipRoyaltyVault.snapshot();

// 2nt payment is made to vault
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(USDC), royaltyAmount / 2);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(USDC), royaltyAmount / 2);
vm.stopPrank();

// take snapshot
Expand Down Expand Up @@ -272,13 +271,13 @@ contract TestIpRoyaltyVault is BaseTest {
function test_IpRoyaltyVault_Snapshot() public {
// payment is made to vault
uint256 royaltyAmount = 100000 * 10 ** 6;
USDC.mint(address(100), royaltyAmount); // 100k USDC
LINK.mint(address(100), royaltyAmount); // 100k LINK
vm.startPrank(address(100));
USDC.mint(address(3), royaltyAmount); // 100k USDC
LINK.mint(address(3), royaltyAmount); // 100k LINK
vm.startPrank(address(3));
USDC.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(USDC), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(USDC), royaltyAmount);
LINK.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(LINK), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(LINK), royaltyAmount);
vm.stopPrank();

// take snapshot
Expand Down Expand Up @@ -371,10 +370,10 @@ contract TestIpRoyaltyVault is BaseTest {
uint256 accruedCollectableRevenue = (royaltyAmount * 5 * 10 ** 5) / royaltyPolicyLAP.TOTAL_RT_SUPPLY();

// payment is made to vault
USDC.mint(address(100), royaltyAmount); // 100k USDC
vm.startPrank(address(100));
USDC.mint(address(3), royaltyAmount); // 100k USDC
vm.startPrank(address(3));
USDC.approve(address(royaltyPolicyLAP), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(100), address(USDC), royaltyAmount);
royaltyModule.payRoyaltyOnBehalf(address(2), address(3), address(USDC), royaltyAmount);
vm.stopPrank();

// take snapshot
Expand Down
Loading

0 comments on commit 69427f2

Please sign in to comment.