From c2b3738cc1c4f3fe8a406c7ec9613d607fb5a941 Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 23 Apr 2024 12:29:47 +0200 Subject: [PATCH 1/5] feat: FlatFeeCalculator --- src/FlatFeeCalculator.sol | 189 ++++++++++++++++++ .../FlatFeeCalculator/FlatFeeCalculator.t.sol | 181 +++++++++++++++++ 2 files changed, 370 insertions(+) create mode 100644 src/FlatFeeCalculator.sol create mode 100644 test/FlatFeeCalculator/FlatFeeCalculator.t.sol diff --git a/src/FlatFeeCalculator.sol b/src/FlatFeeCalculator.sol new file mode 100644 index 0000000..b0469d8 --- /dev/null +++ b/src/FlatFeeCalculator.sol @@ -0,0 +1,189 @@ +// SPDX-FileCopyrightText: 2024 Toucan Protocol +// +// SPDX-License-Identifier: UNLICENSED + +// If you encounter a vulnerability or an issue, please contact +pragma solidity 0.8.19; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; + +import {IFeeCalculator, FeeDistribution} from "./interfaces/IFeeCalculator.sol"; +import "./interfaces/IPool.sol"; + +/// @title FlatFeeCalculator +/// @author Neutral Labs Inc. & Toucan Protocol +/// @notice This contract calculates deposit and redemption fees for a given pool. +/// @dev It implements the IFeeCalculator interface. +contract FlatFeeCalculator is IFeeCalculator, Ownable { + /// @dev Version-related parameters. VERSION keeps track of production + /// releases. VERSION_RELEASE_CANDIDATE keeps track of iterations + /// of a VERSION in our staging environment. + string public constant VERSION = "1.0.0"; + uint256 public constant VERSION_RELEASE_CANDIDATE = 1; + + uint256 public feeBasisPoints = 300; + + address[] private _recipients; + uint256[] private _shares; + + event FeeBasisPointsUpdated(uint256 feeBasisPoints); + event FeeSetup(address[] recipients, uint256[] shares); + + constructor() Ownable() {} + + /// @notice Sets the fee basis points. + /// @dev Can only be called by the current owner. + /// @param _feeBasisPoints The new fee basis points. + function setFeeBasisPoints(uint256 _feeBasisPoints) external onlyOwner { + require(_feeBasisPoints < 10000, "Fee basis points should be less than 10000"); + + feeBasisPoints = _feeBasisPoints; + emit FeeBasisPointsUpdated(_feeBasisPoints); + } + + /// @notice Sets up the fee distribution among recipients. + /// @dev Can only be called by the current owner. + /// @param recipients The addresses of the fee recipients. + /// @param shares The share of the fee each recipient should receive. + function feeSetup(address[] memory recipients, uint256[] memory shares) external onlyOwner { + require(recipients.length == shares.length, "Recipients and shares arrays must have the same length"); + + uint256 totalShares = 0; + for (uint256 i = 0; i < shares.length; i++) { + totalShares += shares[i]; + } + require(totalShares == 100, "Total shares must equal 100"); + + _recipients = recipients; + _shares = shares; + emit FeeSetup(recipients, shares); + } + + /// @notice Calculates the deposit fee for a given amount. + /// @param pool The address of the pool. + /// @param tco2 The address of the TCO2 token. + /// @param depositAmount The amount to be deposited. + /// @return feeDistribution How the fee is meant to be + /// distributed among the fee recipients. + function calculateDepositFees(address pool, address tco2, uint256 depositAmount) + external + view + override + returns (FeeDistribution memory feeDistribution) + { + require(depositAmount > 0, "depositAmount must be > 0"); + + feeDistribution = _calculateFee( + depositAmount + ); + } + + /// @notice Calculates the fee shares and recipients based on the total fee. + /// @param totalFee The total fee to be distributed. + /// @return feeDistribution The recipients and the amount of fees each + /// recipient should receive. + function calculateFeeShares(uint256 totalFee) internal view returns (FeeDistribution memory feeDistribution) { + uint256 recipientsLength = _recipients.length; + uint256[] memory shares = new uint256[](recipientsLength); + + uint256 restFee = totalFee; + for (uint256 i = 0; i < recipientsLength; i++) { + shares[i] = (totalFee * _shares[i]) / 100; + restFee -= shares[i]; + } + + // If any fee is left, it is distributed to the first recipient. + // This may happen if any of the shares of the fee to be distributed + // has leftover from the division by 100 above. + shares[0] += restFee; + + feeDistribution.recipients = _recipients; + feeDistribution.shares = shares; + } + + /// @notice Calculates the redemption fees for a given amount. + /// @param pool The address of the pool. + /// @param tco2s The addresses of the TCO2 token. + /// @param redemptionAmounts The amounts to be redeemed. + /// @return feeDistribution How the fee is meant to be + /// distributed among the fee recipients. + function calculateRedemptionFees(address pool, address[] calldata tco2s, uint256[] calldata redemptionAmounts) + external + view + override + returns (FeeDistribution memory feeDistribution) + { + require(tco2s.length == redemptionAmounts.length, "length mismatch"); + require(tco2s.length == 1, "only one"); + + feeDistribution = _calculateFee( + redemptionAmounts[0] + ); + } + + /// @notice Calculates the deposit fee for a given amount of an ERC1155 project. + /// @param pool The address of the pool. + /// @param erc1155 The address of the ERC1155 project + /// @param tokenId The tokenId of the vintage. + /// @param depositAmount The amount to be deposited. + /// @return feeDistribution How the fee is meant to be + /// distributed among the fee recipients. + function calculateDepositFees(address pool, address erc1155, uint256 tokenId, uint256 depositAmount) + external + view + override + returns (FeeDistribution memory feeDistribution) + { + require(depositAmount > 0, "depositAmount must be > 0"); + + feeDistribution = _calculateFee( + depositAmount + ); + } + + /// @notice Calculates the redemption fees for a given amount on ERC1155 projects. + /// @param pool The address of the pool. + /// @param erc1155s The addresses of the ERC1155 projects. + /// @param tokenIds The tokenIds of the project vintages. + /// @param redemptionAmounts The amounts to be redeemed. + /// @return feeDistribution How the fee is meant to be + /// distributed among the fee recipients. + function calculateRedemptionFees( + address pool, + address[] calldata erc1155s, + uint256[] calldata tokenIds, + uint256[] calldata redemptionAmounts + ) external view override returns (FeeDistribution memory feeDistribution) { + require(erc1155s.length == tokenIds.length, "erc1155s/tokenIds length mismatch"); + require(erc1155s.length == redemptionAmounts.length, "erc1155s/redemptionAmounts length mismatch"); + require(erc1155s.length == 1, "only one"); + + feeDistribution = _calculateFee( + redemptionAmounts[0] + ); + } + + /// @notice Returns the current fee setup. + /// @return recipients shares The fee recipients and their share of the total fee. + function getFeeSetup() external view returns (address[] memory recipients, uint256[] memory shares) { + recipients = _recipients; + shares = _shares; + } + + /// @notice Calculates the fee for a given amount. + /// @param requestedAmount The amount to be used for the fee calculation. + /// @return feeDistribution How the fee is meant to be + function _calculateFee( + uint256 requestedAmount + ) internal view returns (FeeDistribution memory) { + require(requestedAmount > 0, "requested amount must be > 0"); + + uint256 feeAmount = requestedAmount * feeBasisPoints / 10000; + + require(feeAmount <= requestedAmount, "Fee must be lower or equal to requested amount"); + require(feeAmount > 0, "Fee must be greater than 0"); + + return calculateFeeShares(feeAmount); + } +} diff --git a/test/FlatFeeCalculator/FlatFeeCalculator.t.sol b/test/FlatFeeCalculator/FlatFeeCalculator.t.sol new file mode 100644 index 0000000..9c1bba1 --- /dev/null +++ b/test/FlatFeeCalculator/FlatFeeCalculator.t.sol @@ -0,0 +1,181 @@ +// SPDX-FileCopyrightText: 2024 Toucan Protocol +// +// SPDX-License-Identifier: UNLICENSED + +// If you encounter a vulnerability or an issue, please contact +pragma solidity ^0.8.13; + +import {Test, console2} from "forge-std/Test.sol"; +import {FeeCalculator} from "../../src/FeeCalculator.sol"; +import {FeeDistribution} from "../../src/interfaces/IFeeCalculator.sol"; +import {FlatFeeCalculator} from "../../src/FlatFeeCalculator.sol"; + +contract FlatFeeCalculatorTest is Test { + FlatFeeCalculator public feeCalculator; + address public feeRecipient = 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B; + address public empty = address(0); + + function setUp() public { + feeCalculator = new FlatFeeCalculator(); + address[] memory recipients = new address[](1); + recipients[0] = feeRecipient; + uint256[] memory feeShares = new uint256[](1); + feeShares[0] = 100; + feeCalculator.feeSetup(recipients, feeShares); + } + + function testFeeSetupEmpty() public { + address[] memory recipients = new address[](0); + uint256[] memory feeShares = new uint256[](0); + vm.expectRevert("Total shares must equal 100"); + feeCalculator.feeSetup(recipients, feeShares); + } + + function testGetFeeSetup() public { + address feeRecipient1 = 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B; + address feeRecipient2 = 0xCA35b7d915458EF540aDe6068dFe2F44E8fa733c; + + address[] memory recipients = new address[](2); + recipients[0] = feeRecipient1; + recipients[1] = feeRecipient2; + uint256[] memory feeShares = new uint256[](2); + feeShares[0] = 30; + feeShares[1] = 70; + + feeCalculator.feeSetup(recipients, feeShares); + + (address[] memory _recipients, uint256[] memory _feeShares) = feeCalculator.getFeeSetup(); + assertEq(_recipients[0], feeRecipient1); + assertEq(_recipients[1], feeRecipient2); + assertEq(_feeShares[0], 30); + assertEq(_feeShares[1], 70); + } + + function testSetFeeBasisPoints(uint256 basisPoints) public { + vm.assume(basisPoints > 0); + vm.assume(basisPoints < 10000); + + feeCalculator.setFeeBasisPoints(basisPoints); + + assertEq(feeCalculator.feeBasisPoints(), basisPoints); + } + + function testSetFeeBasisPoints_OutOfRange_Reverts(uint256 basisPoints) public { + vm.assume(basisPoints >= 10000); + + vm.expectRevert("Fee basis points should be less than 10000"); + feeCalculator.setFeeBasisPoints(basisPoints); + } + + function testCalculateDepositFeesNormalCase() public { + // Arrange + // Set up your test data + uint256 depositAmount = 100 * 1e18; + + // Act + FeeDistribution memory feeDistribution = + feeCalculator.calculateDepositFees(empty, empty, depositAmount); + address[] memory recipients = feeDistribution.recipients; + uint256[] memory fees = feeDistribution.shares; + + // Assert + assertEq(feeDistribution.recipients.length, feeDistribution.shares.length, "array length mismatch"); + assertEq(recipients[0], feeRecipient); + assertEq(fees[0], 3000000000000000000); + } + + function testCalculateRedemptionFeesNormalCase() public { + // Arrange + // Set up your test data + uint256 redemptionAmount = 100 * 1e18; + address[] memory tco2s = new address[](1); + tco2s[0] = empty; + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; + + // Act + FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); + address[] memory recipients = feeDistribution.recipients; + uint256[] memory fees = feeDistribution.shares; + + // Assert + assertEq(feeDistribution.recipients.length, feeDistribution.shares.length, "array length mismatch"); + assertEq(recipients[0], feeRecipient); + assertEq(fees[0], 3000000000000000000); + } + + function testCalculateRedemptionFeesDustAmount_ShouldThrow() public { + // Arrange + // Set up your test data + uint256 depositAmount = 1; + + // Act + vm.expectRevert("Fee must be greater than 0"); + FeeDistribution memory feeDistribution = + feeCalculator.calculateDepositFees(empty, empty, depositAmount); + } + + function testCalculateDepositFee_TCO2(uint256 depositAmount) public { + // Arrange + vm.assume(depositAmount > 100); + vm.assume(depositAmount < 1e18*1e18); + // Act + FeeDistribution memory feeDistribution = + feeCalculator.calculateDepositFees(empty, empty, depositAmount); + + uint256 expected = depositAmount * feeCalculator.feeBasisPoints() / 10000; + + assertEq(feeDistribution.shares[0], expected); + } + + function testCalculateDepositFee_ERC1155(uint256 depositAmount) public { + // Arrange + vm.assume(depositAmount > 100); + vm.assume(depositAmount < 1e18*1e18); + // Act + FeeDistribution memory feeDistribution = + feeCalculator.calculateDepositFees(empty, empty, 0, depositAmount); + + uint256 expected = depositAmount * feeCalculator.feeBasisPoints() / 10000; + + assertEq(feeDistribution.shares[0], expected); + } + + function testCalculateRedemptionAmount_TCO2(uint256 redemptionAmount) public { + // Arrange + vm.assume(redemptionAmount > 100); + vm.assume(redemptionAmount < 1e18*1e18); + // Act + address[] memory tco2s = new address[](1); + tco2s[0] = empty; + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; + + FeeDistribution memory feeDistribution = + feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); + + uint256 expected = redemptionAmount * feeCalculator.feeBasisPoints() / 10000; + + assertEq(feeDistribution.shares[0], expected); + } + + function testCalculateRedemptionAmount_ERC1155(uint256 redemptionAmount) public { + // Arrange + vm.assume(redemptionAmount > 100); + vm.assume(redemptionAmount < 1e18*1e18); + // Act + address[] memory erc1155s = new address[](1); + erc1155s[0] = empty; + uint256[] memory tokenIds = new uint256[](1); + tokenIds[0] = 1; + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; + + FeeDistribution memory feeDistribution = + feeCalculator.calculateRedemptionFees(empty, erc1155s, tokenIds, redemptionAmounts); + + uint256 expected = redemptionAmount * feeCalculator.feeBasisPoints() / 10000; + + assertEq(feeDistribution.shares[0], expected); + } +} From 1139dc606ccd1b342a45034708754c2b2c74cc0f Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 23 Apr 2024 13:01:19 +0200 Subject: [PATCH 2/5] fix: format fix --- src/FlatFeeCalculator.sol | 44 +++++++------------ .../FlatFeeCalculator/FlatFeeCalculator.t.sol | 25 +++++------ 2 files changed, 27 insertions(+), 42 deletions(-) diff --git a/src/FlatFeeCalculator.sol b/src/FlatFeeCalculator.sol index b0469d8..3a9413d 100644 --- a/src/FlatFeeCalculator.sol +++ b/src/FlatFeeCalculator.sol @@ -67,16 +67,14 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { /// @return feeDistribution How the fee is meant to be /// distributed among the fee recipients. function calculateDepositFees(address pool, address tco2, uint256 depositAmount) - external - view - override - returns (FeeDistribution memory feeDistribution) + external + view + override + returns (FeeDistribution memory feeDistribution) { require(depositAmount > 0, "depositAmount must be > 0"); - feeDistribution = _calculateFee( - depositAmount - ); + feeDistribution = _calculateFee(depositAmount); } /// @notice Calculates the fee shares and recipients based on the total fee. @@ -109,17 +107,15 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { /// @return feeDistribution How the fee is meant to be /// distributed among the fee recipients. function calculateRedemptionFees(address pool, address[] calldata tco2s, uint256[] calldata redemptionAmounts) - external - view - override - returns (FeeDistribution memory feeDistribution) + external + view + override + returns (FeeDistribution memory feeDistribution) { require(tco2s.length == redemptionAmounts.length, "length mismatch"); require(tco2s.length == 1, "only one"); - feeDistribution = _calculateFee( - redemptionAmounts[0] - ); + feeDistribution = _calculateFee(redemptionAmounts[0]); } /// @notice Calculates the deposit fee for a given amount of an ERC1155 project. @@ -130,16 +126,14 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { /// @return feeDistribution How the fee is meant to be /// distributed among the fee recipients. function calculateDepositFees(address pool, address erc1155, uint256 tokenId, uint256 depositAmount) - external - view - override - returns (FeeDistribution memory feeDistribution) + external + view + override + returns (FeeDistribution memory feeDistribution) { require(depositAmount > 0, "depositAmount must be > 0"); - feeDistribution = _calculateFee( - depositAmount - ); + feeDistribution = _calculateFee(depositAmount); } /// @notice Calculates the redemption fees for a given amount on ERC1155 projects. @@ -159,9 +153,7 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { require(erc1155s.length == redemptionAmounts.length, "erc1155s/redemptionAmounts length mismatch"); require(erc1155s.length == 1, "only one"); - feeDistribution = _calculateFee( - redemptionAmounts[0] - ); + feeDistribution = _calculateFee(redemptionAmounts[0]); } /// @notice Returns the current fee setup. @@ -174,9 +166,7 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { /// @notice Calculates the fee for a given amount. /// @param requestedAmount The amount to be used for the fee calculation. /// @return feeDistribution How the fee is meant to be - function _calculateFee( - uint256 requestedAmount - ) internal view returns (FeeDistribution memory) { + function _calculateFee(uint256 requestedAmount) internal view returns (FeeDistribution memory) { require(requestedAmount > 0, "requested amount must be > 0"); uint256 feeAmount = requestedAmount * feeBasisPoints / 10000; diff --git a/test/FlatFeeCalculator/FlatFeeCalculator.t.sol b/test/FlatFeeCalculator/FlatFeeCalculator.t.sol index 9c1bba1..95e7f94 100644 --- a/test/FlatFeeCalculator/FlatFeeCalculator.t.sol +++ b/test/FlatFeeCalculator/FlatFeeCalculator.t.sol @@ -73,8 +73,7 @@ contract FlatFeeCalculatorTest is Test { uint256 depositAmount = 100 * 1e18; // Act - FeeDistribution memory feeDistribution = - feeCalculator.calculateDepositFees(empty, empty, depositAmount); + FeeDistribution memory feeDistribution = feeCalculator.calculateDepositFees(empty, empty, depositAmount); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; @@ -111,17 +110,15 @@ contract FlatFeeCalculatorTest is Test { // Act vm.expectRevert("Fee must be greater than 0"); - FeeDistribution memory feeDistribution = - feeCalculator.calculateDepositFees(empty, empty, depositAmount); + FeeDistribution memory feeDistribution = feeCalculator.calculateDepositFees(empty, empty, depositAmount); } function testCalculateDepositFee_TCO2(uint256 depositAmount) public { // Arrange vm.assume(depositAmount > 100); - vm.assume(depositAmount < 1e18*1e18); + vm.assume(depositAmount < 1e18 * 1e18); // Act - FeeDistribution memory feeDistribution = - feeCalculator.calculateDepositFees(empty, empty, depositAmount); + FeeDistribution memory feeDistribution = feeCalculator.calculateDepositFees(empty, empty, depositAmount); uint256 expected = depositAmount * feeCalculator.feeBasisPoints() / 10000; @@ -131,10 +128,9 @@ contract FlatFeeCalculatorTest is Test { function testCalculateDepositFee_ERC1155(uint256 depositAmount) public { // Arrange vm.assume(depositAmount > 100); - vm.assume(depositAmount < 1e18*1e18); + vm.assume(depositAmount < 1e18 * 1e18); // Act - FeeDistribution memory feeDistribution = - feeCalculator.calculateDepositFees(empty, empty, 0, depositAmount); + FeeDistribution memory feeDistribution = feeCalculator.calculateDepositFees(empty, empty, 0, depositAmount); uint256 expected = depositAmount * feeCalculator.feeBasisPoints() / 10000; @@ -144,15 +140,14 @@ contract FlatFeeCalculatorTest is Test { function testCalculateRedemptionAmount_TCO2(uint256 redemptionAmount) public { // Arrange vm.assume(redemptionAmount > 100); - vm.assume(redemptionAmount < 1e18*1e18); + vm.assume(redemptionAmount < 1e18 * 1e18); // Act address[] memory tco2s = new address[](1); tco2s[0] = empty; uint256[] memory redemptionAmounts = new uint256[](1); redemptionAmounts[0] = redemptionAmount; - FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); + FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); uint256 expected = redemptionAmount * feeCalculator.feeBasisPoints() / 10000; @@ -162,7 +157,7 @@ contract FlatFeeCalculatorTest is Test { function testCalculateRedemptionAmount_ERC1155(uint256 redemptionAmount) public { // Arrange vm.assume(redemptionAmount > 100); - vm.assume(redemptionAmount < 1e18*1e18); + vm.assume(redemptionAmount < 1e18 * 1e18); // Act address[] memory erc1155s = new address[](1); erc1155s[0] = empty; @@ -172,7 +167,7 @@ contract FlatFeeCalculatorTest is Test { redemptionAmounts[0] = redemptionAmount; FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(empty, erc1155s, tokenIds, redemptionAmounts); + feeCalculator.calculateRedemptionFees(empty, erc1155s, tokenIds, redemptionAmounts); uint256 expected = redemptionAmount * feeCalculator.feeBasisPoints() / 10000; From c228b0928efd498c06deaf17b225bea080710eaa Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 23 Apr 2024 16:59:54 +0200 Subject: [PATCH 3/5] fix: rename to FlatFeeCalculator.fuzzy.t.sol --- .../FlatFeeCalculator.fuzzy.t.sol} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename test/{FlatFeeCalculator/FlatFeeCalculator.t.sol => FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol} (99%) diff --git a/test/FlatFeeCalculator/FlatFeeCalculator.t.sol b/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol similarity index 99% rename from test/FlatFeeCalculator/FlatFeeCalculator.t.sol rename to test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol index 95e7f94..50f6fef 100644 --- a/test/FlatFeeCalculator/FlatFeeCalculator.t.sol +++ b/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol @@ -10,7 +10,7 @@ import {FeeCalculator} from "../../src/FeeCalculator.sol"; import {FeeDistribution} from "../../src/interfaces/IFeeCalculator.sol"; import {FlatFeeCalculator} from "../../src/FlatFeeCalculator.sol"; -contract FlatFeeCalculatorTest is Test { +contract FlatFeeCalculatorTestFuzzy is Test { FlatFeeCalculator public feeCalculator; address public feeRecipient = 0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B; address public empty = address(0); From 0a704672a5aebc388d7509e7472311ffc71a9adb Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 23 Apr 2024 17:17:33 +0200 Subject: [PATCH 4/5] feat: support multiple tco2 and erc1155 redemption fee calculation --- src/FlatFeeCalculator.sol | 23 ++++-- .../FlatFeeCalculator.fuzzy.t.sol | 74 +++++++++++++++---- 2 files changed, 74 insertions(+), 23 deletions(-) diff --git a/src/FlatFeeCalculator.sol b/src/FlatFeeCalculator.sol index 3a9413d..c8c8602 100644 --- a/src/FlatFeeCalculator.sol +++ b/src/FlatFeeCalculator.sol @@ -49,10 +49,7 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { function feeSetup(address[] memory recipients, uint256[] memory shares) external onlyOwner { require(recipients.length == shares.length, "Recipients and shares arrays must have the same length"); - uint256 totalShares = 0; - for (uint256 i = 0; i < shares.length; i++) { - totalShares += shares[i]; - } + uint256 totalShares = sumOf(shares); require(totalShares == 100, "Total shares must equal 100"); _recipients = recipients; @@ -113,9 +110,10 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { returns (FeeDistribution memory feeDistribution) { require(tco2s.length == redemptionAmounts.length, "length mismatch"); - require(tco2s.length == 1, "only one"); - feeDistribution = _calculateFee(redemptionAmounts[0]); + uint256 totalRedemptionAmount = sumOf(redemptionAmounts); + + feeDistribution = _calculateFee(totalRedemptionAmount); } /// @notice Calculates the deposit fee for a given amount of an ERC1155 project. @@ -151,9 +149,10 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { ) external view override returns (FeeDistribution memory feeDistribution) { require(erc1155s.length == tokenIds.length, "erc1155s/tokenIds length mismatch"); require(erc1155s.length == redemptionAmounts.length, "erc1155s/redemptionAmounts length mismatch"); - require(erc1155s.length == 1, "only one"); - feeDistribution = _calculateFee(redemptionAmounts[0]); + uint256 totalRedemptionAmount = sumOf(redemptionAmounts); + + feeDistribution = _calculateFee(totalRedemptionAmount); } /// @notice Returns the current fee setup. @@ -176,4 +175,12 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { return calculateFeeShares(feeAmount); } + + function sumOf(uint256[] memory array) private pure returns (uint256) { + uint256 total = 0; + for (uint i = 0; i < array.length; i++) { + total += array[i]; + } + return total; + } } diff --git a/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol b/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol index 50f6fef..b51f184 100644 --- a/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol +++ b/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol @@ -103,6 +103,32 @@ contract FlatFeeCalculatorTestFuzzy is Test { assertEq(fees[0], 3000000000000000000); } + function testCalculateRedemptionFeesMultipleTokensNormalCase() public { + // Arrange + // Set up your test data + uint256 redemptionAmount = 100 * 1e18; + address[] memory tco2s = new address[](3); + tco2s[0] = empty; + tco2s[1] = empty; + tco2s[2] = empty; + uint256[] memory redemptionAmounts = new uint256[](3); + redemptionAmounts[0] = redemptionAmount; + redemptionAmounts[1] = redemptionAmount; + redemptionAmounts[2] = redemptionAmount; + + // Act + FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); + address[] memory recipients = feeDistribution.recipients; + uint256[] memory fees = feeDistribution.shares; + + // Assert + assertEq(feeDistribution.recipients.length, feeDistribution.shares.length, "array length mismatch"); + assertEq(recipients[0], feeRecipient); + + uint256 expected = 3*redemptionAmount*feeCalculator.feeBasisPoints() / 10000; + assertEq(fees[0], expected); + } + function testCalculateRedemptionFeesDustAmount_ShouldThrow() public { // Arrange // Set up your test data @@ -137,39 +163,57 @@ contract FlatFeeCalculatorTestFuzzy is Test { assertEq(feeDistribution.shares[0], expected); } - function testCalculateRedemptionAmount_TCO2(uint256 redemptionAmount) public { + function testCalculateRedemptionAmount_TCO2(uint256 redemptionAmount1, uint256 redemptionAmount2, uint256 redemptionAmount3) public { // Arrange - vm.assume(redemptionAmount > 100); - vm.assume(redemptionAmount < 1e18 * 1e18); + vm.assume(redemptionAmount1 > 100); + vm.assume(redemptionAmount1 < 1e18 * 1e18); + vm.assume(redemptionAmount2 > 100); + vm.assume(redemptionAmount2 < 1e18 * 1e18); + vm.assume(redemptionAmount3 > 100); + vm.assume(redemptionAmount3 < 1e18 * 1e18); // Act - address[] memory tco2s = new address[](1); + address[] memory tco2s = new address[](3); tco2s[0] = empty; - uint256[] memory redemptionAmounts = new uint256[](1); - redemptionAmounts[0] = redemptionAmount; + tco2s[1] = empty; + tco2s[2] = empty; + uint256[] memory redemptionAmounts = new uint256[](3); + redemptionAmounts[0] = redemptionAmount1; + redemptionAmounts[1] = redemptionAmount2; + redemptionAmounts[2] = redemptionAmount3; FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); - uint256 expected = redemptionAmount * feeCalculator.feeBasisPoints() / 10000; + uint256 expected = (redemptionAmount1 + redemptionAmount2 + redemptionAmount3) * feeCalculator.feeBasisPoints() / 10000; assertEq(feeDistribution.shares[0], expected); } - function testCalculateRedemptionAmount_ERC1155(uint256 redemptionAmount) public { + function testCalculateRedemptionAmount_ERC1155(uint256 redemptionAmount1, uint256 redemptionAmount2, uint256 redemptionAmount3) public { // Arrange - vm.assume(redemptionAmount > 100); - vm.assume(redemptionAmount < 1e18 * 1e18); + vm.assume(redemptionAmount1 > 100); + vm.assume(redemptionAmount1 < 1e18 * 1e18); + vm.assume(redemptionAmount2 > 100); + vm.assume(redemptionAmount2 < 1e18 * 1e18); + vm.assume(redemptionAmount3 > 100); + vm.assume(redemptionAmount3 < 1e18 * 1e18); // Act - address[] memory erc1155s = new address[](1); + address[] memory erc1155s = new address[](3); erc1155s[0] = empty; - uint256[] memory tokenIds = new uint256[](1); + erc1155s[1] = empty; + erc1155s[2] = empty; + uint256[] memory tokenIds = new uint256[](3); tokenIds[0] = 1; - uint256[] memory redemptionAmounts = new uint256[](1); - redemptionAmounts[0] = redemptionAmount; + tokenIds[1] = 2; + tokenIds[2] = 3; + uint256[] memory redemptionAmounts = new uint256[](3); + redemptionAmounts[0] = redemptionAmount1; + redemptionAmounts[1] = redemptionAmount2; + redemptionAmounts[2] = redemptionAmount3; FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, erc1155s, tokenIds, redemptionAmounts); - uint256 expected = redemptionAmount * feeCalculator.feeBasisPoints() / 10000; + uint256 expected = (redemptionAmount1 + redemptionAmount2 + redemptionAmount3) * feeCalculator.feeBasisPoints() / 10000; assertEq(feeDistribution.shares[0], expected); } From 9766421f0d7196f6211f04954834b26a28323048 Mon Sep 17 00:00:00 2001 From: Piotr Date: Tue, 23 Apr 2024 17:26:34 +0200 Subject: [PATCH 5/5] fix: format --- src/FlatFeeCalculator.sol | 2 +- .../FlatFeeCalculator.fuzzy.t.sol | 20 ++++++++++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/FlatFeeCalculator.sol b/src/FlatFeeCalculator.sol index c8c8602..121610c 100644 --- a/src/FlatFeeCalculator.sol +++ b/src/FlatFeeCalculator.sol @@ -178,7 +178,7 @@ contract FlatFeeCalculator is IFeeCalculator, Ownable { function sumOf(uint256[] memory array) private pure returns (uint256) { uint256 total = 0; - for (uint i = 0; i < array.length; i++) { + for (uint256 i = 0; i < array.length; i++) { total += array[i]; } return total; diff --git a/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol b/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol index b51f184..0397b0c 100644 --- a/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol +++ b/test/FlatFeeCalculatorFuzzy/FlatFeeCalculator.fuzzy.t.sol @@ -125,7 +125,7 @@ contract FlatFeeCalculatorTestFuzzy is Test { assertEq(feeDistribution.recipients.length, feeDistribution.shares.length, "array length mismatch"); assertEq(recipients[0], feeRecipient); - uint256 expected = 3*redemptionAmount*feeCalculator.feeBasisPoints() / 10000; + uint256 expected = 3 * redemptionAmount * feeCalculator.feeBasisPoints() / 10000; assertEq(fees[0], expected); } @@ -163,7 +163,11 @@ contract FlatFeeCalculatorTestFuzzy is Test { assertEq(feeDistribution.shares[0], expected); } - function testCalculateRedemptionAmount_TCO2(uint256 redemptionAmount1, uint256 redemptionAmount2, uint256 redemptionAmount3) public { + function testCalculateRedemptionAmount_TCO2( + uint256 redemptionAmount1, + uint256 redemptionAmount2, + uint256 redemptionAmount3 + ) public { // Arrange vm.assume(redemptionAmount1 > 100); vm.assume(redemptionAmount1 < 1e18 * 1e18); @@ -183,12 +187,17 @@ contract FlatFeeCalculatorTestFuzzy is Test { FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, tco2s, redemptionAmounts); - uint256 expected = (redemptionAmount1 + redemptionAmount2 + redemptionAmount3) * feeCalculator.feeBasisPoints() / 10000; + uint256 expected = + (redemptionAmount1 + redemptionAmount2 + redemptionAmount3) * feeCalculator.feeBasisPoints() / 10000; assertEq(feeDistribution.shares[0], expected); } - function testCalculateRedemptionAmount_ERC1155(uint256 redemptionAmount1, uint256 redemptionAmount2, uint256 redemptionAmount3) public { + function testCalculateRedemptionAmount_ERC1155( + uint256 redemptionAmount1, + uint256 redemptionAmount2, + uint256 redemptionAmount3 + ) public { // Arrange vm.assume(redemptionAmount1 > 100); vm.assume(redemptionAmount1 < 1e18 * 1e18); @@ -213,7 +222,8 @@ contract FlatFeeCalculatorTestFuzzy is Test { FeeDistribution memory feeDistribution = feeCalculator.calculateRedemptionFees(empty, erc1155s, tokenIds, redemptionAmounts); - uint256 expected = (redemptionAmount1 + redemptionAmount2 + redemptionAmount3) * feeCalculator.feeBasisPoints() / 10000; + uint256 expected = + (redemptionAmount1 + redemptionAmount2 + redemptionAmount3) * feeCalculator.feeBasisPoints() / 10000; assertEq(feeDistribution.shares[0], expected); }