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

Dispute and licensing integration #93

Merged
merged 14 commits into from
Feb 17, 2024
Merged
21 changes: 21 additions & 0 deletions contracts/interfaces/modules/dispute/IDisputeModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -155,4 +155,25 @@ interface IDisputeModule {
bytes32 targetTag, // The target tag of the dispute
bytes32 currentTag // The current tag of the dispute
);

/// @notice returns true if the ipId is tagged with the tag (meaning the dispute went through)
/// @param _ipId The ipId
/// @param _tag The tag
function isIpTaggedWith(address _ipId, bytes32 _tag) external view returns (bool);

/// @notice returns true if the ipId is tagged with any tag (meaning at least one dispute went through)
/// @param _ipId The ipId
function isIpTagged(address _ipId) external view returns (bool);

/// @notice returns the tags for a given ipId (note: this method could be expensive, use in frontends only)
/// @param _ipId The ipId
function ipTags(address _ipId) external view returns (bytes32[] memory);

/// @notice returns the total tags for a given ipId
/// @param _ipId The ipId
function totalTagsForIp(address _ipId) external view returns (uint256);

/// @notice returns the tag at a given index for a given ipId. No guarantees on ordering
/// @param _ipId The ipId
function tagForIpAt(address _ipId, uint256 _index) external view returns (bytes32);
}
9 changes: 5 additions & 4 deletions contracts/interfaces/registries/ILicenseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,6 @@ interface ILicenseRegistry is IERC1155 {
Licensing.License licenseData
);

/// @notice Set the licensing module
/// @param newLicensingModule The address of the licensing module
function setLicensingModule(address newLicensingModule) external;

/// @notice Returns the address of the licensing module
function licensingModule() external view returns (address);

