Skip to content

Commit

Permalink
Dispute modifications (storyprotocol#60)
Browse files Browse the repository at this point in the history
* add new tagDerivativeIfParentInfringed function

* format fix

* rebase: eliminate data uniqueness

* fix rebase duplication

* revert royalty related fixes

* format fix

* add business logic comments

* add restriction clarifications

* fix comment size

---------

Co-authored-by: Jongwon Park <[email protected]>
Co-authored-by: kingster-will <[email protected]>
Co-authored-by: Andy Wu <[email protected]>
Co-authored-by: Ramarti <[email protected]>
  • Loading branch information
5 people committed Apr 14, 2024
1 parent a2fe556 commit e62c6db
Show file tree
Hide file tree
Showing 17 changed files with 392 additions and 94 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ format:
coverage:
mkdir -p coverage
forge coverage --report lcov
lcov --remove lcov.info -o coverage/lcov.info 'test/*' 'script/*' --rc branch_coverage=1
genhtml coverage/lcov.info -o coverage --rc branch_coverage=1 --ignore-errors category
lcov --remove lcov.info -o coverage/lcov.info 'test/*' 'script/*' --rc lcov_branch_coverage=1
genhtml coverage/lcov.info -o coverage --rc lcov_branch_coverage=1

abi:
rm -rf abi
Expand Down
35 changes: 31 additions & 4 deletions contracts/interfaces/modules/dispute/IDisputeModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ interface IDisputeModule {
/// @param linkToDisputeEvidence The link of the dispute evidence
/// @param targetTag The target tag of the dispute
/// @param currentTag The current tag of the dispute
/// @param parentDisputeId The parent dispute id
struct Dispute {
address targetIpId;
address disputeInitiator;
address arbitrationPolicy;
bytes32 linkToDisputeEvidence;
bytes32 targetTag;
bytes32 currentTag;
uint256 parentDisputeId;
}

/// @notice Event emitted when a dispute tag whitelist status is updated
Expand Down Expand Up @@ -73,6 +75,18 @@ interface IDisputeModule {
/// @param data Custom data adjusted to each policy
event DisputeCancelled(uint256 disputeId, bytes data);

/// @notice Event emitted when a derivative is tagged on a parent infringement
/// @param parentIpId The parent ipId which infringed
/// @param derivativeIpId The derivative ipId which was tagged
/// @param parentDisputeId The parent dispute id in which infringement was found
/// @param tag The tag of the dispute applied to the derivative
event DerivativeTaggedOnParentInfringement(
address parentIpId,
address derivativeIpId,
uint256 parentDisputeId,
bytes32 tag
);

/// @notice Event emitted when a dispute is resolved
/// @param disputeId The dispute id
event DisputeResolved(uint256 disputeId);
Expand All @@ -94,6 +108,7 @@ interface IDisputeModule {
/// @return linkToDisputeEvidence The link of the dispute summary
/// @return targetTag The target tag of the dispute
/// @return currentTag The current tag of the dispute
/// @return parentDisputeId The parent dispute id
function disputes(
uint256 disputeId
)
Expand All @@ -105,7 +120,8 @@ interface IDisputeModule {
address arbitrationPolicy,
bytes32 linkToDisputeEvidence,
bytes32 targetTag,
bytes32 currentTag
bytes32 currentTag,
uint256 parentDisputeId
);

/// @notice Indicates if a dispute tag is whitelisted
Expand Down Expand Up @@ -161,7 +177,7 @@ interface IDisputeModule {
/// @param targetIpId The ipId that is the target of the dispute
/// @param linkToDisputeEvidence The link of the dispute evidence
/// @param targetTag The target tag of the dispute
/// @param data The data to initialize the policy
/// @param data The data to raise a dispute
/// @return disputeId The id of the newly raised dispute
function raiseDispute(
address targetIpId,
Expand All @@ -181,9 +197,20 @@ interface IDisputeModule {
/// @param data The data to cancel the dispute
function cancelDispute(uint256 disputeId, bytes calldata data) external;

/// @notice Tags a derivative if a parent has been tagged with an infringement tag
/// @param parentIpId The infringing parent ipId
/// @param derivativeIpId The derivative ipId
/// @param parentDisputeId The dispute id that tagged the parent ipId as infringing
function tagDerivativeIfParentInfringed(
address parentIpId,
address derivativeIpId,
uint256 parentDisputeId
) external;

/// @notice Resolves a dispute after it has been judged
/// @param disputeId The dispute id
function resolveDispute(uint256 disputeId) external;
/// @param disputeId The dispute
/// @param data The data to resolve the dispute
function resolveDispute(uint256 disputeId, bytes calldata data) external;

/// @notice Returns true if the ipId is tagged with any tag (meaning at least one dispute went through)
/// @param ipId The ipId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,13 @@ interface IArbitrationPolicy {
/// @param data The arbitrary data used to cancel the dispute
function onDisputeCancel(address caller, uint256 disputeId, bytes calldata data) external;

/// @notice Executes custom logic on resolving dispute
/// @dev Enforced to be only callable by the DisputeModule
/// @param caller Address of the caller
/// @param disputeId The dispute id
/// @param data The arbitrary data used to resolve the dispute
function onResolveDispute(address caller, uint256 disputeId, bytes calldata data) external;

/// @notice Allows governance address to withdraw
/// @dev Enforced to be only callable by the governance protocol admin.
function governanceWithdraw() external;
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/registries/ILicenseRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ interface ILicenseRegistry {
/// @return parentIpId The address of the parent IP.
function getParentIp(address childIpId, uint256 index) external view returns (address parentIpId);

/// @notice Checks if an IP is a parent IP.
/// @param parentIpId The address of the parent IP.
/// @param childIpId The address of the child IP.
/// @return Whether the IP is a parent IP.
function isParentIp(address parentIpId, address childIpId) external view returns (bool);

/// @notice Gets the count of parent IPs.
/// @param childIpId The address of the childIP.
/// @return The count o parent IPs.
Expand Down
10 changes: 5 additions & 5 deletions contracts/lib/ArrayUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ pragma solidity 0.8.23;
/// @notice Library for address array operations
library ArrayUtils {
/// @notice Finds the index of the first occurrence of the given element.
/// @param _array The input array to search
/// @param _element The value to find
/// @param array The input array to search
/// @param element The value to find
/// @return Returns (index and isIn) for the first occurrence starting from index 0
function indexOf(address[] memory _array, address _element) internal pure returns (uint32, bool) {
for (uint32 i = 0; i < _array.length; i++) {
if (_array[i] == _element) return (i, true);
function indexOf(address[] memory array, address element) internal pure returns (uint32, bool) {
for (uint32 i = 0; i < array.length; i++) {
if (array[i] == element) return (i, true);
}
return (0, false);
}
Expand Down
12 changes: 11 additions & 1 deletion contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -233,10 +233,19 @@ library Errors {
error DisputeModule__NotInDisputeState();
error DisputeModule__NotAbleToResolve();
error DisputeModule__NotRegisteredIpId();
error DisputeModule__ParentIpIdMismatch();
error DisputeModule__ParentNotTagged();
error DisputeModule__NotDerivative();
error DisputeModule__ParentDisputeNotResolved();
error DisputeModule__ZeroLicenseRegistry();
error DisputeModule__ZeroAssetRegistry();
error DisputeModule__ZeroController();
error DisputeModule__ZeroAccessManager();

error ArbitrationPolicySP__ZeroDisputeModule();
error ArbitrationPolicySP__ZeroPaymentToken();
error ArbitrationPolicySP__NotDisputeModule();
error ArbitrationPolicySP__ZeroAccessManager();

////////////////////////////////////////////////////////////////////////////
// Royalty Module //
Expand All @@ -254,6 +263,7 @@ library Errors {
error RoyaltyModule__NoParentsOnLinking();
error RoyaltyModule__ZeroDisputeModule();
error RoyaltyModule__IpIsTagged();
error RoyaltyModule__ZeroAccessManager();

error RoyaltyPolicyLAP__ZeroRoyaltyModule();
error RoyaltyPolicyLAP__ZeroLiquidSplitFactory();
Expand All @@ -270,8 +280,8 @@ library Errors {
error RoyaltyPolicyLAP__UnlinkableToParents();
error RoyaltyPolicyLAP__LastPositionNotAbleToMintLicense();
error RoyaltyPolicyLAP__ZeroIpRoyaltyVaultBeacon();
error RoyaltyPolicyLAP__ZeroAccessManager();

error IpRoyaltyVault__ZeroIpId();
error IpRoyaltyVault__ZeroRoyaltyPolicyLAP();
error IpRoyaltyVault__NotRoyaltyPolicyLAP();
error IpRoyaltyVault__SnapshotIntervalTooShort();
Expand Down
109 changes: 95 additions & 14 deletions contracts/modules/dispute/DisputeModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
// solhint-disable-next-line max-line-length
import { AccessManagedUpgradeable } from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";
import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol";

import { DISPUTE_MODULE_KEY } from "../../lib/modules/Module.sol";
import { BaseModule } from "../../modules/BaseModule.sol";
import { AccessControlled } from "../../access/AccessControlled.sol";
import { IIPAssetRegistry } from "../../interfaces/registries/IIPAssetRegistry.sol";
import { ILicenseRegistry } from "../../interfaces/registries/ILicenseRegistry.sol";
import { IDisputeModule } from "../../interfaces/modules/dispute/IDisputeModule.sol";
import { IArbitrationPolicy } from "../../interfaces/modules/dispute/policies/IArbitrationPolicy.sol";
import { Errors } from "../../lib/Errors.sol";
Expand All @@ -25,7 +27,8 @@ contract DisputeModule is
AccessManagedUpgradeable,
ReentrancyGuardUpgradeable,
AccessControlled,
UUPSUpgradeable
UUPSUpgradeable,
MulticallUpgradeable
{
using EnumerableSet for EnumerableSet.Bytes32Set;

Expand Down Expand Up @@ -64,21 +67,38 @@ contract DisputeModule is
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
IIPAssetRegistry public immutable IP_ASSET_REGISTRY;

/// @notice Protocol-wide license registry
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
ILicenseRegistry public immutable LICENSE_REGISTRY;

/// Constructor
/// @param controller The address of the access controller
/// @param assetRegistry The address of the asset registry
/// @param licenseRegistry The address of the license registry
/// @custom:oz-upgrades-unsafe-allow constructor
constructor(address controller, address assetRegistry) AccessControlled(controller, assetRegistry) {
constructor(
address controller,
address assetRegistry,
address licenseRegistry
) AccessControlled(controller, assetRegistry) {
if (licenseRegistry == address(0)) revert Errors.DisputeModule__ZeroLicenseRegistry();
if (assetRegistry == address(0)) revert Errors.DisputeModule__ZeroAssetRegistry();
if (controller == address(0)) revert Errors.DisputeModule__ZeroController();

IP_ASSET_REGISTRY = IIPAssetRegistry(assetRegistry);
LICENSE_REGISTRY = ILicenseRegistry(licenseRegistry);
_disableInitializers();
}

/// @notice initializer for this implementation contract
/// @notice Initializer for this implementation contract
/// @param accessManager The address of the protocol admin roles contract
function initialize(address accessManager) external initializer {
if (accessManager == address(0)) revert Errors.DisputeModule__ZeroAccessManager();

__AccessManaged_init(accessManager);
__ReentrancyGuard_init();
__UUPSUpgradeable_init();
__Multicall_init();
}

/// @notice Whitelists a dispute tag
Expand Down Expand Up @@ -178,7 +198,8 @@ contract DisputeModule is
arbitrationPolicy: arbitrationPolicy,
linkToDisputeEvidence: linkToDisputeEvidenceBytes,
targetTag: targetTag,
currentTag: IN_DISPUTE
currentTag: IN_DISPUTE,
parentDisputeId: 0
});

IArbitrationPolicy(arbitrationPolicy).onRaiseDispute(msg.sender, data);
Expand Down Expand Up @@ -239,19 +260,76 @@ contract DisputeModule is
emit DisputeCancelled(disputeId, data);
}

/// @notice Tags a derivative if a parent has been tagged with an infringement tag
/// @param parentIpId The infringing parent ipId
/// @param derivativeIpId The derivative ipId
/// @param parentDisputeId The dispute id that tagged the parent ipId as infringing
function tagDerivativeIfParentInfringed(
address parentIpId,
address derivativeIpId,
uint256 parentDisputeId
) external {
DisputeModuleStorage storage $ = _getDisputeModuleStorage();

Dispute memory parentDispute = $.disputes[parentDisputeId];
if (parentDispute.targetIpId != parentIpId) revert Errors.DisputeModule__ParentIpIdMismatch();

// a dispute current tag prior to being resolved can be in 3 states - IN_DISPUTE, 0, or a tag (ie. "PLAGIARISM)
// by restricting IN_DISPUTE and 0 - it is ensire the parent has been tagged before resolving dispute
if (parentDispute.currentTag == IN_DISPUTE || parentDispute.currentTag == bytes32(0))
revert Errors.DisputeModule__ParentNotTagged();

if (!LICENSE_REGISTRY.isParentIp(parentIpId, derivativeIpId)) revert Errors.DisputeModule__NotDerivative();

address arbitrationPolicy = $.arbitrationPolicies[derivativeIpId];
if (!$.isWhitelistedArbitrationPolicy[arbitrationPolicy]) arbitrationPolicy = $.baseArbitrationPolicy;

uint256 disputeId = ++$.disputeCounter;

$.disputes[disputeId] = Dispute({
targetIpId: derivativeIpId,
disputeInitiator: msg.sender,
arbitrationPolicy: arbitrationPolicy,
linkToDisputeEvidence: "",
targetTag: parentDispute.currentTag,
currentTag: parentDispute.currentTag,
parentDisputeId: parentDisputeId
});

$.successfulDisputesPerIp[derivativeIpId]++;

emit DerivativeTaggedOnParentInfringement(
parentIpId,
derivativeIpId,
parentDisputeId,
parentDispute.currentTag
);
}

/// @notice Resolves a dispute after it has been judged
/// @param disputeId The dispute id
function resolveDispute(uint256 disputeId) external {
/// @param data The data to resolve the dispute
function resolveDispute(uint256 disputeId, bytes calldata data) external {
DisputeModuleStorage storage $ = _getDisputeModuleStorage();
Dispute memory dispute = $.disputes[disputeId];

if (msg.sender != dispute.disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator();
// there are two types of disputes - those that are subject to judgment and those that are not
// the way to distinguish is by whether dispute.parentDisputeId is 0 or higher than 0
// for the former - only the dispute initiator can resolve
if (dispute.parentDisputeId == 0 && msg.sender != dispute.disputeInitiator)
revert Errors.DisputeModule__NotDisputeInitiator();
// for the latter - resolving is permissionless as long as the parent dispute has been resolved
if (dispute.parentDisputeId > 0 && $.disputes[dispute.parentDisputeId].currentTag != bytes32(0))
revert Errors.DisputeModule__ParentDisputeNotResolved();

if (dispute.currentTag == IN_DISPUTE || dispute.currentTag == bytes32(0))
revert Errors.DisputeModule__NotAbleToResolve();

$.successfulDisputesPerIp[dispute.targetIpId]--;
$.disputes[disputeId].currentTag = bytes32(0);

IArbitrationPolicy(dispute.arbitrationPolicy).onResolveDispute(msg.sender, disputeId, data);

emit DisputeResolved(disputeId);
}

Expand Down Expand Up @@ -283,6 +361,7 @@ contract DisputeModule is
/// @return linkToDisputeEvidence The link of the dispute summary
/// @return targetTag The target tag of the dispute
/// @return currentTag The current tag of the dispute
/// @return parentDisputeId The parent dispute id
function disputes(
uint256 disputeId
)
Expand All @@ -294,17 +373,19 @@ contract DisputeModule is
address arbitrationPolicy,
bytes32 linkToDisputeEvidence,
bytes32 targetTag,
bytes32 currentTag
bytes32 currentTag,
uint256 parentDisputeId
)
{
DisputeModuleStorage storage $ = _getDisputeModuleStorage();
Dispute memory dispute = _getDisputeModuleStorage().disputes[disputeId];
return (
$.disputes[disputeId].targetIpId,
$.disputes[disputeId].disputeInitiator,
$.disputes[disputeId].arbitrationPolicy,
$.disputes[disputeId].linkToDisputeEvidence,
$.disputes[disputeId].targetTag,
$.disputes[disputeId].currentTag
dispute.targetIpId,
dispute.disputeInitiator,
dispute.arbitrationPolicy,
dispute.linkToDisputeEvidence,
dispute.targetTag,
dispute.currentTag,
dispute.parentDisputeId
);
}

Expand Down
Loading

0 comments on commit e62c6db

Please sign in to comment.