Skip to content

Instantly share code, notes, and snippets.

@pcaversaccio
Last active July 15, 2024 08:37
Show Gist options
  • Save pcaversaccio/665c7a2bc826ebfc1ea626f4bc5d8c48 to your computer and use it in GitHub Desktop.
Save pcaversaccio/665c7a2bc826ebfc1ea626f4bc5d8c48 to your computer and use it in GitHub Desktop.
An illustrative `erc4626_fees` module reference implementation. I have coded this in ~30mins. It's completely untested code, so please be careful!
# pragma version ~=0.4.0rc5
"""
@title `erc4626_fees` Module Reference Implementation
@custom:contract-name erc4626_fees_mock
@license GNU Affero General Public License v3.0 only
@author pcaversaccio
@custom:security I have coded this in ~30mins. It's completely
untested code, so please be careful!
"""
from ethereum.ercs import IERC20
from ethereum.ercs import IERC20Detailed
from ethereum.ercs import IERC4626
# As soon as modules are live, you can configure
# `from snekmate.tokens.interfaces import IERC20Permit`
# (same for `IERC5267`) here.
from ...tokens.interfaces import IERC20Permit
from ...utils.interfaces import IERC5267
implements: IERC20
implements: IERC20Detailed
implements: IERC20Permit
implements: IERC4626
implements: IERC5267
# @dev We import and initialise the `erc4626` module.
# As soon as modules are live, you can configure
# `from snekmate.extensions import erc4626` here.
from .. import erc4626
initializes: erc4626
# @dev We export (i.e. the runtime bytecode exposes these
# functions externally, allowing them to be called using
# the ABI encoding specification) all `external` functions
# from the `erc4626` module that we don't rewrite here.
exports: (
# Export the `IERC20` functions.
erc4626.totalSupply,
erc4626.balanceOf,
erc4626.transfer,
erc4626.transferFrom,
erc4626.approve,
erc4626.allowance,
# Export the `IERC20Detailed` functions.
erc4626.name,
erc4626.symbol,
erc4626.decimals,
# Export the `IERC20Permit` functions.
erc4626.permit,
erc4626.nonces,
erc4626.DOMAIN_SEPARATOR,
# Export the `IERC5267` function.
erc4626.eip712Domain,
# Export the unmodified `IERC4626` functions.
erc4626.asset,
erc4626.totalAssets,
erc4626.convertToShares,
erc4626.convertToAssets,
erc4626.maxDeposit,
erc4626.maxMint,
erc4626.maxWithdraw,
erc4626.maxRedeem,
)
_BASIS_POINT_SCALE: constant(uint256) = 10_000
_ENTRY_FEE_BASIS_POINT_VALUE: immutable(uint256)
_ENTRY_FEE_RECIPIENT_VALUE: immutable(address)
_EXIT_FEE_BASIS_POINT_VALUE: immutable(uint256)
_EXIT_FEE_RECIPIENT_VALUE: immutable(address)
@deploy
@payable
def __init__(name_: String[25], symbol_: String[5], asset_: IERC20, decimals_offset_: uint8, name_eip712_: String[50], version_eip712_: String[20],
entry_fee_basis_point_value_: uint256, entry_fee_recipient_value_: address, exit_fee_basis_point_value_: uint256, exit_fee_recipient_value_: address):
_ENTRY_FEE_BASIS_POINT_VALUE = entry_fee_basis_point_value_
_ENTRY_FEE_RECIPIENT_VALUE = entry_fee_recipient_value_
_EXIT_FEE_BASIS_POINT_VALUE = exit_fee_basis_point_value_
_EXIT_FEE_RECIPIENT_VALUE = exit_fee_recipient_value_
erc4626.__init__(name_, symbol_, asset_, decimals_offset_, name_eip712_, version_eip712_)
#############
# OVERRIDES #
#############
@external
@view
def previewDeposit(assets: uint256) -> uint256:
fee: uint256 = self._fee_on_total(assets, _ENTRY_FEE_BASIS_POINT_VALUE)
return erc4626._preview_deposit(assets - fee)
@external
@view
def previewMint(shares: uint256) -> uint256:
assets: uint256 = erc4626._preview_mint(shares)
return (assets + self._fee_on_raw(assets, _ENTRY_FEE_BASIS_POINT_VALUE))
@external
@view
def previewWithdraw(assets: uint256) -> uint256:
fee: uint256 = self._fee_on_raw(assets, _EXIT_FEE_BASIS_POINT_VALUE)
return erc4626._preview_withdraw(assets + fee)
@external
@view
def previewRedeem(shares: uint256) -> uint256:
assets: uint256 = erc4626._preview_redeem(shares)
return (assets - self._fee_on_total(assets, _EXIT_FEE_BASIS_POINT_VALUE))
@external
def deposit(assets: uint256, receiver: address) -> uint256:
assert assets <= erc4626._max_deposit(receiver), "erc4626_fees: deposit more than maximum"
shares: uint256 = erc4626._preview_deposit(assets)
self._deposit_with_fees(msg.sender, receiver, assets, shares)
return shares
@external
def mint(shares: uint256, receiver:address) -> uint256:
assert shares <= erc4626._max_mint(receiver), "erc4626_fees: mint more than maximum"
assets: uint256 = erc4626._preview_mint(shares)
self._deposit_with_fees(msg.sender, receiver, assets, shares)
return assets
@external
def withdraw(assets: uint256, receiver: address, owner: address) -> uint256:
assert assets <= erc4626._max_withdraw(receiver), "erc4626_fees: withdraw more than maximum"
shares: uint256 = erc4626._preview_withdraw(assets)
self._withdraw_with_fees(msg.sender, receiver, owner, assets, shares)
return shares
@external
def redeem(shares: uint256, receiver: address, owner: address) -> uint256:
assert shares <= erc4626._max_redeem(owner), "erc4626_fees: redeem more than maximum"
assets: uint256 = erc4626._preview_redeem(shares)
self._withdraw_with_fees(msg.sender, receiver, owner, assets, shares)
return assets
##################
# FEE OPERATIONS #
##################
@internal
def _deposit_with_fees(sender: address, receiver: address, assets: uint256, shares: uint256):
fee: uint256 = self._fee_on_total(assets, _ENTRY_FEE_BASIS_POINT_VALUE)
erc4626._deposit(sender, receiver, assets, shares)
if ((fee != empty(uint256)) and (_ENTRY_FEE_RECIPIENT_VALUE != empty(address))):
assert extcall erc4626._ASSET.transfer(_ENTRY_FEE_RECIPIENT_VALUE, fee, default_return_value=True), "erc4626_fees: transfer operation did not succeed"
@internal
def _withdraw_with_fees(sender: address, receiver: address, owner: address, assets: uint256, shares: uint256):
fee: uint256 = self._fee_on_raw(assets, _EXIT_FEE_BASIS_POINT_VALUE)
erc4626._withdraw(sender, receiver, owner, assets, shares)
if ((fee != empty(uint256)) and (_EXIT_FEE_RECIPIENT_VALUE != empty(address))):
assert extcall erc4626._ASSET.transfer(_EXIT_FEE_RECIPIENT_VALUE, fee, default_return_value=True), "erc4626_fees: transfer operation did not succeed"
@internal
@pure
def _fee_on_raw(assets: uint256, fee_basis_points: uint256) -> uint256:
return erc4626.math._mul_div(assets, fee_basis_points, _BASIS_POINT_SCALE, True)
@internal
@pure
def _fee_on_total(assets: uint256, fee_basis_points: uint256) -> uint256:
return erc4626.math._mul_div(assets, fee_basis_points, fee_basis_points + _BASIS_POINT_SCALE, True)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment