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

Add claimer.sol and adjust royalty module and liquid split policy #53

Merged
merged 20 commits into from
Feb 1, 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
44 changes: 36 additions & 8 deletions contracts/interfaces/modules/royalty/IRoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,51 @@ pragma solidity ^0.8.23;

/// @title RoyaltyModule interface
interface IRoyaltyModule {
/// @notice Event emitted when a royalty policy is whitelisted
/// @param royaltyPolicy The address of the royalty policy
/// @param allowed Indicates if the royalty policy is whitelisted or not
event RoyaltyPolicyWhitelistUpdated(address royaltyPolicy, bool allowed);

/// @notice Event emitted when a royalty token is whitelisted
/// @param token The address of the royalty token
/// @param allowed Indicates if the royalty token is whitelisted or not
event RoyaltyTokenWhitelistUpdated(address token, bool allowed);

/// @notice Event emitted when a royalty policy is set
/// @param ipId The ipId
/// @param royaltyPolicy The address of the royalty policy
/// @param data The data to initialize the policy
event RoyaltyPolicySet(address ipId, address royaltyPolicy, bytes data);

/// @notice Event emitted when royalties are paid
/// @param receiverIpId The ipId that receives the royalties
/// @param payerIpId The ipId that pays the royalties
/// @param sender The address that pays the royalties on behalf of the payer ipId
/// @param token The token that is used to pay the royalties
/// @param amount The amount that is paid
event RoyaltyPaid(address receiverIpId, address payerIpId, address sender, address token, uint256 amount);

/// @notice Whitelist a royalty policy
/// @param royaltyPolicy The address of the royalty policy
/// @param allowed Indicates if the royalty policy is whitelisted or not
function whitelistRoyaltyPolicy(address royaltyPolicy, bool allowed) external;

/// @notice Whitelist a royalty token
/// @param token The token address
/// @param allowed Indicates if the token is whitelisted or not
function whitelistRoyaltyToken(address token, bool allowed) external;

/// @notice Sets the royalty policy for an ipId
/// @param ipId The ipId
/// @param royaltyPolicy The address of the royalty policy
/// @param parentIpIds The parent ipIds
/// @param data The data to initialize the policy
function setRoyaltyPolicy(address ipId, address royaltyPolicy, bytes calldata data) external;
function setRoyaltyPolicy(address ipId, address royaltyPolicy, address[] calldata parentIpIds, bytes calldata data) external;

/// @notice Allows an IPAccount to pay royalties
/// @param ipId The ipId
/// @param token The token to pay the royalties in
/// @notice Allows a sender to to pay royalties on behalf of an ipId
/// @param receiverIpId The ipId that receives the royalties
/// @param payerIpId The ipId that pays the royalties
/// @param token The token to use to pay the royalties
/// @param amount The amount to pay
function payRoyalty(address ipId, address token, uint256 amount) external;

/// @notice Gets the royalty policy for a given ipId
function royaltyPolicies(address ipId) external view returns (address royaltyPolicy);
function payRoyaltyOnBehalf(address receiverIpId, address payerIpId, address token, uint256 amount) external;
}
21 changes: 21 additions & 0 deletions contracts/interfaces/modules/royalty/policies/IClaimerLS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

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

