Skip to content

Commit

Permalink
feat(grouping): add royalty claiming for group IPs (#97)
Browse files Browse the repository at this point in the history
* feat(grouping): support royalty claiming for group IPs

* fix(test): use new group pool 4 tests

* test(grouping): add tests for group royalty claim

* fix linting
  • Loading branch information
sebsadface authored Oct 16, 2024
1 parent 8da160a commit 55e6338
Show file tree
Hide file tree
Showing 8 changed files with 226 additions and 65 deletions.
1 change: 0 additions & 1 deletion contracts/interfaces/story-nft/IStoryNFT.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ interface IStoryNFT is IERC721, IERC7572 {
/// @notice Zero address provided as a param to StoryNFT constructor.
error StoryNFT__ZeroAddressParam();


////////////////////////////////////////////////////////////////////////////
// Structs //
////////////////////////////////////////////////////////////////////////////
Expand Down
13 changes: 13 additions & 0 deletions contracts/interfaces/workflows/IGroupingWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,17 @@ interface IGroupingWorkflows {
address licenseTemplate,
uint256 licenseTermsId
) external returns (address groupId);

/// @notice Collect royalties for the entire group and distribute the rewards to each member IP's royalty vault
/// @param groupId The ID of the group IP.
/// @param currencyTokens The addresses of the currency (revenue) tokens to claim.
/// @param groupSnapshotIds The IDs of the snapshots to collect royalties on.
/// @param memberIpIds The IDs of the member IPs to distribute the rewards to.
/// @return collectedRoyalties The amounts of royalties collected for each currency token.
function collectRoyaltiesAndClaimReward(
address groupId,
address[] calldata currencyTokens,
uint256[] calldata groupSnapshotIds,
address[] calldata memberIpIds
) external returns (uint256[] memory collectedRoyalties);
}
20 changes: 20 additions & 0 deletions contracts/workflows/GroupingWorkflows.sol
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,26 @@ contract GroupingWorkflows is
GROUP_NFT.safeTransferFrom(address(this), msg.sender, GROUP_NFT.totalSupply() - 1);
}

/// @notice Collect royalties for the entire group and distribute the rewards to each member IP's royalty vault
/// @param groupId The ID of the group IP.
/// @param currencyTokens The addresses of the currency (revenue) tokens to claim.
/// @param groupSnapshotIds The IDs of the snapshots to collect royalties on.
/// @param memberIpIds The IDs of the member IPs to distribute the rewards to.
/// @return collectedRoyalties The amounts of royalties collected for each currency token.
function collectRoyaltiesAndClaimReward(
address groupId,
address[] calldata currencyTokens,
uint256[] calldata groupSnapshotIds,
address[] calldata memberIpIds
) external returns (uint256[] memory collectedRoyalties) {
collectedRoyalties = new uint256[](currencyTokens.length);
for (uint256 i = 0; i < currencyTokens.length; i++) {
if (currencyTokens[i] == address(0)) revert Errors.GroupingWorkflows__ZeroAddressParam();
collectedRoyalties[i] = GROUPING_MODULE.collectRoyalties(groupId, currencyTokens[i], groupSnapshotIds);
GROUPING_MODULE.claimReward(groupId, currencyTokens[i], memberIpIds);
}
}

//
// Upgrade
//
Expand Down
48 changes: 0 additions & 48 deletions script/deployment/MockRewardPool.s.sol

This file was deleted.

28 changes: 27 additions & 1 deletion script/utils/DeployHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { CoreMetadataModule } from "@storyprotocol/core/modules/metadata/CoreMet
import { CoreMetadataViewModule } from "@storyprotocol/core/modules/metadata/CoreMetadataViewModule.sol";
import { DisputeModule } from "@storyprotocol/core/modules/dispute/DisputeModule.sol";
import { GroupingModule } from "@storyprotocol/core/modules/grouping/GroupingModule.sol";
import { EvenSplitGroupPool } from "@storyprotocol/core/modules/grouping/EvenSplitGroupPool.sol";
import { GroupNFT } from "@storyprotocol/core/GroupNFT.sol";
import { IPAccountImpl } from "@storyprotocol/core/IPAccountImpl.sol";
import { IPAssetRegistry } from "@storyprotocol/core/registries/IPAssetRegistry.sol";
Expand All @@ -32,7 +33,6 @@ import { RoyaltyPolicyLRP } from "@storyprotocol/core/modules/royalty/policies/L
import { StorageLayoutChecker } from "@storyprotocol/script/utils/upgrades/StorageLayoutCheck.s.sol";

// contracts
import { IStoryNFT } from "../../contracts/interfaces/story-nft/IStoryNFT.sol";
import { SPGNFT } from "../../contracts/SPGNFT.sol";
import { DerivativeWorkflows } from "../../contracts/workflows/DerivativeWorkflows.sol";
import { GroupingWorkflows } from "../../contracts/workflows/GroupingWorkflows.sol";
Expand Down Expand Up @@ -114,6 +114,7 @@ contract DeployHelper is
RoyaltyPolicyLAP internal royaltyPolicyLAP;
RoyaltyPolicyLRP internal royaltyPolicyLRP;
UpgradeableBeacon internal ipRoyaltyVaultBeacon;
EvenSplitGroupPool internal evenSplitGroupPool;

