Improve ERC4626 fees example (#4476)

Co-authored-by: Francisco <fg@frang.io>
Co-authored-by: Hadrien Croubois <hadrien.croubois@gmail.com>
This commit is contained in:
Ernesto García
2023-07-28 19:16:14 -06:00
committed by GitHub
parent aed5720a01
commit f631d8a5f0
3 changed files with 50 additions and 37 deletions

View File

@ -7,36 +7,41 @@ import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol";
import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol"; import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol";
import {Math} from "../../utils/math/Math.sol"; import {Math} from "../../utils/math/Math.sol";
/// @dev ERC4626 vault with entry/exit fees expressed in https://en.wikipedia.org/wiki/Basis_point[basis point (bp)].
abstract contract ERC4626Fees is ERC4626 { abstract contract ERC4626Fees is ERC4626 {
using Math for uint256; using Math for uint256;
/** @dev See {IERC4626-previewDeposit}. */ uint256 private constant _BASIS_POINT_SCALE = 1e4;
// === Overrides === ///
/// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}.
function previewDeposit(uint256 assets) public view virtual override returns (uint256) { function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
uint256 fee = _feeOnTotal(assets, _entryFeeBasePoint()); uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints());
return super.previewDeposit(assets - fee); return super.previewDeposit(assets - fee);
} }
/** @dev See {IERC4626-previewMint}. */ /// @dev Preview adding an entry fee on mint. See {IERC4626-previewMint}.
function previewMint(uint256 shares) public view virtual override returns (uint256) { function previewMint(uint256 shares) public view virtual override returns (uint256) {
uint256 assets = super.previewMint(shares); uint256 assets = super.previewMint(shares);
return assets + _feeOnRaw(assets, _entryFeeBasePoint()); return assets + _feeOnRaw(assets, _entryFeeBasisPoints());
} }
/** @dev See {IERC4626-previewWithdraw}. */ /// @dev Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}.
function previewWithdraw(uint256 assets) public view virtual override returns (uint256) { function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
uint256 fee = _feeOnRaw(assets, _exitFeeBasePoint()); uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints());
return super.previewWithdraw(assets + fee); return super.previewWithdraw(assets + fee);
} }
/** @dev See {IERC4626-previewRedeem}. */ /// @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}.
function previewRedeem(uint256 shares) public view virtual override returns (uint256) { function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
uint256 assets = super.previewRedeem(shares); uint256 assets = super.previewRedeem(shares);
return assets - _feeOnTotal(assets, _exitFeeBasePoint()); return assets - _feeOnTotal(assets, _exitFeeBasisPoints());
} }
/** @dev See {IERC4626-_deposit}. */ /// @dev Send entry fee to {_entryFeeRecipient}. See {IERC4626-_deposit}.
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override {
uint256 fee = _feeOnTotal(assets, _entryFeeBasePoint()); uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints());
address recipient = _entryFeeRecipient(); address recipient = _entryFeeRecipient();
super._deposit(caller, receiver, assets, shares); super._deposit(caller, receiver, assets, shares);
@ -46,7 +51,7 @@ abstract contract ERC4626Fees is ERC4626 {
} }
} }
/** @dev See {IERC4626-_deposit}. */ /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}.
function _withdraw( function _withdraw(
address caller, address caller,
address receiver, address receiver,
@ -54,7 +59,7 @@ abstract contract ERC4626Fees is ERC4626 {
uint256 assets, uint256 assets,
uint256 shares uint256 shares
) internal virtual override { ) internal virtual override {
uint256 fee = _feeOnRaw(assets, _exitFeeBasePoint()); uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints());
address recipient = _exitFeeRecipient(); address recipient = _exitFeeRecipient();
super._withdraw(caller, receiver, owner, assets, shares); super._withdraw(caller, receiver, owner, assets, shares);
@ -64,27 +69,35 @@ abstract contract ERC4626Fees is ERC4626 {
} }
} }
function _entryFeeBasePoint() internal view virtual returns (uint256) { // === Fee configuration === ///
return 0;
function _entryFeeBasisPoints() internal view virtual returns (uint256) {
return 0; // replace with e.g. 1_000 for 1%
}
function _exitFeeBasisPoints() internal view virtual returns (uint256) {
return 0; // replace with e.g. 1_000 for 1%
} }
function _entryFeeRecipient() internal view virtual returns (address) { function _entryFeeRecipient() internal view virtual returns (address) {
return address(0); return address(0); // replace with e.g. a treasury address
}
function _exitFeeBasePoint() internal view virtual returns (uint256) {
return 0;
} }
function _exitFeeRecipient() internal view virtual returns (address) { function _exitFeeRecipient() internal view virtual returns (address) {
return address(0); return address(0); // replace with e.g. a treasury address
} }
function _feeOnRaw(uint256 assets, uint256 feeBasePoint) private pure returns (uint256) { // === Fee operations === ///
return assets.mulDiv(feeBasePoint, 1e5, Math.Rounding.Ceil);
/// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees.
/// Used in {IERC4626-mint} and {IERC4626-withdraw} operations.
function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil);
} }
function _feeOnTotal(uint256 assets, uint256 feeBasePoint) private pure returns (uint256) { /// @dev Calculates the fee part of an amount `assets` that already includes fees.
return assets.mulDiv(feeBasePoint, feeBasePoint + 1e5, Math.Rounding.Ceil); /// Used in {IERC4626-deposit} and {IERC4626-redeem} operations.
function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil);
} }
} }

