Skip to content

Commit

Permalink
Store royalty percentage in the license token
Browse files Browse the repository at this point in the history
  • Loading branch information
kingster-will committed Dec 11, 2024
1 parent bd1d8ce commit d8e6df3
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 20 deletions.
14 changes: 11 additions & 3 deletions contracts/LicenseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils
import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";

import { ILicenseToken } from "./interfaces/ILicenseToken.sol";
import { ILicenseRegistry } from "./interfaces/registries/ILicenseRegistry.sol";
import { ILicensingModule } from "./interfaces/modules/licensing/ILicensingModule.sol";
import { IDisputeModule } from "./interfaces/modules/dispute/IDisputeModule.sol";
import { Errors } from "./lib/Errors.sol";
Expand All @@ -19,6 +20,8 @@ import { ILicenseTemplate } from "./interfaces/modules/licensing/ILicenseTemplat
contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManagedUpgradeable, UUPSUpgradeable {
using Strings for *;

/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ILicenseRegistry public immutable LICENSE_REGISTRY;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ILicensingModule public immutable LICENSING_MODULE;
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
Expand Down Expand Up @@ -48,9 +51,10 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag
}

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address licensingModule, address disputeModule) {
constructor(address licensingModule, address disputeModule, address licenseRegistry) {
LICENSING_MODULE = ILicensingModule(licensingModule);
DISPUTE_MODULE = IDisputeModule(disputeModule);
LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry);
_disableInitializers();
}

Expand Down Expand Up @@ -94,7 +98,8 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag
licensorIpId: licensorIpId,
licenseTemplate: licenseTemplate,
licenseTermsId: licenseTermsId,
transferable: ILicenseTemplate(licenseTemplate).isLicenseTransferable(licenseTermsId)
transferable: ILicenseTemplate(licenseTemplate).isLicenseTransferable(licenseTermsId),
commercialRevShare: LICENSE_REGISTRY.getRoyaltyPercent(licensorIpId, licenseTemplate, licenseTermsId)
});

LicenseTokenStorage storage $ = _getLicenseTokenStorage();
Expand Down Expand Up @@ -128,19 +133,21 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag
/// @return licenseTemplate The address of the License Template associated with the License Tokens.
/// @return licensorIpIds An array of licensor IPs associated with each License Token.
/// @return licenseTermsIds An array of License Terms associated with each validated License Token.
/// @return commercialRevShares An array of commercial revenue share percentages associated with each License Token.
function validateLicenseTokensForDerivative(
address caller,
address childIpId,
uint256[] calldata tokenIds
)
external
view
returns (address licenseTemplate, address[] memory licensorIpIds, uint256[] memory licenseTermsIds)
returns (address licenseTemplate, address[] memory licensorIpIds, uint256[] memory licenseTermsIds, uint32[] memory commercialRevShares)
{
LicenseTokenStorage storage $ = _getLicenseTokenStorage();
licenseTemplate = $.licenseTokenMetadatas[tokenIds[0]].licenseTemplate;
licensorIpIds = new address[](tokenIds.length);
licenseTermsIds = new uint256[](tokenIds.length);
commercialRevShares = new uint32[](tokenIds.length);

for (uint256 i = 0; i < tokenIds.length; i++) {
LicenseTokenMetadata memory ltm = $.licenseTokenMetadatas[tokenIds[i]];
Expand All @@ -160,6 +167,7 @@ contract LicenseToken is ILicenseToken, ERC721EnumerableUpgradeable, AccessManag

licensorIpIds[i] = ltm.licensorIpId;
licenseTermsIds[i] = ltm.licenseTermsId;
commercialRevShares[i] = ltm.commercialRevShare;
}
}

Expand Down
12 changes: 11 additions & 1 deletion contracts/interfaces/ILicenseToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface ILicenseToken is IERC721Metadata, IERC721Enumerable {
address licenseTemplate;
uint256 licenseTermsId;
bool transferable;
uint32 commercialRevShare;
}

/// @notice Emitted when a License Token is minted.
Expand Down Expand Up @@ -98,9 +99,18 @@ interface ILicenseToken is IERC721Metadata, IERC721Enumerable {
/// @return licenseTemplate The address of the License Template associated with the License Tokens.
/// @return licensorIpIds An array of licensor IPs associated with each License Token.
/// @return licenseTermsIds An array of License Terms associated with each validated License Token.
/// @return commercialRevShares An array of commercial revenue share percentages associated with each License Token.
function validateLicenseTokensForDerivative(
address caller,
address childIpId,
uint256[] calldata tokenIds
) external view returns (address licenseTemplate, address[] memory licensorIpIds, uint256[] memory licenseTermsIds);
)
external
view
returns (
address licenseTemplate,
address[] memory licensorIpIds,
uint256[] memory licenseTermsIds,
uint32[] memory commercialRevShares
);
}
13 changes: 13 additions & 0 deletions contracts/interfaces/registries/ILicenseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -217,4 +217,17 @@ interface ILicenseRegistry {
address childIpId,
address parentIpId
) external view returns (address licenseTemplate, uint256 licenseTermsId);

