From 1c09acedcc964421ae516f5f22a4d4851cd75416 Mon Sep 17 00:00:00 2001 From: 0xlucian <0xluciandev@gmail.com> Date: Tue, 3 Dec 2024 13:23:58 -0800 Subject: [PATCH] refactor: address PR comments --- contracts/domain/BosonErrors.sol | 6 ++--- .../handlers/IBosonConfigHandler.sol | 13 ++++++++- contracts/protocol/bases/ProtocolBase.sol | 4 +-- .../protocol/facets/ConfigHandlerFacet.sol | 22 ++++++++++++++- scripts/config/revert-reasons.js | 2 +- test/protocol/ConfigHandlerTest.js | 27 ++++++++++++++++++- 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/contracts/domain/BosonErrors.sol b/contracts/domain/BosonErrors.sol index e3a4b444e..a39114d5b 100644 --- a/contracts/domain/BosonErrors.sol +++ b/contracts/domain/BosonErrors.sol @@ -377,7 +377,7 @@ interface BosonErrors { // Price does not cover the cancellation penalty error PriceDoesNotCoverPenalty(); - //Fee Table related - // Exchange token should be different than $BOSON when requesting feePercentage - error InvalidExchangeToken(); + // Fee Table related + // Thrown if asset is not supported in feeTable feature. + error FeeTableAssetNotSupported(); } diff --git a/contracts/interfaces/handlers/IBosonConfigHandler.sol b/contracts/interfaces/handlers/IBosonConfigHandler.sol index 87e097233..2e603a1c6 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: 0x13fd085d + * The ERC-165 identifier for this interface is: 0xe9aa49b6 */ interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors { /** @@ -149,6 +149,17 @@ interface IBosonConfigHandler is IBosonConfigEvents, BosonErrors { 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. * diff --git a/contracts/protocol/bases/ProtocolBase.sol b/contracts/protocol/bases/ProtocolBase.sol index be252976b..6760c9955 100644 --- a/contracts/protocol/bases/ProtocolBase.sol +++ b/contracts/protocol/bases/ProtocolBase.sol @@ -712,7 +712,7 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase, BosonErrors * @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(); + if (_exchangeToken == protocolAddresses().token) revert FeeTableAssetNotSupported(); ProtocolLib.ProtocolFees storage fees = protocolFees(); uint256[] storage priceRanges = fees.tokenPriceRanges[_exchangeToken]; @@ -721,7 +721,7 @@ abstract contract ProtocolBase is PausableBase, ReentrancyGuardBase, BosonErrors // 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) { + for (uint256 i; i < priceRangesLength - 1; ++i) { if (_price <= priceRanges[i]) { // Return the fee percentage for the matching price range return feePercentages[i]; diff --git a/contracts/protocol/facets/ConfigHandlerFacet.sol b/contracts/protocol/facets/ConfigHandlerFacet.sol index c4a452689..864258457 100644 --- a/contracts/protocol/facets/ConfigHandlerFacet.sol +++ b/contracts/protocol/facets/ConfigHandlerFacet.sol @@ -233,6 +233,7 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { * @notice Sets the feeTable for a specific token given price ranges and fee tiers for * the corresponding price ranges. * + * 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%. @@ -248,6 +249,7 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { uint256[] calldata _priceRanges, uint256[] calldata _feePercentages ) external override onlyRole(ADMIN) nonReentrant { + if (_tokenAddress == protocolAddresses().token) revert FeeTableAssetNotSupported(); if (_priceRanges.length != _feePercentages.length) revert ArrayLengthMismatch(); // Clear existing price ranges and percentage tiers delete protocolFees().tokenPriceRanges[_tokenAddress]; @@ -260,6 +262,24 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { 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. * @@ -638,7 +658,7 @@ contract ConfigHandlerFacet is IBosonConfigHandler, ProtocolBase { */ function setTokenPriceRanges(address _tokenAddress, uint256[] calldata _priceRanges) internal { for (uint256 i = 1; i < _priceRanges.length; ++i) { - if (_priceRanges[i] < _priceRanges[i - 1]) revert NonAscendingOrder(); + if (_priceRanges[i] <= _priceRanges[i - 1]) revert NonAscendingOrder(); } protocolFees().tokenPriceRanges[_tokenAddress] = _priceRanges; } diff --git a/scripts/config/revert-reasons.js b/scripts/config/revert-reasons.js index 514fc747c..a35158728 100644 --- a/scripts/config/revert-reasons.js +++ b/scripts/config/revert-reasons.js @@ -237,5 +237,5 @@ exports.RevertReasons = { PRICE_DOES_NOT_COVER_PENALTY: "PriceDoesNotCoverPenalty", //Fee Table related - INVALID_EXCHANGE_TOKEN: "InvalidExchangeToken", + FEE_TABLE_ASSET_NOT_SUPOPRTED: "FeeTableAssetNotSupported", }; diff --git a/test/protocol/ConfigHandlerTest.js b/test/protocol/ConfigHandlerTest.js index 12a2ddcaf..9ea530d92 100644 --- a/test/protocol/ConfigHandlerTest.js +++ b/test/protocol/ConfigHandlerTest.js @@ -1013,6 +1013,13 @@ describe("IBosonConfigHandler", function () { configHandler.connect(rando).setProtocolFeeTable(usdcAddress, feePriceRanges, feePercentages) ).to.revertedWithCustomError(bosonErrors, RevertReasons.ACCESS_DENIED); }); + it("should revert if _tokenAddress is the BOSON token", async function () { + await expect( + configHandler + .connect(deployer) + .setProtocolFeeTable(await token.getAddress(), feePriceRanges, feePercentages) + ).to.be.revertedWithCustomError(bosonErrors, RevertReasons.FEE_TABLE_ASSET_NOT_SUPOPRTED); + }); it("price ranges count different from fee percents", async function () { const newPriceRanges = [...feePriceRanges, parseUnits("10", "ether").toString()]; await expect( @@ -1042,7 +1049,7 @@ describe("IBosonConfigHandler", function () { const randomPrice = 10000; await expect( configHandler.connect(rando).getProtocolFeePercentage(await token.getAddress(), randomPrice) - ).to.revertedWithCustomError(bosonErrors, RevertReasons.INVALID_EXCHANGE_TOKEN); + ).to.revertedWithCustomError(bosonErrors, RevertReasons.FEE_TABLE_ASSET_NOT_SUPOPRTED); }); }); }); @@ -1125,6 +1132,24 @@ describe("IBosonConfigHandler", function () { minDisputePeriod, "Invalid min dispute period" ); + it("Should return the correct fee table for a token", async function () { + const feePriceRanges = [ + parseUnits("1", "ether").toString(), + parseUnits("2", "ether").toString(), + parseUnits("5", "ether").toString(), + ]; + const feePercentages = [500, 1000, 2000]; // 5%, 10%, 20% + + await configHandler.connect(deployer).setProtocolFeeTable(usdc.address, feePriceRanges, feePercentages); + + const [retrievedRanges, retrievedPercentages] = await configHandler.getProtocolFeeTable(usdc.address); + + expect(retrievedRanges.map((r) => r.toString())).to.deep.equal(feePriceRanges, "Incorrect price ranges"); + expect(retrievedPercentages.map((p) => p.toNumber())).to.deep.equal( + feePercentages, + "Incorrect fee percentages" + ); + }); }); }); });