From 18bbf83f82ce7c4e912b178f6bd73965d757a250 Mon Sep 17 00:00:00 2001 From: Michalis Kargakis Date: Thu, 25 Jan 2024 11:13:49 +0100 Subject: [PATCH] chore: refactor redemption interface Refactor redemption interface to support multi-TCO2 inputs and restrict to a single TCO2 at the same time. This should help make the interface future-proof to support multi-TCO2 redemptions in the future. --- src/FeeCalculator.sol | 11 +++- src/interfaces/IFeeCalculator.sol | 6 +- test/FeeCalculator.fuzzy.t.sol | 10 ++- test/FeeCalculator.t.sol | 105 +++++++++++++++++++++++------- 4 files changed, 101 insertions(+), 31 deletions(-) diff --git a/src/FeeCalculator.sol b/src/FeeCalculator.sol index e33e1ea..04d67cf 100644 --- a/src/FeeCalculator.sol +++ b/src/FeeCalculator.sol @@ -178,16 +178,21 @@ contract FeeCalculator is IFeeCalculator, Ownable { /// @notice Calculates the redemption fees for a given amount. /// @param pool The address of the pool. - /// @param tco2 The address of the TCO2 token. - /// @param redemptionAmount The amount to be redeemed. + /// @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 tco2, uint256 redemptionAmount) + 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"); + address tco2 = tco2s[0]; + uint256 redemptionAmount = redemptionAmounts[0]; + require(redemptionAmount > 0, "redemptionAmount must be > 0"); uint256 feeAmount = getRedemptionFee(redemptionAmount, getTokenBalance(pool, tco2), getTotalSupply(pool)); diff --git a/src/interfaces/IFeeCalculator.sol b/src/interfaces/IFeeCalculator.sol index 2ef267b..69266a3 100644 --- a/src/interfaces/IFeeCalculator.sol +++ b/src/interfaces/IFeeCalculator.sol @@ -27,11 +27,11 @@ interface IFeeCalculator { /// @notice Calculates the redemption fees for a given amount. /// @param pool The address of the pool. - /// @param tco2 The address of the TCO2 token. - /// @param redemptionAmount The amount to be redeemed. + /// @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 tco2, uint256 redemptionAmount) + function calculateRedemptionFees(address pool, address[] calldata tco2s, uint256[] calldata redemptionAmounts) external view returns (FeeDistribution memory feeDistribution); diff --git a/test/FeeCalculator.fuzzy.t.sol b/test/FeeCalculator.fuzzy.t.sol index c73d94f..d9d2983 100644 --- a/test/FeeCalculator.fuzzy.t.sol +++ b/test/FeeCalculator.fuzzy.t.sol @@ -119,8 +119,13 @@ contract FeeCalculatorTestFuzzy is Test { bool oneTimeRedemptionFailed = false; uint256 multipleTimesRedemptionFailedCount = 0; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; + // Act - try feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemptionAmount) returns ( + try feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts) returns ( FeeDistribution memory feeDistribution ) { oneTimeFee = feeDistribution.shares.sumOf(); @@ -145,7 +150,8 @@ contract FeeCalculatorTestFuzzy is Test { for (uint256 i = 0; i < numberOfRedemptions; i++) { uint256 redemption = equalRedemption + (i == 0 ? restRedemption : 0); - try feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemption) returns ( + redemptionAmounts[0] = redemption; + try feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts) returns ( FeeDistribution memory feeDistribution ) { feeFromDividedRedemptions += feeDistribution.shares.sumOf(); diff --git a/test/FeeCalculator.t.sol b/test/FeeCalculator.t.sol index dc89840..a14be2f 100644 --- a/test/FeeCalculator.t.sol +++ b/test/FeeCalculator.t.sol @@ -88,6 +88,10 @@ contract FeeCalculatorTest is Test { // Arrange // Set up your test data uint256 redemptionAmount = 100 * 1e18; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); @@ -95,7 +99,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemptionAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; @@ -109,6 +113,10 @@ contract FeeCalculatorTest is Test { // Arrange // Set up your test data uint256 redemptionAmount = 1 * 1e18; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1e6 * 1e18); @@ -116,7 +124,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemptionAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; @@ -131,6 +139,10 @@ contract FeeCalculatorTest is Test { // Arrange // Set up your test data uint256 redemptionAmount = 1 * 1e18; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1e6 * 1e18); @@ -138,7 +150,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemptionAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; @@ -453,7 +465,11 @@ contract FeeCalculatorTest is Test { function testCalculateRedemptionFees_CurrentGreaterThanTotal_ExceptionShouldBeThrown() public { // Arrange // Set up your test data - uint256 depositAmount = 1; + uint256 redemptionAmount = 1; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); @@ -463,13 +479,17 @@ contract FeeCalculatorTest is Test { vm.expectRevert( "The total volume in the pool must be greater than or equal to the volume for an individual asset" ); - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), depositAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); } function testCalculateRedemptionFees_AmountGreaterThanCurrent_ExceptionShouldBeThrown() public { // Arrange // Set up your test data - uint256 depositAmount = 600 * 1e18; + uint256 redemptionAmount = 600 * 1e18; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); @@ -477,13 +497,17 @@ contract FeeCalculatorTest is Test { // Act vm.expectRevert("The amount to be redeemed cannot exceed the current balance of the pool"); - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), depositAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); } function testCalculateRedemptionFees_ZeroRedemption_ExceptionShouldBeThrown() public { // Arrange // Set up your test data - uint256 depositAmount = 0; + uint256 redemptionAmount = 0; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); @@ -491,7 +515,7 @@ contract FeeCalculatorTest is Test { // Act & Assert vm.expectRevert("redemptionAmount must be > 0"); - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), depositAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); } function testCalculateDepositFees_EmptyPool_FeeCappedAt10Percent() public { @@ -538,6 +562,10 @@ contract FeeCalculatorTest is Test { // Arrange // Set up your test data uint256 redemptionAmount = 0; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); @@ -545,13 +573,17 @@ contract FeeCalculatorTest is Test { // Act vm.expectRevert("redemptionAmount must be > 0"); - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemptionAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); } function testCalculateRedemptionFees_TotalEqualCurrent_FeeCappedAt10Percent() public { // Arrange // Set up your test data uint256 redemptionAmount = 100; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(1000); @@ -559,7 +591,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), redemptionAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; @@ -631,7 +663,11 @@ contract FeeCalculatorTest is Test { function testCalculateRedemptionFees_HugeTotalLargeCurrentSmallDeposit_FeeCappedAt30Percent() public { // Arrange // Set up your test data - uint256 depositAmount = 10000 * 1e18; + uint256 redemptionAmount = 10000 * 1e18; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool uint256 supply = 100000 * 1e18; @@ -640,19 +676,23 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), depositAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; // Assert assertEq(recipients[0], feeRecipient); - assertEq(fees[0], depositAmount * 30 / 100); + assertEq(fees[0], redemptionAmount * 30 / 100); } function testCalculateRedemptionFees_NegativeFeeValue_FeeCappedAt30Percent() public { // Arrange // Set up your test data - uint256 depositAmount = 2323662174650; + uint256 redemptionAmount = 2323662174650; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(56636794628913227180683983236); @@ -660,13 +700,13 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), depositAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); address[] memory recipients = feeDistribution.recipients; uint256[] memory fees = feeDistribution.shares; // Assert assertEq(recipients[0], feeRecipient); - assertEq(fees[0], depositAmount * 30 / 100); + assertEq(fees[0], redemptionAmount * 30 / 100); } function testFeeSetup_RecipientsEmpty_ShouldThrowError() public { @@ -846,6 +886,11 @@ contract FeeCalculatorTest is Test { function testSetRedemptionFeeScale() public { // Arrange + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = 100e18; + // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); mockToken.setTokenBalance(address(mockPool), 500 * 1e18); @@ -853,7 +898,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), 100e18); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); uint256[] memory fees = feeDistribution.shares; // Assert @@ -862,6 +907,11 @@ contract FeeCalculatorTest is Test { function testSetRedemptionFeeShift() public { // Arrange + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = 100e18; + // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); mockToken.setTokenBalance(address(mockPool), 500 * 1e18); @@ -869,7 +919,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), 100e18); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); uint256[] memory fees = feeDistribution.shares; // Assert @@ -878,6 +928,11 @@ contract FeeCalculatorTest is Test { function testSetSingleAssetRedemptionRelativeFee() public { // Arrange + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = 100 * 1e18; + // Set up mock pool mockPool.setTotalSupply(1000 * 1e18); mockToken.setTokenBalance(address(mockPool), 1000 * 1e18); @@ -885,7 +940,7 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), 100 * 1e18); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); uint256[] memory fees = feeDistribution.shares; assertEq(fees[0], 83 * 1e18); @@ -894,7 +949,11 @@ contract FeeCalculatorTest is Test { function testSetDustAssetRedemptionRelativeFee() public { // Arrange // Set up your test data - uint256 depositAmount = 2323662174650; + uint256 redemptionAmount = 2323662174650; + address[] memory tco2s = new address[](1); + tco2s[0] = address(mockToken); + uint256[] memory redemptionAmounts = new uint256[](1); + redemptionAmounts[0] = redemptionAmount; // Set up mock pool mockPool.setTotalSupply(56636794628913227180683983236); @@ -903,10 +962,10 @@ contract FeeCalculatorTest is Test { // Act FeeDistribution memory feeDistribution = - feeCalculator.calculateRedemptionFees(address(mockPool), address(mockToken), depositAmount); + feeCalculator.calculateRedemptionFees(address(mockPool), tco2s, redemptionAmounts); uint256[] memory fees = feeDistribution.shares; // Assert - assertEq(fees[0], depositAmount * 91 / 100); + assertEq(fees[0], redemptionAmount * 91 / 100); } }