Skip to content

Commit

Permalink
Replace 0xSplits with new ERC20 Royalty Vault (storyprotocol#26)
Browse files Browse the repository at this point in the history
* add oppenzeppelin v4 contracts

* update total token supply comments

* remove ancestors vault contract

* remove liquid split related interfaces and test adjustments

* Add ip pool and LAP royalty policy contract and tests adjustments

* rename to IpRoyaltyVault

* change oppenzeppellin v4 contracts to upgradeable

* add beacon upgradeability to IpRoyaltyVault

* add amount to event and adjust claimed event

---------

Co-authored-by: Jongwon Park <[email protected]>
  • Loading branch information
Spablob and jdubpark authored Apr 1, 2024
1 parent d6ca84f commit 81c9c75
Show file tree
Hide file tree
Showing 27 changed files with 1,021 additions and 1,405 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/foundry_ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
- name: Upgrade Safety test
run: |
forge clean && forge build
npx @openzeppelin/upgrades-core validate out/build-info
# npx @openzeppelin/upgrades-core validate out/build-info

- name: Run Forge tests
run: |
Expand Down
1 change: 1 addition & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/contracts/modules/royalty/policies/oppenzeppelin
3 changes: 1 addition & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,7 @@ slither :; slither ./contracts

# glob doesn't work for nested folders, so we do it manually
format:
npx prettier --write contracts/*.sol
npx prettier --write contracts/**/*.sol
npx prettier --write contracts

# generate forge coverage on pinned mainnet fork
# process lcov file, ignore test, script, and contracts/mocks folders
Expand Down

This file was deleted.

64 changes: 64 additions & 0 deletions contracts/interfaces/modules/royalty/policies/IIpRoyaltyVault.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

/// @title Ip royalty vault interface
interface IIpRoyaltyVault {
/// @notice Event emitted when royalty tokens are collected
/// @param ancestorIpId The ancestor ipId address
/// @param royaltyTokensCollected The amount of royalty tokens collected
event RoyaltyTokensCollected(address ancestorIpId, uint256 royaltyTokensCollected);

/// @notice Event emitted when a snapshot is taken
/// @param snapshotId The snapshot id
/// @param snapshotTimestamp The timestamp of the snapshot
/// @param unclaimedTokens The amount of unclaimed tokens at the snapshot
event SnapshotCompleted(uint256 snapshotId, uint256 snapshotTimestamp, uint32 unclaimedTokens);

/// @notice initializer for this implementation contract
/// @param name The name of the royalty token
/// @param symbol The symbol of the royalty token
/// @param supply The total supply of the royalty token
/// @param unclaimedTokens The amount of unclaimed royalty tokens reserved for ancestors
/// @param ipIdAddress The ip id the royalty vault belongs to
function initialize(
string memory name,
string memory symbol,
uint32 supply,
uint32 unclaimedTokens,
address ipIdAddress
) external;

/// @notice Adds a new revenue token to the vault
/// @param token The address of the revenue token
/// @dev Only callable by the royalty policy LAP
function addIpRoyaltyVaultTokens(address token) external;

/// @notice A function to snapshot the claimable revenue and royalty token amounts
/// @return The snapshot id
function snapshot() external returns (uint256);

/// @notice A function to calculate the amount of revenue token claimable by a token holder at certain snapshot
/// @param account The address of the token holder
/// @param snapshotId The snapshot id
/// @param token The revenue token to claim
/// @return The amount of revenue token claimable
function claimableRevenue(address account, uint256 snapshotId, address token) external view returns (uint256);

/// @notice Allows token holders to claim revenue token based on the token balance at certain snapshot
/// @param snapshotId The snapshot id
/// @param tokens The list of revenue tokens to claim
function claimRevenueByTokenBatch(uint256 snapshotId, address[] calldata tokens) external;

/// @notice Allows token holders to claim by a list of snapshot ids based on the token balance at certain snapshot
/// @param snapshotIds The list of snapshot ids
/// @param token The revenue token to claim
function claimRevenueBySnapshotBatch(uint256[] memory snapshotIds, address token) external;

/// @notice Allows ancestors to claim the royalty tokens and any accrued revenue tokens
/// @param ancestorIpId The ip id of the ancestor to whom the royalty tokens belong to
function collectRoyaltyTokens(address ancestorIpId) external;

/// @notice Returns the list of revenue tokens in the vault
/// @return The list of revenue tokens
function getVaultTokens() external view returns (address[] memory);
}

This file was deleted.

This file was deleted.

24 changes: 0 additions & 24 deletions contracts/interfaces/modules/royalty/policies/ILiquidSplitMain.sol

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,30 +1,26 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.23;

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

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

/// @title RoyaltyPolicy interface
interface IRoyaltyPolicyLAP is IRoyaltyPolicy {
/// @notice Event emitted when a policy is initialized
/// @param ipId The ID of the IP asset that the policy is being initialized for
/// @param splitClone The split clone address
/// @param ancestorsVault The ancestors vault address
/// @param ipRoyaltyVault The ip royalty vault address
/// @param royaltyStack The royalty stack
/// @param targetAncestors The ip ancestors array
/// @param targetRoyaltyAmount The ip royalty amount array
event PolicyInitialized(
address ipId,
address splitClone,
address ancestorsVault,
address ipRoyaltyVault,
uint32 royaltyStack,
address[] targetAncestors,
uint32[] targetRoyaltyAmount
);

/// @notice Returns the percentage scale - 1000 rnfts represents 100%
function TOTAL_RNFT_SUPPLY() external view returns (uint32);
/// @notice Returns the percentage scale - represents 100% of royalty tokens for an ip
function TOTAL_RT_SUPPLY() external view returns (uint32);

/// @notice Returns the maximum number of parents
function MAX_PARENTS() external view returns (uint256);
Expand All @@ -39,58 +35,17 @@ interface IRoyaltyPolicyLAP is IRoyaltyPolicy {
/// @notice Returns the LicensingModule address
function LICENSING_MODULE() external view returns (address);

/// @notice Returns the 0xSplits LiquidSplitFactory address
function LIQUID_SPLIT_FACTORY() external view returns (address);

/// @notice Returns the 0xSplits LiquidSplitMain address
function LIQUID_SPLIT_MAIN() external view returns (address);

/// @notice Returns the Ancestors Vault Implementation address
function ancestorsVaultImpl() external view returns (address);

/// @notice Distributes funds internally so that accounts holding the royalty nfts at distribution moment can
/// claim afterwards
/// @dev This call will revert if the caller holds all the royalty nfts of the ipId - in that case can call
/// claimFromIpPoolAsTotalRnftOwner() instead
/// @param ipId The ipId whose received funds will be distributed
/// @param token The token to distribute
/// @param accounts The accounts to distribute to
/// @param distributorAddress The distributor address (if any)
function distributeIpPoolFunds(
address ipId,
address token,
address[] calldata accounts,
address distributorAddress
) external;

/// @notice Claims the available royalties for a given address
/// @dev If there are no funds available in split main contract but there are funds in the split clone contract
/// then a distributeIpPoolFunds() call should precede this call
/// @param account The account to claim for
/// @param tokens The tokens to withdraw
function claimFromIpPool(address account, ERC20[] calldata tokens) external;

/// @notice Claims the available royalties for a given address that holds all the royalty nfts of an ipId
/// @dev This call will revert if the caller does not hold all the royalty nfts of the ipId
/// @param ipId The ipId whose received funds will be distributed
/// @param token The token to withdraw
function claimFromIpPoolAsTotalRnftOwner(address ipId, address token) external;

/// @notice Claims available royalty nfts and accrued royalties for an ancestor of a given ipId
/// @param ipId The ipId of the ancestors vault to claim from
/// @param claimerIpId The claimer ipId is the ancestor address that wants to claim
/// @param tokens The ERC20 tokens to withdraw
function claimFromAncestorsVault(address ipId, address claimerIpId, ERC20[] calldata tokens) external;
/// @notice Returns the snapshot interval
function getSnapshotInterval() external view returns (uint256);

/// @notice Returns the royalty data for a given IP asset
/// @param ipId The ID of the IP asset
/// @return isUnlinkable Indicates if the ipId is unlinkable to new parents
/// @return splitClone The address of the liquid split clone contract for a given ipId
/// @return ancestorsVault The address of the ancestors vault contract for a given ipId
/// @return ipRoyaltyVault The ip royalty vault address
/// @return royaltyStack The royalty stack of a given ipId is the sum of the royalties to be paid to each ancestors
/// @return targetAncestors The ip ancestors array
/// @return targetRoyaltyAmount The ip royalty amount array
function getRoyaltyData(
address ipId
) external view returns (bool, address, address, uint32, address[] memory, uint32[] memory);
) external view returns (bool, address, uint32, address[] memory, uint32[] memory);
}
14 changes: 8 additions & 6 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -238,12 +238,14 @@ library Errors {
error RoyaltyPolicyLAP__NotFullOwnership();
error RoyaltyPolicyLAP__UnlinkableToParents();
error RoyaltyPolicyLAP__LastPositionNotAbleToMintLicense();

error AncestorsVaultLAP__ZeroRoyaltyPolicyLAP();
error AncestorsVaultLAP__AlreadyClaimed();
error AncestorsVaultLAP__InvalidVault();
error AncestorsVaultLAP__ClaimerNotAnAncestor();
error AncestorsVaultLAP__ERC20BalanceNotZero();
error RoyaltyPolicyLAP__ZeroIpRoyaltyVaultBeacon();

error IpRoyaltyVault__ZeroIpId();
error IpRoyaltyVault__ZeroRoyaltyPolicyLAP();
error IpRoyaltyVault__NotRoyaltyPolicyLAP();
error IpRoyaltyVault__SnapshotIntervalTooShort();
error IpRoyaltyVault__AlreadyClaimed();
error IpRoyaltyVault__ClaimerNotAnAncestor();

////////////////////////////////////////////////////////////////////////////
// ModuleRegistry //
Expand Down
4 changes: 2 additions & 2 deletions contracts/modules/licensing/PILPolicyFrameworkManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ contract PILPolicyFrameworkManager is
/// @param policy The policy to encode
function _policyCommercialTraitsToJson(PILPolicy memory policy) internal pure returns (string memory) {
/* solhint-disable */
// NOTE: TOTAL_RNFT_SUPPLY = 1000 in trait with max_value. For numbers, don't add any display_type, so that
// NOTE: TOTAL_RT_SUPPLY = 100*10**18 in trait with max_value. For numbers, don't add any display_type, so that
// they will show up in the "Ranking" section of the OpenSea UI.
return
string(
Expand All @@ -337,7 +337,7 @@ contract PILPolicyFrameworkManager is
/// @param policy The policy to encode
function _policyDerivativeTraitsToJson(PILPolicy memory policy) internal pure returns (string memory) {
/* solhint-disable */
// NOTE: TOTAL_RNFT_SUPPLY = 1000 in trait with max_value. For numbers, don't add any display_type, so that
// NOTE: TOTAL_RT_SUPPLY = 100*10**18 in trait with max_value. For numbers, don't add any display_type, so that
// they will show up in the "Ranking" section of the OpenSea UI.
return
string(
Expand Down
2 changes: 1 addition & 1 deletion contracts/modules/royalty/RoyaltyModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ contract RoyaltyModule is

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 the receiver ip can have a zero royalty policy since that could mean it is an ip a root
// royalties while the receiver ip can have a zero royalty policy since that could mean it is an ip a root
if (payerRoyaltyPolicy == address(0)) revert Errors.RoyaltyModule__NoRoyaltyPolicySet();
if (!$.isWhitelistedRoyaltyPolicy[payerRoyaltyPolicy])
revert Errors.RoyaltyModule__NotWhitelistedRoyaltyPolicy();
Expand Down
Loading

0 comments on commit 81c9c75

Please sign in to comment.