Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Full dispute resolution #622

Draft
wants to merge 35 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
a9b06fa
remove zero DR fee restriction
zajck Apr 17, 2023
70c5a04
add feeMutualizer to Offer struct
zajck Apr 17, 2023
a32bca3
encumber DR fee
zajck Apr 18, 2023
daa7f61
Release DR fee
zajck Apr 18, 2023
17d4329
add events
zajck Apr 20, 2023
7590fe7
MVP mutualizer
zajck Apr 21, 2023
99d0a29
Merge branch 'main' into full-dispute-resolution
zajck Apr 27, 2023
1f5ab34
Disputeresolver handler tests
zajck Apr 27, 2023
743f9c8
Encumber funds tests
zajck Apr 27, 2023
98ace08
tests with mock mutualizer
zajck Apr 28, 2023
66f2af0
release funds [no dispute]
zajck May 9, 2023
94a6616
release funds all tests
zajck May 9, 2023
2747781
refactor-1
zajck May 10, 2023
8143986
refactor-3
zajck May 11, 2023
3511687
refactor-4
zajck May 11, 2023
373d7a1
Withdraw funds - support DR withdrawals
zajck May 17, 2023
f8f3698
changeMutualizer tests
zajck May 18, 2023
bf22979
refactor - 5
zajck May 18, 2023
3b830a6
Release funds to correct mutualizer
zajck May 18, 2023
3974ea5
withdraw DR funds
zajck May 18, 2023
8e7bb71
Fix missing tests
zajck May 18, 2023
a98c0cc
DRfee mutualizer tests
zajck May 30, 2023
95e4a0a
DRfee mutualizer tests - payPremium
zajck May 30, 2023
21a3327
DRfee mutualizer tests - voidAgreement
zajck May 30, 2023
b102243
deposit + withdraw tests
zajck May 30, 2023
4770e3b
DR client getters + voided moved to status
zajck May 31, 2023
2423816
requestDRFee tests
zajck May 31, 2023
91f2773
returnDRFee tests
zajck May 31, 2023
f6c3618
isSellerCovered tests
zajck May 31, 2023
51d2d69
Merge branch 'main' into full-dispute-resolution
zajck May 31, 2023
ef21158
fix failing tests
zajck May 31, 2023
9a0feab
bump coverage
zajck May 31, 2023
292126f
Fix interface id calculation when contracts inherit others
zajck Jun 2, 2023
82ea1a0
Unskip skipped tests
zajck Jun 7, 2023
185d09a
use anyValue predicate
zajck Jun 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ string constant NO_SUCH_DISPUTE_RESOLVER = "No such dispute resolver";
string constant INVALID_ESCALATION_PERIOD = "Invalid escalation period";
string constant INVALID_AMOUNT_DISPUTE_RESOLVER_FEES = "Dispute resolver fees are not present or exceed maximum dispute resolver fees in a single transaction";
string constant DUPLICATE_DISPUTE_RESOLVER_FEES = "Duplicate dispute resolver fee";
string constant FEE_AMOUNT_NOT_YET_SUPPORTED = "Non-zero dispute resolver fees not yet supported";
string constant DISPUTE_RESOLVER_FEE_NOT_FOUND = "Dispute resolver fee not found";
string constant SELLER_ALREADY_APPROVED = "Seller id is approved already";
string constant SELLER_NOT_APPROVED = "Seller id is not approved";
Expand Down Expand Up @@ -149,6 +148,9 @@ string constant TOKEN_TRANSFER_FAILED = "Token transfer failed";
string constant INSUFFICIENT_VALUE_RECEIVED = "Insufficient value received";
string constant INSUFFICIENT_AVAILABLE_FUNDS = "Insufficient available funds";
string constant NATIVE_NOT_ALLOWED = "Transfer of native currency not allowed";
string constant DR_FEE_NOT_RECEIVED = "DR fee not received";
string constant SELLER_NOT_COVERED = "Seller not covered";
string constant INVALID_ENTITY_ID = "Invalid entity id";

// Revert Reasons: Meta-Transactions related
string constant NONCE_USED_ALREADY = "Nonce used already";
Expand Down Expand Up @@ -197,6 +199,19 @@ string constant NOT_COMMITTABLE = "Token not committable";
string constant INVALID_TO_ADDRESS = "Tokens can only be pre-mined to the contract or contract owner address";
string constant EXTERNAL_CALL_FAILED = "External call failed";