/// @notice Return the Royalty percentage of the license terms of the IP.
/// There are 2 places to get the royalty percentage: license terms, LicenseConfig
/// The order of priority is LicenseConfig > license terms
/// @param ipId The address of the IP.
/// @param licenseTemplate The address of the license template where the license terms are defined.
/// @param licenseTermsId The ID of the license terms.
/// @return royaltyPercent The Royalty percentage 100% is 100_000_000.
function getRoyaltyPercent(
address ipId,
address licenseTemplate,
uint256 licenseTermsId
) external view returns (uint32 royaltyPercent);
}
19 changes: 5 additions & 14 deletions contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ contract LicensingModule is
// Confirm that the license token has not been revoked.
// Validate that the owner of the derivative IP is also the owner of the license tokens.
address childIpOwner = IIPAccount(payable(childIpId)).owner();
(address licenseTemplate, address[] memory parentIpIds, uint256[] memory licenseTermsIds) = LICENSE_NFT
(address licenseTemplate, address[] memory parentIpIds, uint256[] memory licenseTermsIds, uint32[] memory royaltyPercents) = LICENSE_NFT
.validateLicenseTokensForDerivative(msg.sender, childIpId, licenseTokenIds);

_verifyIpNotDisputed(childIpId);
Expand All @@ -359,7 +359,7 @@ contract LicensingModule is
// all license terms.
LICENSE_REGISTRY.registerDerivativeIp(childIpId, parentIpIds, licenseTemplate, licenseTermsIds, true);

_setupRoyalty(childIpId, parentIpIds, licenseTermsIds, licenseTemplate, royaltyContext, maxRts);
_setupRoyalty(childIpId, parentIpIds, royaltyPercents, licenseTermsIds, licenseTemplate, royaltyContext, maxRts);

// burn license tokens
LICENSE_NFT.burnLicenseTokens(childIpOwner, licenseTokenIds);
Expand Down Expand Up @@ -516,6 +516,7 @@ contract LicensingModule is
function _setupRoyalty(
address childIpId,
address[] memory parentIpIds,
uint32[] memory royaltyPercents,
uint256[] memory licenseTermsIds,
address licenseTemplate,
bytes memory royaltyContext,
Expand All @@ -524,24 +525,14 @@ contract LicensingModule is
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
// Confirm that the royalty policies defined in all license terms of the parent IPs are identical.
address[] memory rPolicies = new address[](parentIpIds.length);
uint32[] memory rPercents = new uint32[](parentIpIds.length);
for (uint256 i = 0; i < parentIpIds.length; i++) {
(address royaltyPolicy, uint32 royaltyPercent, , ) = lct.getRoyaltyPolicy(licenseTermsIds[i]);
Licensing.LicensingConfig memory lsc = LICENSE_REGISTRY.getLicensingConfig(
parentIpIds[i],
licenseTemplate,
licenseTermsIds[i]
);
if (lsc.isSet && lsc.commercialRevShare != 0) {
royaltyPercent = lsc.commercialRevShare;
}
rPercents[i] = royaltyPercent;
(address royaltyPolicy, , , ) = lct.getRoyaltyPolicy(licenseTermsIds[i]);
rPolicies[i] = royaltyPolicy;
}

if (rPolicies.length != 0 && rPolicies[0] != address(0)) {
// Notify the royalty module
ROYALTY_MODULE.onLinkToParents(childIpId, parentIpIds, rPolicies, rPercents, royaltyContext, maxRts);
ROYALTY_MODULE.onLinkToParents(childIpId, parentIpIds, rPolicies, royaltyPercents, royaltyContext, maxRts);
}
}

Expand Down
20 changes: 20 additions & 0 deletions contracts/registries/LicenseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,26 @@ contract LicenseRegistry is ILicenseRegistry, AccessManagedUpgradeable, UUPSUpgr
return (_getLicenseTemplate(parentIpId), $.parentLicenseTerms[childIpId][parentIpId]);
}

