Last active
July 15, 2024 08:37
-
-
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!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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