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:
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user