/// @notice Return the Royalty percentage of the license terms of the IP.
/// There are 2 places to get the royalty percentage: license terms, LicenseConfig
/// The order of priority is LicenseConfig > license terms
/// @param ipId The address of the IP.
/// @param licenseTemplate The address of the license template where the license terms are defined.
/// @param licenseTermsId The ID of the license terms.
/// @return royaltyPercent The Royalty percentage 100% is 100_000_000.
function getRoyaltyPercent(
address ipId,
address licenseTemplate,
uint256 licenseTermsId
) external view returns (uint32 royaltyPercent) {
ILicenseTemplate lct = ILicenseTemplate(licenseTemplate);
(, royaltyPercent, , ) = lct.getRoyaltyPolicy(licenseTermsId);
Licensing.LicensingConfig memory lsc = _getLicensingConfig(ipId, licenseTemplate, licenseTermsId);
if (lsc.isSet && lsc.commercialRevShare > 0) {
royaltyPercent = lsc.commercialRevShare;
}
}

/// @dev verify the child IP can be registered as a derivative of the parent IP
/// @param parentIpId The address of the parent IP
/// @param childIpId The address of the child IP
Expand Down
2 changes: 1 addition & 1 deletion script/foundry/utils/DeployHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ contract DeployHelper is Script, BroadcastManager, JsonDeploymentHandler, Storag

contractKey = "LicenseToken";
_predeploy(contractKey);
impl = address(new LicenseToken(address(licensingModule), address(disputeModule)));
impl = address(new LicenseToken(address(licensingModule), address(disputeModule), address(licenseRegistry)));
licenseToken = LicenseToken(
TestProxyHelper.deployUUPSProxy(
create3Deployer,
Expand Down
41 changes: 40 additions & 1 deletion test/foundry/LicenseToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { PILFlavors } from "../../contracts/lib/PILFlavors.sol";
import { PILTerms } from "../../contracts/interfaces/modules/licensing/IPILicenseTemplate.sol";
import { LicenseToken } from "../../contracts/LicenseToken.sol";
import { ILicenseToken } from "../../contracts/interfaces/ILicenseToken.sol";
import { Licensing } from "../../contracts/lib/Licensing.sol";

// test
import { BaseTest } from "./utils/BaseTest.t.sol";
Expand Down Expand Up @@ -148,7 +149,7 @@ contract LicenseTokenTest is BaseTest {

function test_LicenseToken_TokenURI() public {
uint256 licenseTermsId = pilTemplate.registerLicenseTerms(PILFlavors.nonCommercialSocialRemixing());

uint256 commercialRevShare = 10_000_000;
vm.prank(address(licensingModule));
uint256 licenseTokenId = licenseToken.mintLicenseTokens({
licensorIpId: ipAcct[1],
Expand Down Expand Up @@ -204,4 +205,42 @@ contract LicenseTokenTest is BaseTest {
assertEq(lmt.licenseTermsId, licenseTermsId);
assertEq(lmt.transferable, true);
}

function test_LicenseToken_getLicenseTokenMetadata_commercialRevShare() public {
uint256 licenseTermsId = pilTemplate.registerLicenseTerms(
PILFlavors.commercialRemix(0, 10_000_000, address(royaltyPolicyLAP), address(USDC))
);

// attach license terms to the ipAcct
Licensing.LicensingConfig memory licensingConfig = Licensing.LicensingConfig({
isSet: true,
mintingFee: 0,
licensingHook: address(0),
hookData: "",
commercialRevShare: 20_000_000,
disabled: false,
expectMinimumGroupRewardShare: 0,
expectGroupRewardPool: address(0)
});
vm.startPrank(ipOwner[1]);
licensingModule.attachLicenseTerms(ipAcct[1], address(pilTemplate), licenseTermsId);
licensingModule.setLicensingConfig(ipAcct[1], address(pilTemplate), licenseTermsId, licensingConfig);
vm.stopPrank();
vm.prank(address(licensingModule));
uint256 licenseTokenId = licenseToken.mintLicenseTokens({
licensorIpId: ipAcct[1],
licenseTemplate: address(pilTemplate),
licenseTermsId: licenseTermsId,
amount: 1,
minter: ipOwner[1],
receiver: ipOwner[1]
});

ILicenseToken.LicenseTokenMetadata memory lmt = licenseToken.getLicenseTokenMetadata(licenseTokenId);
assertEq(lmt.licensorIpId, ipAcct[1]);
assertEq(lmt.licenseTemplate, address(pilTemplate));
assertEq(lmt.licenseTermsId, licenseTermsId);
assertEq(lmt.commercialRevShare, 20_000_000);
assertEq(lmt.transferable, true);
}
}

0 comments on commit d8e6df3

Please sign in to comment.