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

Protocol Fee Table #964

Merged
merged 18 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions contracts/domain/BosonErrors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ interface BosonErrors {
error InvalidState();
// Two or more array parameters with different lengths
error ArrayLengthMismatch();
// Array elements that are not in ascending order (i.e arr[i-1] > arr[i])
error NonAscendingOrder();

// Reentrancy guard
// Reentrancy guard is active and second call to protocol is made
Expand Down Expand Up @@ -374,4 +376,8 @@ interface BosonErrors {
error FeeAmountTooHigh();
// Price does not cover the cancellation penalty
error PriceDoesNotCoverPenalty();

// Fee Table related
// Thrown if asset is not supported in feeTable feature.
error FeeTableAssetNotSupported();
}
6 changes: 6 additions & 0 deletions contracts/interfaces/events/IBosonConfigEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,10 @@ interface IBosonConfigEvents {
event MinDisputePeriodChanged(uint256 minDisputePeriod, address indexed executedBy);
event MaxPremintedVouchersChanged(uint256 maxPremintedVouchers, address indexed executedBy);
event AccessControllerAddressChanged(address indexed accessControllerAddress, address indexed executedBy);
event FeeTableUpdated(
address indexed token,
uint256[] priceRanges,
uint256[] feePercentages,
address indexed executedBy
);
}
70 changes: 67 additions & 3 deletions contracts/interfaces/handlers/IBosonConfigHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { IBosonConfigEvents } from "../events/IBosonConfigEvents.sol";
*
* @notice Handles management of configuration within the protocol.
*
* The ERC-165 identifier for this interface is: 0xe27f0773
* The ERC-165 identifier for this interface is: 0xe9aa49b6
*/
interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors {
/**
Expand Down Expand Up @@ -130,12 +130,76 @@ interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors {
function setProtocolFeePercentage(uint256 _protocolFeePercentage) external;

/**
* @notice Gets the protocol fee percentage.
* @notice Sets the feeTable for a specific token given price ranges and fee tiers for
* the corresponding price ranges.
*
* @return the protocol fee percentage
* Reverts if the number of fee percentages does not match the number of price ranges.
* Reverts if the price ranges are not in ascending order
* Reverts if any of the fee percentages value is above 100%
*
* @dev Caller must have ADMIN role.
*
* @param _tokenAddress - the address of the token
* @param _priceRanges - array of token price ranges
* @param _feePercentages - array of fee percentages corresponding to each price range
*/
function setProtocolFeeTable(
address _tokenAddress,
uint256[] calldata _priceRanges,
uint256[] calldata _feePercentages
) external;

/**
* @notice Gets the current fee table for a given token.
*
* @param _tokenAddress - the address of the token
* @return priceRanges - array of token price ranges
* @return feePercentages - array of fee percentages corresponding to each price range
*/
function getProtocolFeeTable(
address _tokenAddress
) external view returns (uint256[] memory priceRanges, uint256[] memory feePercentages);

/**
* @notice Gets the default protocol fee percentage.
*
* @return the default protocol fee percentage
*/
function getProtocolFeePercentage() external view returns (uint256);

/**
* @notice Gets the protocol fee percentage based on protocol fee table
*
* @dev This function calculates the protocol fee percentage for specific token and price.
* If the token has a custom fee table configured, it returns the corresponding fee percentage
* for the price range. If the token does not have a custom fee table, it falls back
* to the default protocol fee percentage.
*
* Reverts if the exchange token is BOSON.
*
* @param _exchangeToken - The address of the token being used for the exchange.
* @param _price - The price of the item or service in the exchange.
*
* @return the protocol fee percentage for given price and exchange token
*/
function getProtocolFeePercentage(address _exchangeToken, uint256 _price) external view returns (uint256);

/**
* @notice Retrieves the protocol fee percentage for a given exchange token and price.
*
* @dev This function calculates the protocol fee based on the token and price.
* If the token has a custom fee table, it applies the corresponding fee percentage
* for the price range. If the token does not have a custom fee table, it falls back
* to the default protocol fee percentage. If the exchange token is $BOSON,
* this function returns the flatBoson fee
*
* @param _exchangeToken - The address of the token being used for the exchange.
* @param _price - The price of the item or service in the exchange.
*
* @return The protocol fee amount based on the token and the price.
*/
function getProtocolFee(address _exchangeToken, uint256 _price) external view returns (uint256);

/**
* @notice Sets the flat protocol fee for exchanges in $BOSON.
*
Expand Down
2 changes: 1 addition & 1 deletion contracts/protocol/bases/OfferBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ contract OfferBase is ProtocolBase, IBosonOfferEvents {
if (_offer.buyerCancelPenalty > offerPrice) revert InvalidOfferPenalty();
if (_offer.priceType == PriceType.Static) {
// Calculate and set the protocol fee
uint256 protocolFee = getProtocolFee(_offer.exchangeToken, offerPrice);
uint256 protocolFee = _getProtocolFee(_offer.exchangeToken, offerPrice);

// Calculate the agent fee amount
uint256 agentFeeAmount = (agent.feePercentage * offerPrice) / HUNDRED_PERCENT;
Expand Down
48 changes: 41 additions & 7 deletions contracts/protocol/bases/ProtocolBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { EIP712Lib } from "../libs/EIP712Lib.sol";
import { BosonTypes } from "../../domain/BosonTypes.sol";
import { PausableBase } from "./PausableBase.sol";
import { ReentrancyGuardBase } from "./ReentrancyGuardBase.sol";
import { FundsLib } from "../libs/FundsLib.sol";

/**
* @title ProtocolBase
Expand Down Expand Up @@ -687,18 +688,51 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase, BosonErrors
}

/**
* @notice calculate the protocol fee for a given exchange
* @notice calculate the protocol fee amount for a given exchange
*
* @param _exchangeToken - the token used for the exchange
* @param _price - the price of the exchange
* @return protocolFee - the protocol fee
*/
function getProtocolFee(address _exchangeToken, uint256 _price) internal view returns (uint256 protocolFee) {
// Calculate and set the protocol fee
return
_exchangeToken == protocolAddresses().token
? protocolFees().flatBoson
: (protocolFees().percentage * _price) / HUNDRED_PERCENT;
function _getProtocolFee(address _exchangeToken, uint256 _price) internal view returns (uint256 protocolFee) {
// Check if the exchange token is the Boson token
if (_exchangeToken == protocolAddresses().token) {
// Return the flatBoson fee percentage if the exchange token is the Boson token
return protocolFees().flatBoson;
}
uint256 feePercentage = _getFeePercentage(_exchangeToken, _price);
return FundsLib.applyPercent(_price, feePercentage);
}

/**
* @notice calculate the protocol fee percentage for a given exchange
*
* @param _exchangeToken - the token used for the exchange
* @param _price - the price of the exchange
* @return feePercentage - the protocol fee percentage based on token price (using protocol fee table)
*/
function _getFeePercentage(address _exchangeToken, uint256 _price) internal view returns (uint256 feePercentage) {
if (_exchangeToken == protocolAddresses().token) revert FeeTableAssetNotSupported();

ProtocolLib.ProtocolFees storage fees = protocolFees();
uint256[] storage priceRanges = fees.tokenPriceRanges[_exchangeToken];
uint256[] storage feePercentages = fees.tokenFeePercentages[_exchangeToken];

// If the token has a custom fee table, find the appropriate percentage
uint256 priceRangesLength = priceRanges.length;
if (priceRangesLength > 0) {
for (uint256 i; i < priceRangesLength - 1; ++i) {
if (_price <= priceRanges[i]) {
// Return the fee percentage for the matching price range
return feePercentages[i];
}
}
// If price exceeds all ranges, use the highest fee percentage
return feePercentages[priceRangesLength - 1];
}

// If no custom fee table exists, fallback to using the default protocol percentage
return fees.percentage;
}

/**
Expand Down
133 changes: 126 additions & 7 deletions contracts/protocol/facets/ConfigHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase {
*
* @param _addresses - struct of Boson Protocol addresses (Boson Token (ERC-20) contract, treasury, and Voucher contract)
* @param _limits - struct with Boson Protocol limits
* @param _fees - struct of Boson Protocol fees
* @param defaultFeePercentage - efault percentage that will be taken as a fee from the net of a Boson Protocol exchange.
* @param flatBosonFee - flat fee taken for exchanges in $BOSON
* @param buyerEscalationDepositPercentage - buyer escalation deposit percentage
*/
function initialize(
ProtocolLib.ProtocolAddresses calldata _addresses,
ProtocolLib.ProtocolLimits calldata _limits,
ProtocolLib.ProtocolFees calldata _fees
uint256 defaultFeePercentage,
uint256 flatBosonFee,
uint256 buyerEscalationDepositPercentage
) public onlyUninitialized(type(IBosonConfigHandler).interfaceId) {
// Register supported interfaces
DiamondLib.addSupportedInterface(type(IBosonConfigHandler).interfaceId);
Expand All @@ -38,10 +42,10 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase {
setTreasuryAddress(_addresses.treasury);
setVoucherBeaconAddress(_addresses.voucherBeacon);
setPriceDiscoveryAddress(_addresses.priceDiscovery);
setProtocolFeePercentage(_fees.percentage);
setProtocolFeeFlatBoson(_fees.flatBoson);
setProtocolFeePercentage(defaultFeePercentage); // this sets the default fee percentage if fee table is not configured for the exchange token
setProtocolFeeFlatBoson(flatBosonFee);
setMaxEscalationResponsePeriod(_limits.maxEscalationResponsePeriod);
setBuyerEscalationDepositPercentage(_fees.buyerEscalationDepositPercentage);
setBuyerEscalationDepositPercentage(buyerEscalationDepositPercentage);
setMaxTotalOfferFeePercentage(_limits.maxTotalOfferFeePercentage);
setMaxRoyaltyPercentage(_limits.maxRoyaltyPercentage);
setMaxResolutionPeriod(_limits.maxResolutionPeriod);
Expand Down Expand Up @@ -226,14 +230,102 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase {
}

/**
* @notice Gets the protocol fee percentage.
* @notice Sets the feeTable for a specific token given price ranges and fee tiers for
* the corresponding price ranges.
*
* @return the protocol fee percentage
* Reverts if token is $BOSON.
* Reverts if the number of fee percentages does not match the number of price ranges.
* Reverts if the price ranges are not in ascending order.
* Reverts if any of the fee percentages value is above 100%.
*
* @dev Caller must have ADMIN role.
*
* @param _tokenAddress - the address of the token
* @param _priceRanges - array of token price ranges
* @param _feePercentages - array of fee percentages corresponding to each price range
*/
function setProtocolFeeTable(
levalleux-ludo marked this conversation as resolved.
Show resolved Hide resolved
address _tokenAddress,
uint256[] calldata _priceRanges,
uint256[] calldata _feePercentages
) external override onlyRole(ADMIN) nonReentrant {
levalleux-ludo marked this conversation as resolved.
Show resolved Hide resolved
if (_tokenAddress == protocolAddresses().token) revert FeeTableAssetNotSupported();
if (_priceRanges.length != _feePercentages.length) revert ArrayLengthMismatch();
// Clear existing price ranges and percentage tiers
delete protocolFees().tokenPriceRanges[_tokenAddress];
delete protocolFees().tokenFeePercentages[_tokenAddress];

if (_priceRanges.length != 0) {
setTokenPriceRanges(_tokenAddress, _priceRanges);
setTokenFeePercentages(_tokenAddress, _feePercentages);
}
emit FeeTableUpdated(_tokenAddress, _priceRanges, _feePercentages, msgSender());
}

/**
* @notice Gets the current fee table for a given token.
*
* @dev This funciton is used to check price ranges config. If you need to apply percentage based on
* _exchangeToken and offerPrice, use getProtocolFeePercentage(address,uint256)
*
* @param _tokenAddress - the address of the token
* @return priceRanges - array of token price ranges
* @return feePercentages - array of fee percentages corresponding to each price range
*/
function getProtocolFeeTable(
address _tokenAddress
) external view returns (uint256[] memory priceRanges, uint256[] memory feePercentages) {
ProtocolLib.ProtocolFees storage fees = protocolFees();
priceRanges = fees.tokenPriceRanges[_tokenAddress];
feePercentages = fees.tokenFeePercentages[_tokenAddress];
}

/**
* @notice Gets the default protocol fee percentage.
*
* @return the default protocol fee percentage
*/
function getProtocolFeePercentage() external view override returns (uint256) {
return protocolFees().percentage;
}

/**
* @notice Gets the protocol fee percentage based on protocol fee table
*
* @dev This function calculates the protocol fee percentage for specific token and price.
* If the token has a custom fee table configured, it returns the corresponding fee percentage
* for the price range. If the token does not have a custom fee table, it falls back
* to the default protocol fee percentage.
*
* Reverts if the exchange token is BOSON.
*
* @param _exchangeToken - The address of the token being used for the exchange.
* @param _price - The price of the item or service in the exchange.
*
* @return the protocol fee percentage for given price and exchange token
*/
function getProtocolFeePercentage(address _exchangeToken, uint256 _price) external view override returns (uint256) {
return _getFeePercentage(_exchangeToken, _price);
}

/**
* @notice Retrieves the protocol fee percentage for a given token and price.
*
* @dev This function calculates the protocol fee based on the token and price.
* If the token has a custom fee table, it applies the corresponding fee percentage
* for the price range. If the token does not have a custom fee table, it falls back
* to the default protocol fee percentage. If the exchange token is BOSON,
* this function returns the flatBoson fee
*
* @param _exchangeToken - The address of the token being used for the exchange.
* @param _price - The price of the item or service in the exchange.
*
* @return The protocol fee amount based on the token and the price.
*/
function getProtocolFee(address _exchangeToken, uint256 _price) external view override returns (uint256) {
return _getProtocolFee(_exchangeToken, _price);
}

/**
* @notice Sets the flat protocol fee for exchanges in $BOSON.
*
Expand Down Expand Up @@ -558,6 +650,33 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase {
return address(DiamondLib.diamondStorage().accessController);
}

/**
* @notice Sets the price ranges for a specific token.
*
* @param _tokenAddress - the address of the token
* @param _priceRanges - array of price ranges for the token
*/
function setTokenPriceRanges(address _tokenAddress, uint256[] calldata _priceRanges) internal {
for (uint256 i = 1; i < _priceRanges.length; ++i) {
if (_priceRanges[i] <= _priceRanges[i - 1]) revert NonAscendingOrder();
}
protocolFees().tokenPriceRanges[_tokenAddress] = _priceRanges;
}

/**
* @notice Sets the fee percentages for a specific token and price ranges.
*
* @param _tokenAddress - the address of the token
* @param _feePercentages - array of fee percentages corresponding to each price range
*/
function setTokenFeePercentages(address _tokenAddress, uint256[] calldata _feePercentages) internal {
// Set the fee percentages for the token
for (uint256 i; i < _feePercentages.length; ++i) {
checkMaxPercententage(_feePercentages[i]);
}
protocolFees().tokenFeePercentages[_tokenAddress] = _feePercentages;
}

/**
* @notice Checks that supplied value is not 0.
*
Expand Down
2 changes: 1 addition & 1 deletion contracts/protocol/facets/PriceDiscoveryHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ contract PriceDiscoveryHandlerFacet is IBosonPriceDiscoveryHandler, PriceDiscove

// Calculate fees
address exchangeToken = offer.exchangeToken;
uint256 protocolFeeAmount = getProtocolFee(exchangeToken, actualPrice);
uint256 protocolFeeAmount = _getProtocolFee(exchangeToken, actualPrice);

{
// Calculate royalties
Expand Down
2 changes: 1 addition & 1 deletion contracts/protocol/facets/SequentialCommitHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ contract SequentialCommitHandlerFacet is IBosonSequentialCommitHandler, PriceDis

{
// Calculate fees
thisExchangeCost.protocolFeeAmount = getProtocolFee(exchangeToken, thisExchangeCost.price);
thisExchangeCost.protocolFeeAmount = _getProtocolFee(exchangeToken, thisExchangeCost.price);

// Calculate royalties
{
Expand Down
Loading
Loading