/// @title Liquid split policy claimer interface
interface IClaimerLS {
/// @notice Event emitted when a claim is made
/// @param path The path from the ipId to the claimer
/// @param claimer The claimer ipId address
/// @param withdrawETH Indicates if the claimer wants to withdraw ETH
/// @param tokens The ERC20 tokens to withdraw
event Claimed(address[] path, address claimer, bool withdrawETH, ERC20[] tokens);

/// @notice Allows an ipId to claim their rnfts and accrued royalties
/// @param path The path of the ipId
/// @param claimerIpId The ipId of the claimer
/// @param withdrawETH Indicates if the claimer wants to withdraw ETH
/// @param tokens The ERC20 tokens to withdraw
function claim(address[] calldata path, address claimerIpId, bool withdrawETH, ERC20[] calldata tokens) external;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,20 @@ interface ILiquidSplitClone {
/// @param accounts The accounts to distribute to
/// @param distributorAddress The distributor address
function distributeFunds(address token, address[] calldata accounts, address distributorAddress) external;

/// @notice Transfers rnft tokens
/// @param from The address to transfer from
/// @param to The address to transfer to
/// @param id The token id
/// @param amount The amount to transfer
/// @param data Custom data
function safeTransferFrom(
address from,
address to,
uint256 id,
uint256 amount,
bytes calldata data
) external;

function balanceOf(address account, uint256 id) external view returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,13 @@ interface ILiquidSplitMain {
uint256 withdrawETH,
ERC20[] calldata tokens
) external;

/// @notice Gets the ETH balance of an account
/// @param account The account to get the ETH balance of
function getETHBalance(address account) external view returns (uint256);

/// @notice Gets the ERC20 balance of an account
/// @param account The account to get the ERC20 balance of
/// @param token The token to get the balance of
function getERC20Balance(address account, ERC20 token) external view returns (uint256);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ pragma solidity ^0.8.23;
interface IRoyaltyPolicy {
/// @notice Initializes the royalty policy
/// @param ipId The ipId
/// @param data The data to initialize the policy
function initPolicy(address ipId, bytes calldata data) external;
/// @param parentsIpIds The parent ipIds
/// @param data The data to initialize the policy
function initPolicy(address ipId, address[] calldata parentsIpIds, bytes calldata data) external;

/// @notice Allows to pay a royalty
/// @param caller The caller
Expand Down
38 changes: 38 additions & 0 deletions contracts/interfaces/modules/royalty/policies/IRoyaltyPolicyLS.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

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

import { IRoyaltyPolicy } from "contracts/interfaces/modules/royalty/policies/IRoyaltyPolicy.sol";

/// @title RoyaltyPolicy interface
interface IRoyaltyPolicyLS is IRoyaltyPolicy {
/// @notice Gets the royalty data
/// @param ipId The ipId
/// @return splitClone The split clone address
/// claimer The claimer address
/// royaltyStack The royalty stack
/// minRoyalty The min royalty
function royaltyData(address ipId) external view returns (address splitClone, address claimer, uint32 royaltyStack, uint32 minRoyalty);

/// @notice Distributes funds to the accounts in the LiquidSplitClone contract
/// @param ipId The ipId
/// @param token The token to distribute
/// @param accounts The accounts to distribute to
/// @param distributorAddress The distributor address
function distributeFunds(
address ipId,
address token,
address[] calldata accounts,
address distributorAddress
) external;

/// @notice Claims the available royalties for a given account
/// @param account The account to claim for
/// @param withdrawETH The amount of ETH to withdraw
/// @param tokens The tokens to withdraw
function claimRoyalties(address account, uint256 withdrawETH, ERC20[] calldata tokens) external;

/// @notice Gets liquid split main address
function LIQUID_SPLIT_MAIN() external view returns (address);
}
21 changes: 21 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,33 @@ library Errors {
error RoyaltyModule__ZeroRoyaltyPolicy();
error RoyaltyModule__NotWhitelistedRoyaltyPolicy();
error RoyaltyModule__AlreadySetRoyaltyPolicy();
error RoyaltyModule__ZeroRoyaltyToken();
error RoyaltyModule__NotWhitelistedRoyaltyToken();
error RoyaltyModule__NoRoyaltyPolicySet();
error RoyaltyModule__IncompatibleRoyaltyPolicy();

error RoyaltyPolicyLS__ZeroRoyaltyModule();
error RoyaltyPolicyLS__ZeroLiquidSplitFactory();
error RoyaltyPolicyLS__ZeroLiquidSplitMain();
error RoyaltyPolicyLS__NotRoyaltyModule();
error RoyaltyPolicyLS__TransferFailed();
error RoyaltyPolicyLS__InvalidMinRoyalty();
error RoyaltyPolicyLS__InvalidRoyaltyStack();
error RoyaltyPolicyLS__ZeroMinRoyalty();
error RoyaltyPolicyLS__ZeroLicenseRegistry();

error LSClaimer__InvalidPath();
error LSClaimer__InvalidPathFirstPosition();
error LSClaimer__InvalidPathLastPosition();
error LSClaimer__AlreadyClaimed();
error LSClaimer__ZeroRNFT();
error LSClaimer__RNFTAlreadySet();
error LSClaimer__ETHBalanceNotZero();
error LSClaimer__ERC20BalanceNotZero();
error LSClaimer__ZeroIpId();
error LSClaimer__ZeroLicenseRegistry();
error LSClaimer__ZeroRoyaltyPolicyLS();
error LSClaimer__NotRoyaltyPolicyLS();

////////////////////////////////////////////////////////////////////////////
// ModuleRegistry //
Expand Down
67 changes: 45 additions & 22 deletions contracts/modules/royalty-module/RoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ contract RoyaltyModule is IRoyaltyModule, ReentrancyGuard {
/// @notice Indicates if a royalty policy is whitelisted
mapping(address royaltyPolicy => bool allowed) public isWhitelistedRoyaltyPolicy;

/// @notice Indicates if a royalty token is whitelisted
mapping(address token => bool) public isWhitelistedRoyaltyToken;

/// @notice Indicates the royalty policy for a given ipId
mapping(address ipId => address royaltyPolicy) public royaltyPolicies;

Expand All @@ -25,17 +28,11 @@ contract RoyaltyModule is IRoyaltyModule, ReentrancyGuard {
}

/// @notice Restricts the calls to the license module
modifier onlyLicenseModule() {
modifier onlyLicensingModule() {
// TODO: where is license module address defined?
_;
}

/// @notice Restricts the calls to a IPAccount
modifier onlyIPAccount() {
// TODO: where to find if an address is a valid IPAccount or an approved operator?
_;
}

/// @notice Whitelist a royalty policy
/// @param _royaltyPolicy The address of the royalty policy
/// @param _allowed Indicates if the royalty policy is whitelisted or not
Expand All @@ -44,37 +41,63 @@ contract RoyaltyModule is IRoyaltyModule, ReentrancyGuard {

isWhitelistedRoyaltyPolicy[_royaltyPolicy] = _allowed;

// TODO: emit event
emit RoyaltyPolicyWhitelistUpdated(_royaltyPolicy, _allowed);
}

/// @notice Whitelist a royalty token
/// @param _token The token address
/// @param _allowed Indicates if the token is whitelisted or not
function whitelistRoyaltyToken(address _token, bool _allowed) external onlyGovernance {
if (_token == address(0)) revert Errors.RoyaltyModule__ZeroRoyaltyToken();

isWhitelistedRoyaltyToken[_token] = _allowed;

emit RoyaltyTokenWhitelistUpdated(_token, _allowed);
}

// TODO: Ensure this function is called on ipId registration: root and derivatives registrations
// TODO: Ensure that the ipId that is passed in from license cannot be manipulated - given ipId addresses are deterministic
/// @notice Sets the royalty policy for an ipId
/// @param _ipId The ipId
/// @param _royaltyPolicy The address of the royalty policy
/// @param _parentIpIds The parent ipIds
/// @param _data The data to initialize the policy
function setRoyaltyPolicy(
address _ipId,
address _ipId,
address _royaltyPolicy,
address[] calldata _parentIpIds,
bytes calldata _data
) external onlyLicenseModule nonReentrant {
// TODO: make call to ensure ipId exists/has been registered
if (!isWhitelistedRoyaltyPolicy[_royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();
) external onlyLicensingModule nonReentrant {
if (royaltyPolicies[_ipId] != address(0)) revert Errors.RoyaltyModule__AlreadySetRoyaltyPolicy();
// TODO: check if royalty policy is compatible with parents royalty policy
if (!isWhitelistedRoyaltyPolicy[_royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();

for (uint32 i = 0; i < _parentIpIds.length; i++) {
if (royaltyPolicies[_parentIpIds[i]] != _royaltyPolicy) revert Errors.RoyaltyModule__IncompatibleRoyaltyPolicy();
}

royaltyPolicies[_ipId] = _royaltyPolicy;

IRoyaltyPolicy(_royaltyPolicy).initPolicy(_ipId, _data);
IRoyaltyPolicy(_royaltyPolicy).initPolicy(_ipId, _parentIpIds, _data);

// TODO: emit event
emit RoyaltyPolicySet(_ipId, _royaltyPolicy, _data);
}

/// @notice Allows an IPAccount to pay royalties
/// @param _ipId The ipId
/// @param _token The token to pay the royalties in
/// @notice Allows a sender to to pay royalties on behalf of an ipId
/// @param _receiverIpId The ipId that receives the royalties
/// @param _payerIpId The ipId that pays the royalties
/// @param _token The token to use to pay the royalties
/// @param _amount The amount to pay
function payRoyalty(address _ipId, address _token, uint256 _amount) external onlyIPAccount nonReentrant {
IRoyaltyPolicy(royaltyPolicies[_ipId]).onRoyaltyPayment(msg.sender, _ipId, _token, _amount);
function payRoyaltyOnBehalf(address _receiverIpId, address _payerIpId, address _token, uint256 _amount) external nonReentrant {
address royaltyPolicy = royaltyPolicies[_receiverIpId];
if (royaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet();
if (!isWhitelistedRoyaltyToken[_token]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyToken();

// TODO: check how to handle the below with replacement
if (!isWhitelistedRoyaltyPolicy[royaltyPolicy]) revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();

IRoyaltyPolicy(royaltyPolicy).onRoyaltyPayment(msg.sender, _receiverIpId, _token, _amount);

// TODO: emit event
// TODO: review event vs variable for royalty tracking
emit RoyaltyPaid(_receiverIpId, _payerIpId, msg.sender, _token, _amount);
}
}
}
Loading
Loading