View File

@ -5,33 +5,33 @@ pragma solidity ^0.8.19;
import {ERC4626Fees} from "../docs/ERC4626Fees.sol"; import {ERC4626Fees} from "../docs/ERC4626Fees.sol";
abstract contract ERC4626FeesMock is ERC4626Fees { abstract contract ERC4626FeesMock is ERC4626Fees {
uint256 private immutable _entryFeeBasePointValue; uint256 private immutable _entryFeeBasisPointValue;
address private immutable _entryFeeRecipientValue; address private immutable _entryFeeRecipientValue;
uint256 private immutable _exitFeeBasePointValue; uint256 private immutable _exitFeeBasisPointValue;
address private immutable _exitFeeRecipientValue; address private immutable _exitFeeRecipientValue;
constructor( constructor(
uint256 entryFeeBasePoint, uint256 entryFeeBasisPoints,
address entryFeeRecipient, address entryFeeRecipient,
uint256 exitFeeBasePoint, uint256 exitFeeBasisPoints,
address exitFeeRecipient address exitFeeRecipient
) { ) {
_entryFeeBasePointValue = entryFeeBasePoint; _entryFeeBasisPointValue = entryFeeBasisPoints;
_entryFeeRecipientValue = entryFeeRecipient; _entryFeeRecipientValue = entryFeeRecipient;
_exitFeeBasePointValue = exitFeeBasePoint; _exitFeeBasisPointValue = exitFeeBasisPoints;
_exitFeeRecipientValue = exitFeeRecipient; _exitFeeRecipientValue = exitFeeRecipient;
} }
function _entryFeeBasePoint() internal view virtual override returns (uint256) { function _entryFeeBasisPoints() internal view virtual override returns (uint256) {
return _entryFeeBasePointValue; return _entryFeeBasisPointValue;
} }
function _entryFeeRecipient() internal view virtual override returns (address) { function _entryFeeRecipient() internal view virtual override returns (address) {
return _entryFeeRecipientValue; return _entryFeeRecipientValue;
} }
function _exitFeeBasePoint() internal view virtual override returns (uint256) { function _exitFeeBasisPoints() internal view virtual override returns (uint256) {
return _exitFeeBasePointValue; return _exitFeeBasisPointValue;
} }
function _exitFeeRecipient() internal view virtual override returns (address) { function _exitFeeRecipient() internal view virtual override returns (address) {

View File

@ -737,9 +737,9 @@ contract('ERC4626', function (accounts) {
} }
describe('ERC4626Fees', function () { describe('ERC4626Fees', function () {
const feeBasePoint = web3.utils.toBN(5e3); const feeBasisPoints = web3.utils.toBN(5e3);
const valueWithoutFees = web3.utils.toBN(10000); const valueWithoutFees = web3.utils.toBN(10000);
const fees = valueWithoutFees.mul(feeBasePoint).divn(1e5); const fees = valueWithoutFees.mul(feeBasisPoints).divn(1e4);
const valueWithFees = valueWithoutFees.add(fees); const valueWithFees = valueWithoutFees.add(fees);
describe('input fees', function () { describe('input fees', function () {
@ -749,7 +749,7 @@ contract('ERC4626', function (accounts) {
name + ' Vault', name + ' Vault',
symbol + 'V', symbol + 'V',
this.token.address, this.token.address,
feeBasePoint, feeBasisPoints,
other, other,
0, 0,
constants.ZERO_ADDRESS, constants.ZERO_ADDRESS,