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 11 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
1 change: 1 addition & 0 deletions contracts/interfaces/events/IBosonConfigEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ 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);
zajck marked this conversation as resolved.
Show resolved Hide resolved
}
41 changes: 38 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: 0x32872426
*/
interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors {
/**
Expand Down Expand Up @@ -130,12 +130,47 @@ 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 token is Zero address.
*
* @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 default protocol fee percentage.
*
* @return the default protocol fee percentage
*/
function getProtocolFeePercentage() 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 getProtocolFeePercentage(address _exchangeToken, uint256 _price) external view returns (uint256);

/**
* @notice Sets the flat protocol fee for exchanges in $BOSON.
*
Expand Down
32 changes: 26 additions & 6 deletions contracts/protocol/bases/ProtocolBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -687,18 +687,38 @@ 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;
// Check if the exchange token is the Boson token
if (_exchangeToken == protocolAddresses().token) {
// Apply the flatBoson fee if the exchange token is the Boson token
return protocolFees().flatBoson;
}

uint256[] storage priceRanges = protocolFees().tokenPriceRanges[_exchangeToken];
zajck marked this conversation as resolved.
Show resolved Hide resolved
uint256[] storage feePercentages = protocolFees().tokenFeePercentages[_exchangeToken];

// If the token has a custom fee table, calculate based on the price ranges
if (priceRanges.length > 0 && feePercentages.length > 0) {
zajck marked this conversation as resolved.
Show resolved Hide resolved
for (uint256 i = 0; i < priceRanges.length; i++) {
if (_price <= priceRanges[i]) {
// Apply the fee percentage for the matching price range
uint256 feePercentage = feePercentages[i];
zajck marked this conversation as resolved.
Show resolved Hide resolved
return (feePercentage * _price) / HUNDRED_PERCENT;
zajck marked this conversation as resolved.
Show resolved Hide resolved
}
}
// If price exceeds all ranges, use the highest fee percentage
uint256 highestFeePercentage = feePercentages[priceRanges.length - 1];
return (highestFeePercentage * _price) / HUNDRED_PERCENT;
}

// If no custom fee table exists, fallback to using the default protocol percentage
return (protocolFees().percentage * _price) / HUNDRED_PERCENT;
}

/**
Expand Down
94 changes: 87 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,63 @@ 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 the number of fee percentages does not match the number of price ranges.
* Reverts if token is Zero address.
0xlucian marked this conversation as resolved.
Show resolved Hide resolved
*
* @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
checkNonZeroAddress(_tokenAddress);
zajck marked this conversation as resolved.
Show resolved Hide resolved
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);
}

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

/**
* @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 getProtocolFeePercentage(address _exchangeToken, uint256 _price) external view override returns (uint256) {
zajck marked this conversation as resolved.
Show resolved Hide resolved
return getProtocolFee(_exchangeToken, _price);
}

/**
* @notice Sets the flat protocol fee for exchanges in $BOSON.
*
Expand Down Expand Up @@ -558,6 +611,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 {
// Set new price ranges
for (uint256 i = 0; i < _priceRanges.length; i++) {
protocolFees().tokenPriceRanges[_tokenAddress].push(_priceRanges[i]);
zajck marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* @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 = 0; i < _feePercentages.length; i++) {
checkMaxPercententage(_feePercentages[i]);
protocolFees().tokenFeePercentages[_tokenAddress].push(_feePercentages[i]);
zajck marked this conversation as resolved.
Show resolved Hide resolved
}
}

/**
* @notice Checks that supplied value is not 0.
*
Expand Down
6 changes: 5 additions & 1 deletion contracts/protocol/libs/ProtocolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,16 @@ library ProtocolLib {

// Protocol fees storage
struct ProtocolFees {
// Percentage that will be taken as a fee from the net of a Boson Protocol exchange
// Default percentage that will be taken as a fee from the net of a Boson Protocol exchange.
// This fee is returned if no fee ranges are configured in the fee table for the given asset.
uint256 percentage; // 1.75% = 175, 100% = 10000
// Flat fee taken for exchanges in $BOSON
uint256 flatBoson;
// buyer escalation deposit percentage
uint256 buyerEscalationDepositPercentage;
// Token-specific fee tables
mapping(address => uint256[]) tokenPriceRanges; // Price ranges for each token
mapping(address => uint256[]) tokenFeePercentages; // Fee percentages for each price range
}

// Protocol entities storage
Expand Down
4 changes: 3 additions & 1 deletion scripts/config/facet-deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ function getConfigHandlerInitArgs() {
priceDiscovery: protocolConfig.PRICE_DISCOVERY[network],
},
protocolConfig.limits,
protocolConfig.fees,
protocolConfig.protocolFeePercentage,
protocolConfig.protocolFeeFlatBoson,
protocolConfig.buyerEscalationDepositPercentage,
];
}

Expand Down
8 changes: 3 additions & 5 deletions scripts/config/protocol-parameters.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ const { oneWeek, ninetyDays } = require("../../test/util/constants");

module.exports = {
// Protocol configuration params
fees: {
percentage: "50", // 0.5% : 50
flatBoson: "0",
buyerEscalationDepositPercentage: "1000", // 10%
},
protocolFeePercentage: "50", // 0.5% : 50
protocolFeeFlatBoson: "0",
buyerEscalationDepositPercentage: "1000", // 10%,
limits: {
maxExchangesPerBatch: "140",
maxOffersPerGroup: "95",
Expand Down
1 change: 1 addition & 0 deletions scripts/config/revert-reasons.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ exports.RevertReasons = {
// Config related
FEE_PERCENTAGE_INVALID: "InvalidFeePercentage",
VALUE_ZERO_NOT_ALLOWED: "ValueZeroNotAllowed",
ARRAY_LENGTH_MISSMATCH: "ArrayLengthMissmatch",

// ERC2981 related
ROYALTY_FEE_INVALID: "InvalidRoyaltyFee",
Expand Down
8 changes: 3 additions & 5 deletions scripts/util/estimate-limits.js
Original file line number Diff line number Diff line change
Expand Up @@ -1002,11 +1002,9 @@ async function setupCommonEnvironment() {
maxPremintedVouchers: 100,
},
// Protocol fees
{
percentage: protocolFeePercentage,
flatBoson: protocolFeeFlatBoson,
buyerEscalationDepositPercentage,
},
protocolFeePercentage,
protocolFeeFlatBoson,
buyerEscalationDepositPercentage,
];

const facetNames = [
Expand Down
2 changes: 1 addition & 1 deletion test/integration/01-update-account-roles-addresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ describe("[@skip-on-coverage] Update account roles addresses", function () {
diamondAddress: protocolDiamondAddress,
signers: [admin, treasury, buyer, rando, adminDR, treasuryDR, agent],
contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler },
protocolConfig: [, , { buyerEscalationDepositPercentage }],
protocolConfig: [, , , , buyerEscalationDepositPercentage],
} = await setupTestEnvironment(contracts));

bosonErrors = await getContractAt("BosonErrors", await accountHandler.getAddress());
Expand Down
2 changes: 1 addition & 1 deletion test/integration/02-Upgraded-facet.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ describe("[@skip-on-coverage] After facet upgrade, everything is still operation
({
signers: [admin, treasury, buyer, rando, adminDR, treasuryDR],
contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler },
protocolConfig: [, , { buyerEscalationDepositPercentage }],
protocolConfig: [, , , , buyerEscalationDepositPercentage],
diamondAddress: protocolDiamondAddress,
} = await setupTestEnvironment(contracts));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe("[@skip-on-coverage] DR removes sellers from the approved seller list",
({
signers: [admin, treasury, buyer, other1, adminDR, treasuryDR],
contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler },
protocolConfig: [, , { buyerEscalationDepositPercentage }],
protocolConfig: [, , , , buyerEscalationDepositPercentage],
} = await setupTestEnvironment(contracts));

// make all account the same
Expand Down
2 changes: 1 addition & 1 deletion test/integration/04-DR-removes-fees.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe("[@skip-on-coverage] DR removes fee", function () {
diamondAddress: protocolDiamondAddress,
signers: [admin, treasury, buyer, adminDR, treasuryDR],
contractInstances: { accountHandler, offerHandler, exchangeHandler, fundsHandler, disputeHandler },
protocolConfig: [, , { buyerEscalationDepositPercentage }],
protocolConfig: [, , , , buyerEscalationDepositPercentage],
} = await setupTestEnvironment(contracts));

// make all account the same
Expand Down
Loading
Loading