ERC4626Fees.sol 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. import {IERC20} from "../../token/ERC20/IERC20.sol";
  4. import {ERC4626} from "../../token/ERC20/extensions/ERC4626.sol";
  5. import {SafeERC20} from "../../token/ERC20/utils/SafeERC20.sol";
  6. import {Math} from "../../utils/math/Math.sol";
  7. /// @dev ERC-4626 vault with entry/exit fees expressed in https://en.wikipedia.org/wiki/Basis_point[basis point (bp)].
  8. ///
  9. /// NOTE: The contract charges fees in terms of assets, not shares. This means that the fees are calculated based on the
  10. /// amount of assets that are being deposited or withdrawn, and not based on the amount of shares that are being minted or
  11. /// redeemed. This is an opinionated design decision that should be taken into account when integrating this contract.
  12. ///
  13. /// WARNING: This contract has not been audited and shouldn't be considered production ready. Consider using it with caution.
  14. abstract contract ERC4626Fees is ERC4626 {
  15. using Math for uint256;
  16. uint256 private constant _BASIS_POINT_SCALE = 1e4;
  17. // === Overrides ===
  18. /// @dev Preview taking an entry fee on deposit. See {IERC4626-previewDeposit}.
  19. function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
  20. uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints());
  21. return super.previewDeposit(assets - fee);
  22. }
  23. /// @dev Preview adding an entry fee on mint. See {IERC4626-previewMint}.
  24. function previewMint(uint256 shares) public view virtual override returns (uint256) {
  25. uint256 assets = super.previewMint(shares);
  26. return assets + _feeOnRaw(assets, _entryFeeBasisPoints());
  27. }
  28. /// @dev Preview adding an exit fee on withdraw. See {IERC4626-previewWithdraw}.
  29. function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
  30. uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints());
  31. return super.previewWithdraw(assets + fee);
  32. }
  33. /// @dev Preview taking an exit fee on redeem. See {IERC4626-previewRedeem}.
  34. function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
  35. uint256 assets = super.previewRedeem(shares);
  36. return assets - _feeOnTotal(assets, _exitFeeBasisPoints());
  37. }
  38. /// @dev Send entry fee to {_entryFeeRecipient}. See {IERC4626-_deposit}.
  39. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override {
  40. uint256 fee = _feeOnTotal(assets, _entryFeeBasisPoints());
  41. address recipient = _entryFeeRecipient();
  42. super._deposit(caller, receiver, assets, shares);
  43. if (fee > 0 && recipient != address(this)) {
  44. SafeERC20.safeTransfer(IERC20(asset()), recipient, fee);
  45. }
  46. }
  47. /// @dev Send exit fee to {_exitFeeRecipient}. See {IERC4626-_deposit}.
  48. function _withdraw(
  49. address caller,
  50. address receiver,
  51. address owner,
  52. uint256 assets,
  53. uint256 shares
  54. ) internal virtual override {
  55. uint256 fee = _feeOnRaw(assets, _exitFeeBasisPoints());
  56. address recipient = _exitFeeRecipient();
  57. super._withdraw(caller, receiver, owner, assets, shares);
  58. if (fee > 0 && recipient != address(this)) {
  59. SafeERC20.safeTransfer(IERC20(asset()), recipient, fee);
  60. }
  61. }
  62. // === Fee configuration ===
  63. function _entryFeeBasisPoints() internal view virtual returns (uint256) {
  64. return 0; // replace with e.g. 100 for 1%
  65. }
  66. function _exitFeeBasisPoints() internal view virtual returns (uint256) {
  67. return 0; // replace with e.g. 100 for 1%
  68. }
  69. function _entryFeeRecipient() internal view virtual returns (address) {
  70. return address(0); // replace with e.g. a treasury address
  71. }
  72. function _exitFeeRecipient() internal view virtual returns (address) {
  73. return address(0); // replace with e.g. a treasury address
  74. }
  75. // === Fee operations ===
  76. /// @dev Calculates the fees that should be added to an amount `assets` that does not already include fees.
  77. /// Used in {IERC4626-mint} and {IERC4626-withdraw} operations.
  78. function _feeOnRaw(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
  79. return assets.mulDiv(feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Ceil);
  80. }
  81. /// @dev Calculates the fee part of an amount `assets` that already includes fees.
  82. /// Used in {IERC4626-deposit} and {IERC4626-redeem} operations.
  83. function _feeOnTotal(uint256 assets, uint256 feeBasisPoints) private pure returns (uint256) {
  84. return assets.mulDiv(feeBasisPoints, feeBasisPoints + _BASIS_POINT_SCALE, Math.Rounding.Ceil);
  85. }
  86. }