Skip to content

Commit

Permalink
Merge pull request #12 from neutral-protocol/feature/10-percent-flat-…
Browse files Browse the repository at this point in the history
…fee-when-single-asset-in-the-pool

10 percent flat fee for deposits and redemptions when there is a single asset in the pool or the pool is empty
  • Loading branch information
PawelTroka authored Nov 22, 2023
2 parents 3d233d1 + a088d01 commit fec047c
Show file tree
Hide file tree
Showing 5 changed files with 348 additions and 179 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ The fee function is designed to discourage monopolizing the pool with one asset.

The fee functions for both operations are based on dominance coefficients `a` and `b`, which designate the ratio of how dominant a particular asset is before (`a`) and after (`b`) the operation.

## Single Asset or No Assets in the Pool
In the case where there is only one asset in the pool or the pool is empty, the fee structure is simplified to a flat rate. This is designed to encourage diversification in the pool and discourage monopolization by a single asset.

For both deposit and redemption operations, a flat fee of 10% is applied. This means that regardless of the amount deposited or redeemed, the fee will always be 10% of that amount.
This flat fee structure serves two purposes:
1. Simplicity: It provides a straightforward and predictable fee calculation for users when there is only one asset or no assets in the pool.
2. Encouragement of Diversification: The flat fee encourages users to diversify the assets in the pool. If there are multiple assets in the pool, the fee calculation becomes more complex (as described in the sections below), potentially leading to lower fees for less dominant assets.

Remember, the goal of this fee structure is to maintain a balanced composition in the pool and discourage monopolization by any single asset.

## Mathematical Expressions

### Dominance Coefficients
Expand Down
75 changes: 54 additions & 21 deletions src/FeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {

SD59x18 private depositFeeScale = sd(0.18 * 1e18);
SD59x18 private depositFeeRatioScale = sd(0.99 * 1e18);
SD59x18 private singleAssetDepositRelativeFee = sd(0.1 * 1e18);

SD59x18 private redemptionFeeScale = sd(0.3 * 1e18);
SD59x18 private redemptionFeeShift = sd(0.1 * 1e18);//-log10(0+0.1)=1 -> 10^-1
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);

address[] private _recipients;
uint256[] private _shares;
Expand All @@ -36,7 +38,7 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
require(recipients.length > 0, "Recipients and shares arrays must not be empty");

uint256 totalShares = 0;
for (uint i = 0; i < shares.length; i++) {
for (uint256 i = 0; i < shares.length; i++) {
totalShares += shares[i];
}
require(totalShares == 100, "Total shares must equal 100");
Expand All @@ -51,30 +53,42 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
/// @param depositAmount The amount to be deposited.
/// @return recipients The addresses of the fee recipients.
/// @return feesDenominatedInPoolTokens The amount of fees each recipient should receive.
function calculateDepositFees(address tco2, address pool, uint256 depositAmount) external override returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens) {
function calculateDepositFees(address tco2, address pool, uint256 depositAmount)
external
override
returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens)
{
require(depositAmount > 0, "depositAmount must be > 0");

uint256 totalFee = getDepositFee(depositAmount, getTokenBalance(pool, tco2), getTotalSupply(pool));

require(totalFee <= depositAmount, "Fee must be lower or equal to deposit amount");
require(totalFee > 0, "Fee must be greater than 0");

return distributeFeeAmongShares(totalFee);
}

/// @notice Distributes the total fee among the recipients according to their shares.
/// @param totalFee The total fee to be distributed.
/// @return recipients The addresses of the fee recipients.
/// @return feesDenominatedInPoolTokens The amount of fees each recipient should receive.
function distributeFeeAmongShares(uint256 totalFee) private view returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens) {
function distributeFeeAmongShares(uint256 totalFee)
private
view
returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens)
{
feesDenominatedInPoolTokens = new uint256[](_recipients.length);

uint256 restFee = totalFee;

for (uint i = 0; i < _recipients.length; i++) {
for (uint256 i = 0; i < _recipients.length; i++) {
feesDenominatedInPoolTokens[i] = (totalFee * _shares[i]) / 100;
restFee -= feesDenominatedInPoolTokens[i];
}

require(restFee >= 0);
recipients = _recipients;
feesDenominatedInPoolTokens[0] += restFee;//we give rest of the fee (if any) to the first recipient
feesDenominatedInPoolTokens[0] += restFee; //we give rest of the fee (if any) to the first recipient
}

/// @notice Calculates the redemption fees for a given amount.
Expand All @@ -83,10 +97,18 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
/// @param depositAmount The amount to be redeemed.
/// @return recipients The addresses of the fee recipients.
/// @return feesDenominatedInPoolTokens The amount of fees each recipient should receive.
function calculateRedemptionFee(address tco2, address pool, uint256 depositAmount) external override returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens) {
function calculateRedemptionFee(address tco2, address pool, uint256 depositAmount)
external
override
returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens)
{
require(depositAmount > 0, "depositAmount must be > 0");

uint256 totalFee = getRedemptionFee(depositAmount, getTokenBalance(pool, tco2), getTotalSupply(pool));

require(totalFee <= depositAmount, "Fee must be lower or equal to redemption amount");
require(totalFee > 0, "Fee must be greater than 0");

return distributeFeeAmongShares(totalFee);
}

