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

Add licensing and royalty modules (initial thinking) #6

Merged
merged 13 commits into from
Jan 20, 2024
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ jobs:

- name: Run Forge tests
run: |
make test
forge test -vvv --fork-url https://eth.drpc.org --fork-block-number 18613489
id: forge-test

# - name: Gas Difference
Expand Down
40 changes: 34 additions & 6 deletions contracts/lib/Errors.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;
pragma solidity ^0.8.23;

/// @title Errors Library
/// @notice Library for all Story Protocol contract errors.
library Errors {

library Errors {
////////////////////////////////////////////////////////////////////////////
// LicenseRegistry //
////////////////////////////////////////////////////////////////////////////
Expand All @@ -21,6 +19,37 @@ library Errors {
error LicenseRegistry__ParentIdEqualThanChild();
error LicenseRegistry__LicensorDoesntHaveThisPolicy();

////////////////////////////////////////////////////////////////////////////
// Dispute Module //
////////////////////////////////////////////////////////////////////////////

error DisputeModule__ZeroArbitrationPolicy();
error DisputeModule__ZeroArbitrationRelayer();
error DisputeModule__ZeroDisputeTag();
error DisputeModule__ZeroHashToDisputeSummary();
error DisputeModule__NotWhitelistedArbitrationPolicy();
error DisputeModule__NotWhitelistedDisputeTag();
error DisputeModule__NotWhitelistedArbitrationRelayer();
error DisputeModule__NotDisputeInitiator();

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

////////////////////////////////////////////////////////////////////////////
// Royalty Module //
////////////////////////////////////////////////////////////////////////////

error RoyaltyModule__ZeroRoyaltyPolicy();
error RoyaltyModule__NotWhitelistedRoyaltyPolicy();
error RoyaltyModule__AlreadySetRoyaltyPolicy();

error RoyaltyPolicyLS__ZeroRoyaltyModule();
error RoyaltyPolicyLS__ZeroLiquidSplitFactory();
error RoyaltyPolicyLS__ZeroLiquidSplitMain();
error RoyaltyPolicyLS__NotRoyaltyModule();
error RoyaltyPolicyLS__TransferFailed();

////////////////////////////////////////////////////////////////////////////
// ModuleRegistry //
////////////////////////////////////////////////////////////////////////////
Expand All @@ -42,5 +71,4 @@ library Errors {
error AccessController__SignerIsZeroAddress();
error AccessController__CallerIsNotIPAccount();
error AccessController__PermissionIsNotValid();

}
}
171 changes: 171 additions & 0 deletions contracts/modules/dispute-module/DisputeModule.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

import {IArbitrationPolicy} from "../../../interfaces/modules/dispute-module/policies/IArbitrationPolicy.sol";
import {IDisputeModule} from "../../../interfaces/modules/dispute-module/IDisputeModule.sol";

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

import {Errors} from "../../lib/Errors.sol";

/// @title Story Protocol Dispute Module
/// @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, ReentrancyGuard {
/// @notice Dispute struct
struct Dispute {
address ipId; // The ipId
address disputeInitiator; // The address of the dispute initiator
address arbitrationPolicy; // The address of the arbitration policy
bytes32 hashToDisputeSummary; // The hash of the dispute summary
Ramarti marked this conversation as resolved.
Show resolved Hide resolved
bytes32 tag; // The target tag of the dispute // TODO: move to tagging module?
}

/// @notice Dispute id
uint256 public disputeId;

/// @notice Contains the dispute struct info for a given dispute id
mapping(uint256 disputeId => Dispute dispute) public disputes;

/// @notice Indicates if a dispute tag is whitelisted
mapping(bytes32 tag => bool allowed) public isWhitelistedDisputeTag;
Ramarti marked this conversation as resolved.
Show resolved Hide resolved

/// @notice Indicates if an arbitration policy is whitelisted
mapping(address arbitrationPolicy => bool allowed) public isWhitelistedArbitrationPolicy;

/// @notice Indicates if an arbitration relayer is whitelisted for a given arbitration policy
mapping(address arbitrationPolicy => mapping(address arbitrationRelayer => bool allowed)) public
Ramarti marked this conversation as resolved.
Show resolved Hide resolved
isWhitelistedArbitrationRelayer;

/// @notice Restricts the calls to the governance address
modifier onlyGovernance() {
LeoHChen marked this conversation as resolved.
Show resolved Hide resolved
// TODO: where is governance address defined?
_;
}

/// @notice Whitelists a dispute tag
/// @param _tag The dispute tag
/// @param _allowed Indicates if the dispute tag is whitelisted or not
function whitelistDisputeTags(bytes32 _tag, bool _allowed) external onlyGovernance {
if (_tag == bytes32(0)) revert Errors.DisputeModule__ZeroDisputeTag();

isWhitelistedDisputeTag[_tag] = _allowed;

// TODO: emit event
}

/// @notice Whitelists an arbitration policy
/// @param _arbitrationPolicy The address of the arbitration policy
/// @param _allowed Indicates if the arbitration policy is whitelisted or not
function whitelistArbitrationPolicy(address _arbitrationPolicy, bool _allowed) external onlyGovernance {
if (_arbitrationPolicy == address(0)) revert Errors.DisputeModule__ZeroArbitrationPolicy();

isWhitelistedArbitrationPolicy[_arbitrationPolicy] = _allowed;

// TODO: emit event
}

/// @notice Whitelists an arbitration relayer for a given arbitration policy
/// @param _arbitrationPolicy The address of the arbitration policy
/// @param _arbPolicyRelayer The address of the arbitration relayer
/// @param _allowed Indicates if the arbitration relayer is whitelisted or not
function whitelistArbitrationRelayer(address _arbitrationPolicy, address _arbPolicyRelayer, bool _allowed)
external
onlyGovernance
{
if (_arbitrationPolicy == address(0)) revert Errors.DisputeModule__ZeroArbitrationPolicy();
if (_arbPolicyRelayer == address(0)) revert Errors.DisputeModule__ZeroArbitrationRelayer();

isWhitelistedArbitrationRelayer[_arbitrationPolicy][_arbPolicyRelayer] = _allowed;

// TODO: emit event
}

/// @notice Raises a dispute
/// @param _ipId The ipId
/// @param _arbitrationPolicy The address of the arbitration policy
/// @param _hashToDisputeSummary The hash of the dispute summary
/// @param _targetTag The target tag of the dispute
/// @param _data The data to initialize the policy
/// @return disputeId The dispute id
function raiseDispute(
address _ipId,
address _arbitrationPolicy,
bytes32 _hashToDisputeSummary,
bytes32 _targetTag,
bytes calldata _data
) external nonReentrant returns (uint256) {
// TODO: make call to ensure ipId exists/has been registered
if (!isWhitelistedArbitrationPolicy[_arbitrationPolicy]) {
revert Errors.DisputeModule__NotWhitelistedArbitrationPolicy();
}
if (_hashToDisputeSummary == bytes32(0)) revert Errors.DisputeModule__ZeroHashToDisputeSummary();
if (!isWhitelistedDisputeTag[_targetTag]) revert Errors.DisputeModule__NotWhitelistedDisputeTag();

disputeId++;

disputes[disputeId] = Dispute({
ipId: _ipId,
disputeInitiator: msg.sender,
arbitrationPolicy: _arbitrationPolicy,
hashToDisputeSummary: _hashToDisputeSummary,
tag: _targetTag
});

// TODO: set tag to "in-dispute" state
LeoHChen marked this conversation as resolved.
Show resolved Hide resolved

IArbitrationPolicy(_arbitrationPolicy).onRaiseDispute(msg.sender, _data);

// TODO: emit event

return disputeId;
}

/// @notice Sets the dispute judgement
/// @param _disputeId The dispute id
/// @param _decision The decision of the dispute
/// @param _data The data to set the dispute judgement
function setDisputeJudgement(uint256 _disputeId, bool _decision, bytes calldata _data) external nonReentrant {
Ramarti marked this conversation as resolved.
Show resolved Hide resolved
address _arbitrationPolicy = disputes[_disputeId].arbitrationPolicy;

// TODO: if dispute tag is not in "in-dispute" state then the function should revert - the same disputeId cannot be set twice + cancelled cannot be set
if (!isWhitelistedArbitrationRelayer[_arbitrationPolicy][msg.sender]) {
revert Errors.DisputeModule__NotWhitelistedArbitrationRelayer();
}

if (_decision) {
// TODO: set tag to the target dispute tag state
} else {
// TODO: remove tag/set dispute tag to null state
}

IArbitrationPolicy(_arbitrationPolicy).onDisputeJudgement(_disputeId, _decision, _data);
Ramarti marked this conversation as resolved.
Show resolved Hide resolved

// TODO: emit event
}

/// @notice Cancels an ongoing dispute
/// @param _disputeId The dispute id
/// @param _data The data to cancel the dispute
function cancelDispute(uint256 _disputeId, bytes calldata _data) external nonReentrant {
if (msg.sender != disputes[_disputeId].disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator();
// TODO: if tag is not "in-dispute" then revert

IArbitrationPolicy(disputes[_disputeId].arbitrationPolicy).onDisputeCancel(msg.sender, _disputeId, _data);

// TODO: remove tag/set dispute tag to null state

// TODO: emit event
}

/// @notice Resolves a dispute after it has been judged
/// @param _disputeId The dispute id
function resolveDispute(uint256 _disputeId) external {
if (msg.sender != disputes[_disputeId].disputeInitiator) revert Errors.DisputeModule__NotDisputeInitiator();
// TODO: if tag is in "in-dispute" or already "null" then revert

// TODO: remove tag/set dispute tag to null state

// TODO: emit event
}
}
80 changes: 80 additions & 0 deletions contracts/modules/dispute-module/policies/ArbitrationPolicySP.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

import {IArbitrationPolicy} from "../../../../interfaces/modules/dispute-module/policies/IArbitrationPolicy.sol";
import {IDisputeModule} from "../../../../interfaces/modules/dispute-module/IDisputeModule.sol";

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

import {Errors} from "../../../lib/Errors.sol";

/// @title Story Protocol Arbitration Policy
/// @notice The Story Protocol arbitration policy is a simple policy that
/// requires the dispute initiator to pay a fixed amount of tokens
/// to raise a dispute and refunds that amount if the dispute initiator
/// wins the dispute.
contract ArbitrationPolicySP is IArbitrationPolicy {
using SafeERC20 for IERC20;

/// @notice Dispute module address
address public immutable DISPUTE_MODULE;

/// @notice Payment token address
address public immutable PAYMENT_TOKEN;

/// @notice Arbitration price
uint256 public immutable ARBITRATION_PRICE;

/// @notice Restricts the calls to the governance address
modifier onlyGovernance() {
// TODO: where is governance address defined?
_;
}

/// @notice Restricts the calls to the dispute module
modifier onlyDisputeModule() {
if (msg.sender != DISPUTE_MODULE) revert Errors.ArbitrationPolicySP__NotDisputeModule();
_;
}

/// @notice Constructor
/// @param _disputeModule Address of the dispute module contract
/// @param _paymentToken Address of the payment token
/// @param _arbitrationPrice Arbitration price
constructor(address _disputeModule, address _paymentToken, uint256 _arbitrationPrice) {
if (_disputeModule == address(0)) revert Errors.ArbitrationPolicySP__ZeroDisputeModule();
if (_paymentToken == address(0)) revert Errors.ArbitrationPolicySP__ZeroPaymentToken();

DISPUTE_MODULE = _disputeModule;
PAYMENT_TOKEN = _paymentToken;
ARBITRATION_PRICE = _arbitrationPrice;
}

/// @notice Executes custom logic on raise dispute
/// @param _caller Address of the caller
function onRaiseDispute(address _caller, bytes calldata) external onlyDisputeModule {
// TODO: we can add permit if the token supports it
IERC20(PAYMENT_TOKEN).safeTransferFrom(_caller, address(this), ARBITRATION_PRICE);
}

/// @notice Executes custom logic on dispute judgement
/// @param _disputeId The dispute id
/// @param _decision The decision of the dispute
function onDisputeJudgement(uint256 _disputeId, bool _decision, bytes calldata) external onlyDisputeModule {
if (_decision) {
(, address disputeInitiator,,,) = IDisputeModule(DISPUTE_MODULE).disputes(_disputeId);
IERC20(PAYMENT_TOKEN).safeTransfer(disputeInitiator, ARBITRATION_PRICE);
}
}

/// @notice Executes custom logic on dispute cancel
function onDisputeCancel(address, uint256, bytes calldata) external onlyDisputeModule {}

/// @notice Allows governance address to withdraw
/// @param _amount The amount to withdraw
function withdraw(uint256 _amount) external onlyGovernance {
// TODO: where is governance address defined?
/* IERC20(PAYMENT_TOKEN).safeTransfer(governance, _amount); */
}
}
Loading