// DRFeeMutualizer
string constant ONLY_PROTOCOL = "Only protocol can call this function";
string constant AGREEMENT_NOT_STARTED = "Agreement not started yet";
string constant AGREEMENT_EXPIRED = "Agreement expired";
string constant AGREEMENT_VOIDED = "Agreement voided";
string constant EXCEEDED_SINGLE_FEE = "Fee amount exceeds max mutualized amount per transaction";
string constant EXCEEDED_TOTAL_FEE = "Fee amount exceeds max total mutualized amount";
string constant INVALID_UUID = "Invalid UUID";
string constant INVALID_SELLER_ADDRESS = "Invalid seller address";
string constant INVALID_AGREEMENT = "Invalid agreement";
string constant AGREEMENT_ALREADY_CONFIRMED = "Agreement already confirmed";
string constant NOT_OWNER_OR_SELLER = "Not owner or seller";

// Meta Transactions - Structs
bytes32 constant META_TRANSACTION_TYPEHASH = keccak256(
bytes(
Expand Down
2 changes: 2 additions & 0 deletions contracts/domain/BosonTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ contract BosonTypes {
uint256 escalationResponsePeriod;
uint256 feeAmount;
uint256 buyerEscalationDeposit;
address feeMutualizer;
}

struct Offer {
Expand All @@ -146,6 +147,7 @@ contract BosonTypes {
string metadataUri;
string metadataHash;
bool voided;
address feeMutualizer;
}

struct OfferDates {
Expand Down
240 changes: 240 additions & 0 deletions contracts/interfaces/clients/IDRFeeMutualizer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;

/**
* @title IDRFeeMutualizer
*
* @notice This is the interface for the Dispute Resolver fee mutualizers.
*
* The ERC-165 identifier for this interface is: 0x41283543
*/
interface IDRFeeMutualizer {
event DRFeeRequsted(
address indexed sellerAddress,
address _token,
uint256 feeAmount,
address feeRequester,
bytes context
);

event DRFeeSent(address indexed feeRequester, address token, uint256 feeAmount, uint256 indexed uuid);
event DRFeeReturned(uint256 indexed uuid, address indexed token, uint256 feeAmount, bytes context);

/**
* @notice Tells if mutualizer will cover the fee amount for a given seller and requested by a given address.
*
* It checks if agreement is valid, but not if the mutualizer has enough funds to cover the fee.
*
* @param _sellerAddress - the seller address
* @param _token - the token address (use 0x0 for ETH)
* @param _feeAmount - amount to cover
* @param _feeRequester - address of the requester
* @param _context - additional data, describing the context
*/
function isSellerCovered(
address _sellerAddress,
address _token,
uint256 _feeAmount,
address _feeRequester,
bytes calldata _context
) external view returns (bool);

/**
* @notice Request the mutualizer to cover the fee amount.
*
* @dev Verify that seller is covered and send the fee amount to the msg.sender.
* Returned uuid can be used to track the status of the request.
*
* Reverts if:
* - caller is not the protocol
* - agreement does not exist
* - agreement is not confirmed yet
* - agreement is voided
* - agreement has not started yet
* - agreement expired
* - fee amount exceeds max mutualized amount per transaction
* - fee amount exceeds max total mutualized amount
* - amount exceeds available balance
* - token is native and transfer fails
* - token is ERC20 and transferFrom fails
*
* @param _sellerAddress - the seller address
* @param _token - the token address (use 0x0 for ETH)
* @param _feeAmount - amount to cover
* @param _context - additional data, describing the context
* @return isCovered - true if the seller is covered
* @return uuid - unique identifier of the request
*/
function requestDRFee(
address _sellerAddress,
address _token,
uint256 _feeAmount,
bytes calldata _context
) external returns (bool isCovered, uint256 uuid);

/**
* @notice Return fee to the mutualizer.
*
* @dev Returned amount can be between 0 and _feeAmount that was requested for the given uuid.
*
* - caller is not the protocol
* - uuid does not exist
* - same uuid is used twice
* - token is native and sent value is not equal to _feeAmount
* - token is ERC20, but some native value is sent
* - token is ERC20 and sent value is not equal to _feeAmount
* - token is ERC20 and transferFrom fails
*
* @param _uuid - unique identifier of the request
* @param _feeAmount - returned amount
* @param _context - additional data, describing the context
*/
function returnDRFee(uint256 _uuid, uint256 _feeAmount, bytes calldata _context) external payable;
}

/**
* @title IDRFeeMutualizerClient
*
* @notice This is the interface for the Dispute Resolver fee mutualizers.
*
* The ERC-165 identifier for this interface is: 0x391b17cd
*/
interface IDRFeeMutualizerClient is IDRFeeMutualizer {
struct Agreement {
address sellerAddress;
address token;
uint256 maxMutualizedAmountPerTransaction;
uint256 maxTotalMutualizedAmount;
uint256 premium;
uint128 startTimestamp;
uint128 endTimestamp;
bool refundOnCancel;
}

struct AgreementStatus {
bool confirmed;
bool voided;
uint256 outstandingExchanges;
uint256 totalMutualizedAmount;
}

event AgreementCreated(address indexed sellerAddress, uint256 indexed agreementId, Agreement agreement);
event AgreementConfirmed(address indexed sellerAddress, uint256 indexed agreementId);
event AgreementVoided(address indexed sellerAddress, uint256 indexed agreementId);
event FundsDeposited(address indexed tokenAddress, uint256 amount, address indexed depositor);
event FundsWithdrawn(address indexed tokenAddress, uint256 amount);

/**
* @notice Stores a new agreement between mutualizer and seller. Only contract owner can submit an agreement,
* however it becomes valid only after seller confirms it by calling payPremium.
*
* Emits AgreementCreated event if successful.
*
* Reverts if:
* - caller is not the contract owner
* - max mutualized amount per transaction is greater than max total mutualized amount
* - max mutualized amount per transaction is 0
* - end timestamp is not greater than start timestamp
* - end timestamp is not greater than current block timestamp
*
* @param _agreement - a fully populated agreement object
*/
function newAgreement(Agreement calldata _agreement) external;

/**
* @notice Pay the premium for the agreement and confirm it.
*
* Emits AgreementConfirmed event if successful.
*
* Reverts if:
* - agreement does not exist
* - agreement is already confirmed
* - agreement is voided
* - agreement expired
* - token is native and sent value is not equal to the agreement premium
* - token is ERC20, but some native value is sent
* - token is ERC20 and sent value is not equal to the agreement premium
* - token is ERC20 and transferFrom fails
*
* @param _agreementId - a unique identifier of the agreement
*/
function payPremium(uint256 _agreementId) external payable;

/**
* @notice Void the agreement.
*
* Emits AgreementVoided event if successful.
*
* Reverts if:
* - agreement does not exist
* - caller is not the contract owner or the seller
* - agreement is voided already
* - agreement expired
*
* @param _agreementId - a unique identifier of the agreement
*/
function voidAgreement(uint256 _agreementId) external;

/**
* @notice Deposit funds to the mutualizer. Funds are used to cover the DR fees.
*
* Emits FundsDeposited event if successful.
*
* Reverts if:
* - token is native and sent value is not equal to _amount
* - token is ERC20, but some native value is sent
* - token is ERC20 and sent value is not equal to _amount
* - token is ERC20 and transferFrom fails
*
* @param _tokenAddress - the token address (use 0x0 for native token)
* @param _amount - amount to transfer
*/
function deposit(address _tokenAddress, uint256 _amount) external payable;

/**
* @notice Withdraw funds from the mutualizer.
*
* Emits FundsWithdrawn event if successful.
*
* Reverts if:
* - caller is not the mutualizer owner
* - amount exceeds available balance
* - token is ERC20 and transferFrom fails
*
* @param _tokenAddress - the token address (use 0x0 for native token)
* @param _amount - amount to transfer
*/
function withdraw(address _tokenAddress, uint256 _amount) external;

/**
* @notice Returns agreement details and status for a given agreement id.
*
* Reverts if:
* - agreement does not exist
*
* @param _agreementId - a unique identifier of the agreement
* @return agreement - agreement details
* @return status - agreement status
*/
function getAgreement(
uint256 _agreementId
) external view returns (Agreement memory agreement, AgreementStatus memory status);

/**
* @notice Returns agreement id, agreement details and status for given seller and token.
*
* Reverts if:
* - agreement does not exist
* - agreement is not confirmed yet
*
* @param _seller - the seller address
* @param _token - the token address (use 0x0 for native token)
* @return agreementId - a unique identifier of the agreement
* @return agreement - agreement details
* @return status - agreement status
*/
function getConfirmedAgreementBySellerAndToken(
address _seller,
address _token
) external view returns (uint256 agreementId, Agreement memory agreement, AgreementStatus memory status);
}
16 changes: 16 additions & 0 deletions contracts/interfaces/events/IBosonFundsEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,20 @@ interface IBosonFundsLibEvents {
uint256 amount,
address executedBy
);
event DRFeeEncumbered(
address indexed feeMutualizer,
uint256 indexed uuid,
uint256 indexed exchangeId,
address tokenAddress,
uint256 feeAmount,
address executedBy
);
event DRFeeReturned(
address indexed feeMutualizer,
uint256 indexed uuid,
uint256 indexed exchangeId,
address tokenAddress,
uint256 feeAmount,
address executedBy
);
}
6 changes: 6 additions & 0 deletions contracts/interfaces/events/IBosonOfferEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,10 @@ interface IBosonOfferEvents {
address owner,
address indexed executedBy
);
event OfferMutualizerChanged(
uint256 indexed offerId,
uint256 indexed sellerId,
address indexed feeMutualizer,
address executedBy
);
}
4 changes: 0 additions & 4 deletions contracts/interfaces/handlers/IBosonAccountHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,9 @@ interface IBosonAccountHandler is IBosonAccountEvents {
* - Some seller does not exist
* - Some seller id is duplicated
* - DisputeResolver is not active (if active == false)
* - Fee amount is a non-zero value. Protocol doesn't yet support fees for dispute resolvers
*
* @param _disputeResolver - the fully populated struct with dispute resolver id set to 0x0
* @param _disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee}
* feeAmount will be ignored because protocol doesn't yet support fees yet but DR still needs to provide array of fees to choose supported tokens
* @param _sellerAllowList - list of ids of sellers that can choose this dispute resolver. If empty, there are no restrictions on which seller can chose it.
*/
function createDisputeResolver(
Expand Down Expand Up @@ -237,11 +235,9 @@ interface IBosonAccountHandler is IBosonAccountEvents {
* - Number of DisputeResolverFee structs in array exceeds max
* - Number of DisputeResolverFee structs in array is zero
* - DisputeResolverFee array contains duplicates
* - Fee amount is a non-zero value. Protocol doesn't yet support fees for dispute resolvers
*
* @param _disputeResolverId - id of the dispute resolver
* @param _disputeResolverFees - list of fees dispute resolver charges per token type. Zero address is native currency. See {BosonTypes.DisputeResolverFee}
* feeAmount will be ignored because protocol doesn't yet support fees yet but DR still needs to provide array of fees to choose supported tokens
*/
function addFeesToDisputeResolver(
uint256 _disputeResolverId,
Expand Down
35 changes: 34 additions & 1 deletion contracts/interfaces/handlers/IBosonOfferHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IBosonOfferEvents } from "../events/IBosonOfferEvents.sol";
*
* @notice Handles creation, voiding, and querying of offers within the protocol.
*
* The ERC-165 identifier for this interface is: 0xa1598d02
* The ERC-165 identifier for this interface is: 0xebc1b5a3
*/
interface IBosonOfferHandler is IBosonOfferEvents {
/**
Expand Down Expand Up @@ -190,6 +190,39 @@ interface IBosonOfferHandler is IBosonOfferEvents {
*/
function extendOfferBatch(uint256[] calldata _offerIds, uint256 _validUntilDate) external;

/**
* @notice Changes the mutualizer for a given offer.
* Existing exchanges are not affected.
*
* Emits an OfferMutualizerChanged event if successful.
*
* Reverts if:
* - The offers region of protocol is paused
* - Offer id is invalid
* - Caller is not the assistant of the offer
*
* @param _offerId - the id of the offer to void
* @param _feeMutualizer - the new mutualizer address
*/
function changeOfferMutualizer(uint256 _offerId, address _feeMutualizer) external;

/**
* @notice Changes the mutualizers for a batch of offers.
* Existing exchanges are not affected.
*
* Emits an OfferMutualizerChanged event for every offer if successful.
*
* Reverts if, for any offer:
* - The offers region of protocol is paused
* - Number of offers exceeds maximum allowed number per batch
* - Offer id is invalid
* - Caller is not the assistant of the offer
*
* @param _offerIds - list of ids of offers to change the mutualizer for
* @param _feeMutualizer - the new mutualizers address
*/
function changeOfferMutualizerBatch(uint256[] calldata _offerIds, address _feeMutualizer) external;

/**
* @notice Gets the details about a given offer.
*
Expand Down
Loading