Skip to content

Commit

Permalink
Merge pull request #10 from neutral-protocol/feature/documentation
Browse files Browse the repository at this point in the history
documentation
  • Loading branch information
kosecki123 authored Nov 21, 2023
2 parents abf0c6d + d7ca887 commit 3d233d1
Show file tree
Hide file tree
Showing 7 changed files with 258 additions and 43 deletions.
66 changes: 66 additions & 0 deletions FOUNDRY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
## Foundry

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**

Foundry consists of:

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.

## Documentation

https://book.getfoundry.sh/

## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Gas Snapshots

```shell
$ forge snapshot
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
138 changes: 95 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,118 @@
## Foundry
<div align="center">

**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.**
# FeeCalculator :moneybag:

Foundry consists of:
A robust Solidity contract for calculating deposit and redemption fees for a biochar ReFi pool.

- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools).
- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data.
- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network.
- **Chisel**: Fast, utilitarian, and verbose solidity REPL.
[![Solidity](https://img.shields.io/badge/Solidity-^0.8.13-blue.svg)](https://soliditylang.org/)
[![OpenZeppelin](https://img.shields.io/badge/OpenZeppelin-Contracts-green.svg)](https://openzeppelin.com/contracts/)
[![PRBMath](https://img.shields.io/badge/PRBMath-Library-orange.svg)](https://github.com/hifi-finance/prb-math)

## Documentation
</div>

https://book.getfoundry.sh/
## :sparkles: Features

## Usage
FeeCalculator implements `IDepositFeeCalculator` and `IRedemptionFeeCalculator` interfaces, providing the following features:

### Build
- **Fee Distribution:** Set up the fee distribution among recipients.
- **Deposit Fees Calculation:** Calculate the deposit fees for a given amount and distribute the total fee among the recipients according to their shares.
- **Redemption Fees Calculation:** Calculate the redemption fees for a given amount.
- **Pool Information Retrieval:** Get the balance of the TCO2 token in a given pool, and the total supply of a given pool.
- **Ratios Calculation:** Calculate the ratios for deposit and redemption fee calculation.
- **Fee Calculation:** Calculate the deposit and redemption fee for a given amount.

```shell
$ forge build
```
## :hammer_and_wrench: How to Test

### Test
Run the following command in your terminal:

```shell
$ forge test
```bash
forge test -vv --via-ir
```

### Format
# Fee Structure :chart_with_upwards_trend:

```shell
$ forge fmt
```
The fee function is designed to discourage monopolizing the pool with one asset. It imposes higher fees for deposits of assets that already dominate the pool, and lower fees for deposits of assets that are not in the pool. Conversely, redeeming an asset that monopolizes the pool is cheap, while redeeming an asset that makes up a small percentage of the pool is expensive.

### Gas Snapshots
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.

```shell
$ forge snapshot
```
## Mathematical Expressions

### Anvil
### Dominance Coefficients

```shell
$ anvil
```
`a = current_asset_volume / total_pool_volume`

