Skip to content

Commit

Permalink
fix: adjust test so I accounts for dust redemptions
Browse files Browse the repository at this point in the history
  • Loading branch information
kosecki123 committed Nov 29, 2023
1 parent c963782 commit 2a147b9
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 22 deletions.
21 changes: 12 additions & 9 deletions src/FeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
// If you encounter a vulnerability or an issue, please contact <info@neutralx.com>
pragma solidity ^0.8.13;

import "forge-std/console.sol";
import "./interfaces/IDepositFeeCalculator.sol";
import "./interfaces/IRedemptionFeeCalculator.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand All @@ -27,6 +26,7 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
SD59x18 private redemptionFeeShift = sd(0.1 * 1e18); //-log10(0+0.1)=1 -> 10^-1
SD59x18 private redemptionFeeConstant = redemptionFeeScale * (one + redemptionFeeShift).log10(); //0.0413926851582251=log10(1+0.1)
SD59x18 private singleAssetRedemptionRelativeFee = sd(0.1 * 1e18);
SD59x18 private dustAssetRedemptionRelativeFee = sd(0.3 * 1e18);

address[] private _recipients;
uint256[] private _shares;
Expand Down Expand Up @@ -212,21 +212,24 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {

(SD59x18 da, SD59x18 db) = getRatiosRedemption(amount_float, ta, sd(int256(total)));

console.logUint(intoUint256(da));
console.logUint(intoUint256(db));
console.logUint(intoUint256(da - db));

//redemption_fee = scale * (tb * log10(b+shift) - ta * log10(a+shift)) + constant*amount;
SD59x18 i_a = ta * (da + redemptionFeeShift).log10();
SD59x18 i_b = tb * (db + redemptionFeeShift).log10();
SD59x18 fee_float = redemptionFeeScale * (i_b - i_a) + redemptionFeeConstant * amount_float;

/*
@dev
The fee is negative if the amount is too small relative to the pool domination
In this case we apply the dustAssetRedemptionRelativeFee which is currently set to 30%
which corresponds to maximum fee for redemption function
This protects the case where sum of multiple extremely redemption would allow emptying the pool at discount
Case exists only if asset pool domination is > 90% and amount is ~1e-18 of that asset in the pool
*/
if (fee_float < zero) {
uint256 fee = intoUint256(amount_float * (singleAssetRedemptionRelativeFee));
return fee;
return intoUint256(amount_float * dustAssetRedemptionRelativeFee);
}

uint256 fee = intoUint256(fee_float);
return fee;
return intoUint256(fee_float);
}
}
29 changes: 16 additions & 13 deletions test/FeeCalculator.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ pragma solidity ^0.8.13;

import {Test, console2} from "forge-std/Test.sol";
import {FeeCalculator} from "../src/FeeCalculator.sol";
import {SD59x18, sd, intoUint256 as sdIntoUint256} from "@prb/math/src/SD59x18.sol";
import {UD60x18, ud, intoUint256} from "@prb/math/src/UD60x18.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockPool is IERC20 {
Expand Down Expand Up @@ -590,7 +592,7 @@ contract FeeCalculatorTest is Test {
assertEq(fees[0], 737254938220315128);
}

function testCalculateRedemptionFees_HugeTotalLargeCurrentSmallDeposit() public {
function testCalculateRedemptionFees_HugeTotalLargeCurrentSmallDeposit_FeeCappedAt30Percent() public {
// Arrange
// Set up your test data
uint256 depositAmount = 10000 * 1e18;
Expand All @@ -606,10 +608,10 @@ contract FeeCalculatorTest is Test {

// Assert
assertEq(recipients[0], feeRecipient);
assertEq(fees[0], depositAmount / 10);
assertEq(fees[0], depositAmount * 30 / 100);
}

function testCalculateRedemptionFees_NegativeCase() public {
function testCalculateRedemptionFees_NegativeFeeValue_FeeCappedAt30Percent() public {
// Arrange
// Set up your test data
uint256 depositAmount = 2323662174650;
Expand All @@ -624,7 +626,7 @@ contract FeeCalculatorTest is Test {

// Assert
assertEq(recipients[0], feeRecipient);
assertEq(fees[0], depositAmount / 10);
assertEq(fees[0], depositAmount * 30 / 100);
}

function testCalculateDepositFeesFuzzy(uint256 depositAmount, uint256 current, uint256 total) public {
Expand Down Expand Up @@ -687,6 +689,8 @@ contract FeeCalculatorTest is Test {
uint256 current = _current;
uint256 total = _total;

SD59x18 dustAssetRedemptionRelativeFee = sd(0.3 * 1e18);

// Arrange
// Set up your test data

Expand Down Expand Up @@ -714,6 +718,12 @@ contract FeeCalculatorTest is Test {
);
}

/// @dev if we fail at the first try, we do not want to test the rest of the function
vm.assume(oneTimeRedemptionFailed == false);
/// @dev This prevents the case when the fee is so small that it is being calculated using dustAssetRedemptionRelativeFee
/// @dev we don not want to test this case
vm.assume(oneTimeFee != sdIntoUint256(sd(int256(redemptionAmount)) * dustAssetRedemptionRelativeFee));

uint256 equalRedemption = redemptionAmount / numberOfRedemptions;
uint256 restRedemption = redemptionAmount % numberOfRedemptions;
uint256 feeFromDividedRedemptions = 0;
Expand All @@ -738,15 +748,8 @@ contract FeeCalculatorTest is Test {
}
}

// Assert
if (multipleTimesRedemptionFailedCount == 0 && !oneTimeRedemptionFailed) {
uint256 maximumAllowedErrorPercentage = (numberOfRedemptions <= 1) ? 0 : 1;
if (
oneTimeFee + feeFromDividedRedemptions > 1e-8 * 1e18 // we skip assertion for extremely small fees (basically zero fees) because of numerical errors
) {
assertGe((maximumAllowedErrorPercentage + 100) * feeFromDividedRedemptions / 100, oneTimeFee);
} //we add 1% tolerance for numerical errors
}
// @dev we allow for 0.1% error
assertGe(1001 * feeFromDividedRedemptions / 1000, oneTimeFee);
}

function testCalculateDepositFeesFuzzy_DepositDividedIntoOneChunkFeesGreaterOrEqualToOneDeposit(
Expand Down

0 comments on commit 2a147b9

Please sign in to comment.