// mock core contract deployer
address internal mockDeployer;
Expand Down Expand Up @@ -782,6 +783,28 @@ contract DeployHelper is
"Deploy: Grouping Module Address Mismatch"
);
require(_loadProxyImpl(address(groupingModule)) == impl, "GroupingModule Proxy Implementation Mismatch");

_predeploy("EvenSplitGroupPool");
impl = address(new EvenSplitGroupPool(
address(groupingModule),
address(royaltyModule),
address(ipAssetRegistry)
));
evenSplitGroupPool = EvenSplitGroupPool(
TestProxyHelper.deployUUPSProxy(
create3Deployer,
_getSalt(type(EvenSplitGroupPool).name),
impl,
abi.encodeCall(EvenSplitGroupPool.initialize, address(protocolAccessManager))
)
);
require(
_getDeployedAddress(type(EvenSplitGroupPool).name) == address(evenSplitGroupPool),
"Deploy: EvenSplitGroupPool Address Mismatch"
);
require(_loadProxyImpl(address(evenSplitGroupPool)) == impl, "EvenSplitGroupPool Proxy Implementation Mismatch");
impl = address(0);
_postdeploy("EvenSplitGroupPool", address(evenSplitGroupPool));
}

function _configureMockCoreContracts() private {
Expand All @@ -807,6 +830,9 @@ contract DeployHelper is
royaltyModule.whitelistRoyaltyPolicy(address(royaltyPolicyLRP), true);
royaltyModule.setIpRoyaltyVaultBeacon(address(ipRoyaltyVaultBeacon));
ipRoyaltyVaultBeacon.transferOwnership(address(royaltyPolicyLAP));

// add evenSplitGroupPool to whitelist of group pools
groupingModule.whitelistGroupRewardPool(address(evenSplitGroupPool));
}

/// @dev get the salt for the contract deployment with CREATE3
Expand Down
6 changes: 4 additions & 2 deletions test/integration/workflows/RoyaltyIntegration.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@ contract RoyaltyIntegration is BaseIntegration {
royaltyModule.maxPercent() + // 1000 * 10% = 100 royalty from childIpB
(((defaultMintingFeeA * defaultCommRevShareA) / royaltyModule.maxPercent()) * defaultCommRevShareA) /
royaltyModule.maxPercent() + // 1000 * 10% * 10% * 2 = 20 royalty from grandChildIp
defaultMintingFeeC + (defaultMintingFeeC * defaultCommRevShareC) /
defaultMintingFeeC +
(defaultMintingFeeC * defaultCommRevShareC) /
royaltyModule.maxPercent() // 500 from from minting fee of childIpC,500 * 20% = 100 royalty from childIpC
);
}
Expand Down Expand Up @@ -189,7 +190,8 @@ contract RoyaltyIntegration is BaseIntegration {
royaltyModule.maxPercent() + // 1000 * 10% = 100 royalty from childIpB
(((defaultMintingFeeA * defaultCommRevShareA) / royaltyModule.maxPercent()) * defaultCommRevShareA) /
royaltyModule.maxPercent() + // 1000 * 10% * 10% = 10 royalty from grandChildIp
defaultMintingFeeC + (defaultMintingFeeC * defaultCommRevShareC) /
defaultMintingFeeC +
(defaultMintingFeeC * defaultCommRevShareC) /
royaltyModule.maxPercent() // 500 from from minting fee of childIpC, 500 * 20% = 100 royalty from childIpC
);
}
Expand Down
9 changes: 0 additions & 9 deletions test/utils/BaseTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { ICoreMetadataModule } from "@storyprotocol/core/interfaces/modules/meta
import { IIPAccount } from "@storyprotocol/core/interfaces/IIPAccount.sol";
import { ILicensingModule } from "@storyprotocol/core/interfaces/modules/licensing/ILicensingModule.sol";
import { MetaTx } from "@storyprotocol/core/lib/MetaTx.sol";
import { MockEvenSplitGroupPool } from "@storyprotocol/test/mocks/grouping/MockEvenSplitGroupPool.sol";
import { MockIPGraph } from "@storyprotocol/test/mocks/MockIPGraph.sol";

// contracts
Expand Down Expand Up @@ -56,9 +55,6 @@ contract BaseTest is Test, DeployHelper {
address internal CREATE3_DEPLOYER = address(new Create3Deployer());
uint256 internal CREATE3_DEFAULT_SEED = 1234567890;

/// @dev MockEvenSplitGroupPool for testing
MockEvenSplitGroupPool internal mockRewardPool;

/// @dev Mock assets
MockERC20 internal mockToken;
MockERC721 internal mockNft;
Expand Down Expand Up @@ -122,11 +118,6 @@ contract BaseTest is Test, DeployHelper {
groupingWorkflows.setNftContractBeacon(address(spgNftBeacon));
licenseAttachmentWorkflows.setNftContractBeacon(address(spgNftBeacon));
registrationWorkflows.setNftContractBeacon(address(spgNftBeacon));

// whitelist mockRewardPool as a group reward pool
mockRewardPool = new MockEvenSplitGroupPool(royaltyModuleAddr);
groupingModule.whitelistGroupRewardPool(address(mockRewardPool));

vm.stopPrank();
}

Expand Down
Loading

0 comments on commit 55e6338

Please sign in to comment.