### Deploy
`b = (current_asset_volume +/- deposit/redemption amount ) / (total_pool_volume +/- deposit/redemption amount)`

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```
### Current and Future Amounts of a Particular Asset in the Pool

### Cast
`ta = current_asset_volume`

```shell
$ cast <subcommand>
```
`tb = current_asset_volume +/- deposit/redemption amount`

### Help
### Fee Function for Deposit

```shell
$ forge --help
$ anvil --help
$ cast --help
```
Relative fee values are between 0% (exclusive) and 36% (inclusive).

Functional form for absolute fee is as follows:

`Fee = M * (ta * log10(1 - a * N) - tb * log10(1 - b * N))`

where
`M = 0.18 ; N=0.99`

### Fee Function for Redemption

Relative fee values are between 0% (exclusive) and ~31.24% (inclusive).

Functional form for absolute fee is as follows:

`Fee = scale * (tb * log10(b+shift) - ta * log10(a+shift)) + C*amount`

where
`scale=0.3 ; shift=0.1 ; C=scale*log10(1+shift)=scale*0.0413926851582251=0.0124178055474675`

## Fee Function Graphs

The following graphs illustrate the fee functions for deposit and redemption.

### Deposit Fee Function Graph

![Deposit Fee Function Graph](https://github.com/neutral-protocol/dynamic-fee-pools/assets/11928766/8247198c-a620-4533-aede-fa827a3cfc46)

In this graph, the X-axis represents the dominance of an asset, and the Y-axis represents the relative fee for deposit.

### Redemption Fee Function Graph

![Redemption Fee Function Graph](https://github.com/neutral-protocol/dynamic-fee-pools/assets/11928766/e308e855-b89e-4311-b182-28f81bc3ab94)

In this graph, the X-axis represents the dominance of an asset, and the Y-axis represents the relative fee for redemption.

These graphs help visualize how the fee changes based on the dominance of an asset in the pool. As the dominance of an asset increases, so does the fee for depositing more of that asset. Conversely, as the dominance of an asset decreases, so does the fee for redeeming that asset.

This fee structure is designed to maintain a balanced composition in the pool and discourage monopolization by any single asset.

### Conclusion

The FeeCalculator contract uses these mathematical models to calculate fees for deposit and redemption operations. By understanding these functions, users can make informed decisions about their transactions to optimize their costs.

Remember, the goal is to maintain a balanced pool composition and discourage monopolization by any single asset.




## How to Use

1. Deploy the contract on a Polygon network.
2. Call `feeSetup` function to set up the fee distribution among recipients.
3. Call `calculateDepositFees` function to calculate the deposit fees for a given amount.
4. Call `calculateRedemptionFee` function to calculate the redemption fees for a given amount.

## Requirements

- Solidity ^0.8.13
- OpenZeppelin Contracts
- PRBMath

## License

This project is unlicensed.
10 changes: 10 additions & 0 deletions script/FeeCalculator.s.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
// SPDX-FileCopyrightText: 2023 Neutral Labs Inc.
//
// SPDX-License-Identifier: UNLICENSED

// If you encounter a vulnerability or an issue, please contact <info@neutralx.com>
pragma solidity ^0.8.13;

import {Script, console2} from "forge-std/Script.sol";

/// @title FeeCalculatorScript
/// @author Neutral Labs Inc.
/// @notice This contract is a script for fee calculation.
/// @dev It extends the Script contract from the forge-std library.
contract FeeCalculatorScript is Script {
/// @notice Sets up the contract.
function setUp() public {}

/// @notice Runs the script, broadcasting the result.
function run() public {
vm.broadcast();
}
Expand Down
55 changes: 55 additions & 0 deletions src/FeeCalculator.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
// SPDX-FileCopyrightText: 2023 Neutral Labs Inc.
//
// SPDX-License-Identifier: UNLICENSED

// If you encounter a vulnerability or an issue, please contact <info@neutralx.com>
pragma solidity ^0.8.13;

import "./interfaces/IDepositFeeCalculator.sol";
import "./interfaces/IRedemptionFeeCalculator.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SD59x18, sd, intoUint256} from "@prb/math/src/SD59x18.sol";

/// @title FeeCalculator
/// @author Neutral Labs Inc.
/// @notice This contract calculates deposit and redemption fees for a given pool.
/// @dev It implements IDepositFeeCalculator and IRedemptionFeeCalculator interfaces.
contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
SD59x18 private zero = sd(0);
SD59x18 private one = sd(1e18);
Expand All @@ -19,6 +28,9 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
address[] private _recipients;
uint256[] private _shares;

/// @notice Sets up the fee distribution among recipients.
/// @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 {
require(recipients.length == shares.length, "Recipients and shares arrays must have the same length");
require(recipients.length > 0, "Recipients and shares arrays must not be empty");
Expand All @@ -33,13 +45,23 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
_shares = shares;
}

/// @notice Calculates the deposit fees for a given amount.
/// @param tco2 The address of the TCO2 token.
/// @param pool The address of the pool.
/// @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) {
require(depositAmount > 0, "depositAmount must be > 0");

uint256 totalFee = getDepositFee(depositAmount, getTokenBalance(pool, tco2), getTotalSupply(pool));
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) {
feesDenominatedInPoolTokens = new uint256[](_recipients.length);

Expand All @@ -55,23 +77,41 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
feesDenominatedInPoolTokens[0] += restFee;//we give rest of the fee (if any) to the first recipient
}

/// @notice Calculates the redemption fees for a given amount.
/// @param tco2 The address of the TCO2 token.
/// @param pool The address of the pool.
/// @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) {
require(depositAmount > 0, "depositAmount must be > 0");

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

/// @notice Gets the balance of the TCO2 token in a given pool.
/// @param pool The address of the pool.
/// @param tco2 The address of the TCO2 token.
/// @return The balance of the TCO2 token in the pool.
function getTokenBalance(address pool, address tco2) private view returns (uint256) {
uint256 tokenBalance = IERC20(tco2).balanceOf(pool);
return tokenBalance;
}

/// @notice Gets the total supply of a given pool.
/// @param pool The address of the pool.
/// @return The total supply of the pool.
function getTotalSupply(address pool) private view returns (uint256) {
uint256 totalSupply = IERC20(pool).totalSupply();
return totalSupply;
}

/// @notice Calculates the ratios for deposit fee calculation.
/// @param amount The amount to be deposited.
/// @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)
{
SD59x18 a = total == zero ? zero : current / total;
Expand All @@ -80,6 +120,11 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
return (a, b);
}

/// @notice Calculates the ratios for redemption fee calculation.
/// @param amount The amount to be redeemed.
/// @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)
{
SD59x18 a = total == zero ? zero : current / total;
Expand All @@ -88,6 +133,11 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
return (a, b);
}

/// @notice Calculates the deposit fee for a given amount.
/// @param amount The amount to be deposited.
/// @param current The current balance of the pool.
/// @param total The total supply of the pool.
/// @return The calculated deposit fee.
function getDepositFee(uint256 amount, uint256 current, uint256 total) private view returns (uint256) {
require(total >= current);

Expand All @@ -110,6 +160,11 @@ contract FeeCalculator is IDepositFeeCalculator, IRedemptionFeeCalculator {
return fee;
}

/// @notice Calculates the redemption fee for a given amount.
/// @param amount The amount to be redeemed.
/// @param current The current balance of the pool.
/// @param total The total supply of the pool.
/// @return The calculated redemption fee.
function getRedemptionFee(uint256 amount, uint256 current, uint256 total) private view returns (uint256) {
require(total >= current);
require(amount <= current);
Expand Down
Loading

0 comments on commit 3d233d1

Please sign in to comment.