Expand Down Expand Up @@ -65,4 +61,9 @@ interface ILicenseRegistry is IERC1155 {

/// @notice License data (licensor, policy...) for the license id
function license(uint256 licenseId) external view returns (Licensing.License memory);

/// @notice Returns true if the license has been revoked (licensor tagged after a dispute in
/// the dispute module). If the tag is removed, the license is not revoked anymore.
/// @param licenseId The id of the license
function isLicenseRevoked(uint256 licenseId) external view returns (bool);
}
7 changes: 7 additions & 0 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,11 @@ library Errors {
error LicenseRegistry__CallerNotLicensingModule();
error LicenseRegistry__ZeroLicensingModule();
error LicensingModule__CallerNotLicenseRegistry();
error LicenseRegistry__RevokedLicense();
/// @notice emitted when trying to transfer a license that is not transferable (by policy)
error LicenseRegistry__NotTransferable();
/// @notice emitted on constructor if dispute module is not set
error LicenseRegistry__ZeroDisputeModule();

////////////////////////////////////////////////////////////////////////////
// LicensingModule //
Expand Down Expand Up @@ -149,6 +152,10 @@ library Errors {
error LicensingModule__DerivativeRevShareSumExceedsMaxRNFTSupply();
error LicensingModule__MismatchBetweenCommercialRevenueShareAndMinRoyalty();
error LicensingModule__MismatchBetweenRoyaltyPolicy();
/// @notice emitted when trying to interact with an IP that has been disputed in the DisputeModule
error LicensingModule__DisputedIpId();
/// @notice emitted when linking a license from a licensor that has been disputed in the DisputeModule
error LicensingModule__LinkingRevokedLicense();

////////////////////////////////////////////////////////////////////////////
// LicensingModuleAware //
Expand Down
5 changes: 3 additions & 2 deletions contracts/lib/Licensing.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ library Licensing {
}

/// @notice Data that define a License Agreement NFT
/// @param policyId Id of the policy this license is based on, which will be set in the derivative
/// IP when the license is burnt
/// @param policyId Id of the policy this license is based on, which will be set in the derivative IP when the
/// license is burnt for linking
/// @param licensorIpId Id of the IP this license is for
/// @param transferable Whether or not the license is transferable
struct License {
uint256 policyId;
address licensorIpId;
Expand Down
40 changes: 40 additions & 0 deletions contracts/modules/dispute-module/DisputeModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
pragma solidity ^0.8.23;

import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";

import { DISPUTE_MODULE_KEY } from "../../lib/modules/Module.sol";
import { BaseModule } from "../../modules/BaseModule.sol";
Expand All @@ -17,6 +18,7 @@ import { ShortStringOps } from "../../utils/ShortStringOps.sol";
/// @notice The Story Protocol dispute module acts as an enforcement layer for
/// that allows to raise disputes and resolve them through arbitration.
contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuard, AccessControlled {
using EnumerableSet for EnumerableSet.Bytes32Set;
/// @notice tag to represent the dispute is in dispute state waiting for judgement
bytes32 public constant IN_DISPUTE = bytes32("IN_DISPUTE");

Expand Down Expand Up @@ -54,6 +56,8 @@ contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuar
/// @notice Arbitration policy for a given ipId
mapping(address ipId => address arbitrationPolicy) public arbitrationPolicies;

mapping(address ipId => EnumerableSet.Bytes32Set) private _taggedIpIds;

/// @notice Initializes the registration module contract
/// @param _controller The access controller used for IP authorization
/// @param _assetRegistry The address of the IP asset registry
Expand Down Expand Up @@ -189,6 +193,8 @@ contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuar

if (_decision) {
disputes[_disputeId].currentTag = dispute.targetTag;
// We ignore the result of add(), we don't care if the tag is already there
_taggedIpIds[dispute.targetIpId].add(dispute.targetTag);
} else {
disputes[_disputeId].currentTag = bytes32(0);
}
Expand Down Expand Up @@ -222,11 +228,45 @@ contract DisputeModule is IDisputeModule, BaseModule, Governable, ReentrancyGuar
if (dispute.currentTag == IN_DISPUTE) revert Errors.DisputeModule__NotAbleToResolve();
if (msg.sender != dispute.disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator();

// Ignore the result of remove(), resolveDispute can only be called once when there's a dispute tag.
// Once resolveDispute is called, the tag will be removed and calling this fn again will throw an error.
_taggedIpIds[dispute.targetIpId].remove(dispute.currentTag);
Ramarti marked this conversation as resolved.
Show resolved Hide resolved
disputes[_disputeId].currentTag = bytes32(0);

emit DisputeResolved(_disputeId);
}

/// @notice returns true if the ipId is tagged with the tag (meaning the dispute went through)
/// @param _ipId The ipId
/// @param _tag The tag
function isIpTaggedWith(address _ipId, bytes32 _tag) external view returns (bool) {
return _taggedIpIds[_ipId].contains(_tag);
}

/// @notice returns true if the ipId is tagged with any tag (meaning at least one dispute went through)
/// @param _ipId The ipId
function isIpTagged(address _ipId) external view returns (bool) {
return _taggedIpIds[_ipId].length() > 0;
}

/// @notice returns the tags for a given ipId (note: this method could be expensive, use in frontends only)
/// @param _ipId The ipId
function ipTags(address _ipId) external view returns (bytes32[] memory) {
return _taggedIpIds[_ipId].values();
}

/// @notice returns the total tags for a given ipId
/// @param _ipId The ipId
function totalTagsForIp(address _ipId) external view returns (uint256) {
return _taggedIpIds[_ipId].length();
}

/// @notice returns the tag at a given index for a given ipId. No guarantees on ordering
/// @param _ipId The ipId
function tagForIpAt(address _ipId, uint256 _index) external view returns (bytes32) {
return _taggedIpIds[_ipId].at(_index);
}

/// @notice Gets the protocol-wide module identifier for this module
/// @return The dispute module key
function name() public pure override returns (string memory) {
Expand Down
20 changes: 18 additions & 2 deletions contracts/modules/licensing/LicensingModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { IPolicyFrameworkManager } from "../../interfaces/modules/licensing/IPol
import { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol";
import { ILicensingModule } from "../../interfaces/modules/licensing/ILicensingModule.sol";
import { IIPAccountRegistry } from "../../interfaces/registries/IIPAccountRegistry.sol";
import { IDisputeModule } from "../../interfaces/modules/dispute/IDisputeModule.sol";
import { Errors } from "../../lib/Errors.sol";
import { DataUniqueness } from "../../lib/DataUniqueness.sol";
import { Licensing } from "../../lib/Licensing.sol";
Expand Down Expand Up @@ -40,6 +41,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen

RoyaltyModule public immutable ROYALTY_MODULE;
ILicenseRegistry public immutable LICENSE_REGISTRY;
IDisputeModule public immutable DISPUTE_MODULE;

string public constant override name = LICENSING_MODULE_KEY;
mapping(address framework => bool registered) private _registeredFrameworkManagers;
Expand All @@ -63,10 +65,12 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
address accessController,
address ipAccountRegistry,
address royaltyModule,
address registry
address registry,
address disputeModule
) AccessControlled(accessController, ipAccountRegistry) {
ROYALTY_MODULE = RoyaltyModule(royaltyModule);
LICENSE_REGISTRY = ILicenseRegistry(registry);
DISPUTE_MODULE = IDisputeModule(disputeModule);
}

function licenseRegistry() external view returns (address) {
Expand Down Expand Up @@ -130,6 +134,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
if (!isPolicyDefined(polId)) {
revert Errors.LicensingModule__PolicyNotFound();
}
_verifyIpNotDisputed(ipId);

indexOnIpId = _addPolicyIdToIp({ ipId: ipId, policyId: polId, isInherited: false, skipIfDuplicate: false });

Expand Down Expand Up @@ -171,10 +176,10 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
uint256 amount, // mint amount
address receiver
) external nonReentrant returns (uint256 licenseId) {
// TODO: check if licensor has been tagged by disputer
if (!IP_ACCOUNT_REGISTRY.isIpAccount(licensorIp)) {
revert Errors.LicensingModule__LicensorNotRegistered();
}
_verifyIpNotDisputed(licensorIp);

bool isInherited = _policySetups[licensorIp][policyId].isInherited;
Licensing.Policy memory pol = policy(policyId);
Expand Down Expand Up @@ -262,6 +267,7 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
address childIpId,
uint32 minRoyalty
) external nonReentrant verifyPermission(childIpId) {
_verifyIpNotDisputed(childIpId);
address holder = IIPAccount(payable(childIpId)).owner();
address[] memory licensors = new address[](licenseIds.length);
// If royalty policy address is address(0), this means no royalty policy to set.
Expand All @@ -271,6 +277,9 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen

for (uint256 i = 0; i < licenseIds.length; i++) {
uint256 licenseId = licenseIds[i];
if (LICENSE_REGISTRY.isLicenseRevoked(licenseId)) {
revert Errors.LicensingModule__LinkingRevokedLicense();
}
if (!LICENSE_REGISTRY.isLicensee(licenseId, holder)) {
revert Errors.LicensingModule__NotLicensee();
}
Expand Down Expand Up @@ -556,4 +565,11 @@ contract LicensingModule is AccessControlled, ILicensingModule, BaseModule, Reen
function _policySetPerIpId(bool isInherited, address ipId) private view returns (EnumerableSet.UintSet storage) {
return _policiesPerIpId[keccak256(abi.encode(isInherited, ipId))];
}

function _verifyIpNotDisputed(address ipId) private view {
// TODO: in beta, any tag means revocation, for mainnet we need more context
if (DISPUTE_MODULE.isIpTagged(ipId)) {
jdubpark marked this conversation as resolved.
Show resolved Hide resolved
revert Errors.LicensingModule__DisputedIpId();
}
}
}
Loading
Loading