Expand All @@ -112,8 +134,7 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
/// @param current The current balance of the pool.
/// @param total The total supply of the pool.
/// @return The calculated ratios.
function getRatiosDeposit(SD59x18 amount, SD59x18 current, SD59x18 total) private view returns (SD59x18, SD59x18)
{
function getRatiosDeposit(SD59x18 amount, SD59x18 current, SD59x18 total) private view returns (SD59x18, SD59x18) {
SD59x18 a = total == zero ? zero : current / total;
SD59x18 b = (current + amount) / (total + amount);

Expand All @@ -125,7 +146,10 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
/// @param current The current balance of the pool.
/// @param total The total supply of the pool.
/// @return The calculated ratios.
function getRatiosRedemption(SD59x18 amount, SD59x18 current, SD59x18 total) private view returns (SD59x18, SD59x18)
function getRatiosRedemption(SD59x18 amount, SD59x18 current, SD59x18 total)
private
view
returns (SD59x18, SD59x18)
{
SD59x18 a = total == zero ? zero : current / total;
SD59x18 b = (total - amount) == zero ? zero : (current - amount) / (total - amount);
Expand All @@ -142,6 +166,14 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
require(total >= current);

SD59x18 amount_float = sd(int256(amount));

if (
current == total //single asset (or no assets) special case
) {
uint256 fee = intoUint256(amount_float * singleAssetDepositRelativeFee);
return fee;
}

SD59x18 ta = sd(int256(current));
SD59x18 tb = ta + amount_float;

Expand All @@ -153,10 +185,6 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
SD59x18 fee_float = depositFeeScale * (ta_log_a - tb_log_b);

uint256 fee = intoUint256(fee_float);

require(fee <= amount, "Fee must be lower or equal to deposit amount");
require(fee > 0, "Fee must be greater than 0");

return fee;
}

Expand All @@ -170,6 +198,14 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
require(amount <= current);

SD59x18 amount_float = sd(int256(amount));

if (
current == total //single asset (or no assets) special case
) {
uint256 fee = intoUint256(amount_float * (singleAssetRedemptionRelativeFee));
return fee;
}

SD59x18 ta = sd(int256(current));
SD59x18 tb = ta - amount_float;

Expand All @@ -180,19 +216,16 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
SD59x18 i_b = tb * (db + redemptionFeeShift).log10();
SD59x18 fee_float = redemptionFeeScale * (i_b - i_a) + redemptionFeeConstant * amount_float;

if (fee_float < zero)
{
if (fee_float / amount_float < sd(1e-6 * 1e18))
if (fee_float < zero) {
if (fee_float / amount_float < sd(1e-6 * 1e18)) {
//fee_float=zero_signed;//if the fee is negative but is less than 0.0001% of amount than it's basically 0
require(fee_float > zero, "Fee must be greater than 0");
else
} else {
require(fee_float > zero, "Total failure. Fee must be greater than 0 or at least close to it.");
}
}

uint256 fee = intoUint256(fee_float);

require(fee <= amount, "Fee must be lower or equal to redemption amount");

return fee;
}
}
4 changes: 3 additions & 1 deletion src/interfaces/IDepositFeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ interface IDepositFeeCalculator {
/// @param depositAmount The amount to be deposited.
/// @return recipients The addresses of the fee recipients.
/// @return feesDenominatedInPoolTokens The amount of fees each recipient should receive.
function calculateDepositFees(address tco2, address pool, uint256 depositAmount) external returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens);
function calculateDepositFees(address tco2, address pool, uint256 depositAmount)
external
returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens);
}
4 changes: 3 additions & 1 deletion src/interfaces/IRedemptionFeeCalculator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,7 @@ interface IRedemptionFeeCalculator {
/// @param depositAmount The amount to be redeemed.
/// @return recipients The addresses of the fee recipients.
/// @return feesDenominatedInPoolTokens The amount of fees each recipient should receive.
function calculateRedemptionFee(address tco2, address pool, uint256 depositAmount) external returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens);
function calculateRedemptionFee(address tco2, address pool, uint256 depositAmount)
external
returns (address[] memory recipients, uint256[] memory feesDenominatedInPoolTokens);
}
Loading

0 comments on commit fec047c

Please sign in to comment.