diff --git a/contracts/domain/BosonErrors.sol b/contracts/domain/BosonErrors.sol index 3fc02e688..e3a4b444e 100644 --- a/contracts/domain/BosonErrors.sol +++ b/contracts/domain/BosonErrors.sol @@ -376,4 +376,8 @@ interface BosonErrors { error FeeAmountTooHigh(); // Price does not cover the cancellation penalty error PriceDoesNotCoverPenalty(); + + //Fee Table related + // Exchange token should be different than $BOSON when requesting feePercentage + error InvalidExchangeToken(); } diff --git a/contracts/interfaces/handlers/IBosonConfigHandler.sol b/contracts/interfaces/handlers/IBosonConfigHandler.sol index 508b92912..87e097233 100644 --- a/contracts/interfaces/handlers/IBosonConfigHandler.sol +++ b/contracts/interfaces/handlers/IBosonConfigHandler.sol @@ -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: 0xc040bf51 + * The ERC-165 identifier for this interface is: 0x13fd085d */ interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors { /** @@ -156,6 +156,23 @@ interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors { */ 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. * diff --git a/contracts/protocol/bases/ProtocolBase.sol b/contracts/protocol/bases/ProtocolBase.sol index 98a2d9635..be252976b 100644 --- a/contracts/protocol/bases/ProtocolBase.sol +++ b/contracts/protocol/bases/ProtocolBase.sol @@ -695,31 +695,44 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase, BosonErrors * @return protocolFee - the protocol fee */ function _getProtocolFee(address _exchangeToken, uint256 _price) internal view returns (uint256 protocolFee) { - ProtocolLib.ProtocolFees storage fees = protocolFees(); // 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 fees.flatBoson; + // 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 InvalidExchangeToken(); + + 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, calculate based on the price ranges + // If the token has a custom fee table, find the appropriate percentage uint256 priceRangesLength = priceRanges.length; if (priceRangesLength > 0) { for (uint256 i; i < priceRangesLength; ++i) { if (_price <= priceRanges[i]) { - // Apply the fee percentage for the matching price range - return FundsLib.applyPercent(_price, feePercentages[i]); + // Return the fee percentage for the matching price range + return feePercentages[i]; } } // If price exceeds all ranges, use the highest fee percentage - return FundsLib.applyPercent(_price, feePercentages[priceRangesLength - 1]); + return feePercentages[priceRangesLength - 1]; } // If no custom fee table exists, fallback to using the default protocol percentage - return FundsLib.applyPercent(_price, fees.percentage); + return fees.percentage; } /** diff --git a/contracts/protocol/facets/ConfigHandlerFacet.sol b/contracts/protocol/facets/ConfigHandlerFacet.sol index e69cb3439..c4a452689 100644 --- a/contracts/protocol/facets/ConfigHandlerFacet.sol +++ b/contracts/protocol/facets/ConfigHandlerFacet.sol @@ -269,6 +269,25 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { 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. * diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index d1b8fe51c..514fc747c 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -235,4 +235,7 @@ exports.RevertReasons = { TOKEN_ID_MANDATORY: "TokenIdMandatory", NEGATIVE_PRICE_NOT_ALLOWED: "NegativePriceNotAllowed", PRICE_DOES_NOT_COVER_PENALTY: "PriceDoesNotCoverPenalty", + + //Fee Table related + INVALID_EXCHANGE_TOKEN: "InvalidExchangeToken", }; diff --git a/test/protocol/ConfigHandlerTest.js b/test/protocol/ConfigHandlerTest.js index a40f5c615..12a2ddcaf 100644 --- a/test/protocol/ConfigHandlerTest.js +++ b/test/protocol/ConfigHandlerTest.js @@ -989,6 +989,7 @@ describe("IBosonConfigHandler", function () { feeTier = feePercentages[i]; expectedFeeAmount = applyPercentage(exchangeAmount, feeTier); expect(await configHandler.getProtocolFee(usdcAddress, exchangeAmount)).to.equal(expectedFeeAmount); + expect(await configHandler.getProtocolFeePercentage(usdcAddress, exchangeAmount)).to.equal(feeTier); } // check for a way bigger value @@ -996,6 +997,7 @@ describe("IBosonConfigHandler", function () { exchangeAmount = BigInt(feePriceRanges[feePriceRanges.length - 1]) * BigInt(2); expectedFeeAmount = applyPercentage(exchangeAmount, feeTier); expect(await configHandler.getProtocolFee(usdcAddress, exchangeAmount)).to.equal(expectedFeeAmount); + expect(await configHandler.getProtocolFeePercentage(usdcAddress, exchangeAmount)).to.equal(feeTier); }); it("should update state and return boson flat fee if boson token used as exchange token", async function () { @@ -1036,6 +1038,12 @@ describe("IBosonConfigHandler", function () { configHandler.connect(deployer).setProtocolFeeTable(usdcAddress, newPriceRanges, feePercentages) ).to.revertedWithCustomError(bosonErrors, RevertReasons.NON_ASCENDING_ORDER); }); + it("getProtocolFeePercentage should not accept BOSON token as parameter", async function () { + const randomPrice = 10000; + await expect( + configHandler.connect(rando).getProtocolFeePercentage(await token.getAddress(), randomPrice) + ).to.revertedWithCustomError(bosonErrors, RevertReasons.INVALID_EXCHANGE_TOKEN); + }); }); }); });