ERC4626
The information provided by Aloe Labs, Inc. (“we,” “us” or “our”) on docs.aloe.capital (the “Site”) is for general informational purposes only. All information on the Site is provided in good faith, however we make no representation or warranty of any kind, express or implied, regarding the accuracy, adequacy, validity, reliability, availability or completeness of any information on the Site.
Under no circumstance shall we have any liability to you for any loss or damage of any kind incurred as a result of the use of the site or reliance on any information provided on the site. Your use of the site and your reliance on any information on the site is solely at your own risk.
The Lender
is ERC4626 compliant, with two exceptions: various view methods may revert upon reentrancy, and redeem
treats type(uint256).max
as a special case. Also, deposit
and redeem
are much more gas efficient than mint
and withdraw
.
See below for details.
asset
The address of the underlying token used for the Vault for accounting, depositing, and withdrawing.
MUST be an EIP-20 token contract.
MUST NOT revert
totalAssets
Total amount of the underlying asset that is “managed” by Vault.
SHOULD include any compounding that occurs from yield.
MUST be inclusive of any fees that are charged against assets in the Vault.
MUST NOT revert.
convertToShares
The amount of shares that the Vault would exchange for the amount of assets provided, in an ideal scenario where all the conditions are met.
MUST NOT be inclusive of any fees that are charged against assets in the Vault.
MUST NOT show any variations depending on the caller.
MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
MUST round down towards 0.
This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.
Notes
convertToShares(totalAssets()) != totalSupply()
because totalAssets()
includes compounding interest, while totalSupply()
is static. However, if you use stats
to get the updated total supply, the following will hold:
(, uint256 totalAssets, , uint256 totalSupply) = lender.stats();
assertApproxEqAbs(lender.convertToAssets(totalSupply), totalAssets, 1);
assertApproxEqAbs(lender.convertToShares(totalAssets), totalSupply, 1);
convertToAssets
The amount of assets that the Vault would exchange for the amount of shares provided, in an ideal scenario where all the conditions are met.
MUST NOT be inclusive of any fees that are charged against assets in the Vault.
MUST NOT show any variations depending on the caller.
MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange.
MUST NOT revert unless due to integer overflow caused by an unreasonably large input.
MUST round down towards 0.
This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and from.
Notes
convertToAssets(totalSupply()) != totalAssets()
because totalAssets()
includes compounding interest, while totalSupply()
is static. However, if you use stats
to get the updated total supply, the following will hold:
(, uint256 totalAssets, , uint256 totalSupply) = lender.stats();
assertApproxEqAbs(lender.convertToAssets(totalSupply), totalAssets, 1);
assertApproxEqAbs(lender.convertToShares(totalAssets), totalSupply, 1);
maxDeposit
Maximum amount of the underlying asset that can be deposited into the Vault for the receiver through a deposit call.
MUST return the maximum amount of assets
deposit
would allow to be deposited forreceiver
and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assume that the user has infinite assets, i.e. MUST NOT rely onbalanceOf
ofasset
.MUST factor in both global and user-specific limits, like if deposits are entirely disabled (even temporarily) it MUST return 0.
MUST return
2 ** 256 - 1
if there is no limit on the maximum amount of assets that may be deposited.MUST NOT revert.
Notes
The precise maximum would be on the order of 1 << 112
with weird constraints coming from both lastBalance
and totalSupply
, which changes during interest accrual. Instead of doing complicated math, we return a conservative estimate of 1 << 96
It is unclear whether the spec would prefer for us to return 2 ** 256 - 1
— the only constraints involved arise from math precision/overflow.
maxMint
Maximum amount of shares that can be minted from the Vault for the receiver through a mint call.
MUST return the maximum amount of shares
mint
would allow to be deposited toreceiver
and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary). This assumes that the user has infinite assets, i.e. MUST NOT rely onbalanceOf
ofasset
.MUST factor in both global and user-specific limits, like if mints are entirely disabled (even temporarily) it MUST return 0.
MUST return
2 ** 256 - 1
if there is no limit on the maximum amount of shares that may be minted.MUST NOT revert.
Notes
The precise maximum would be on the order of 1 << 112
with weird constraints coming from both lastBalance
and totalSupply
, which changes during interest accrual. Instead of doing complicated math, we return a conservative estimate of 1 << 96
It is unclear whether the spec would prefer for us to return 2 ** 256 - 1
— the only constraints involved arise from math precision/overflow.
maxWithdraw
Maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call.
MUST return the maximum amount of assets that could be transferred from
owner
throughwithdraw
and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary).MUST factor in both global and user-specific limits, like if withdrawals are entirely disabled (even temporarily) it MUST return 0.
MUST NOT revert.
Notes
Our implementation of maxWithdraw(owner)
simply returns convertToAssets(maxRedeem(owner))
.
maxRedeem
Maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a redeem call.
MUST return the maximum amount of shares that could be transferred from
owner
throughredeem
and not cause a revert, which MUST NOT be higher than the actual maximum that would be accepted (it should underestimate if necessary).MUST factor in both global and user-specific limits, like if redemption is entirely disabled (even temporarily) it MUST return 0.
MUST NOT revert.
previewDeposit
Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.
MUST return as close to and no more than the exact amount of Vault shares that would be minted in a
deposit
call in the same transaction. i.e.deposit
should return the same or moreshares
aspreviewDeposit
if called in the same transaction.MUST NOT account for deposit limits like those returned from
maxDeposit
and should always act as though the deposit would be accepted, regardless if the user has enough tokens approved, etc.MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause
deposit
to revert.
Note that any unfavorable discrepancy between convertToShares
and previewDeposit
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing.
Notes
Our implementation of previewDeposit(assets)
simply returns convertToShares(assets)
, as we have no deposit fees or slippage.
previewMint
Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.
MUST return as close to and no more than the exact amount of assets that would be deposited in a
mint
call in the same transaction. i.e.mint
should return the same or fewerassets
aspreviewMint
if called in the same transaction.MUST NOT account for mint limits like those returned from
maxMint
and should always act as though the mint would be accepted, regardless if the user has enough tokens approved, etc.MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees.
MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause
mint
to revert.
Note that any unfavorable discrepancy between convertToAssets
and previewMint
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by minting.
Notes
Our implementation of previewMint(assets)
simply returns convertToAssets(shares)
(rounding up), as we have no deposit fees or slippage.
previewWithdraw
Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.
MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a
withdraw
call in the same transaction. i.e.withdraw
should return the same or fewershares
aspreviewWithdraw
if called in the same transaction.MUST NOT account for withdrawal limits like those returned from
maxWithdraw
and should always act as though the withdrawal would be accepted, regardless if the user has enough shares, etc.MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause
withdraw
to revert.
Note that any unfavorable discrepancy between convertToShares
and previewWithdraw
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by depositing.
Notes
Our implementation of previewWithdraw(assets)
simply returns convertToShares(assets)
(rounding up).
We do have withdrawal fees, but they’re user-specific, and without having owner
as an argument, it’s impossible to account for them here. Instead, as a given user’s owed fees grow, the value returned by maxWithdraw(owner)
will shrink. In other words, though balanceOf(owner)
stays the same, their withdraw-able balance shrinks over time.
Since we “MUST NOT account for withdrawal limits” in previewWithdraw
, and since our previewWithdraw
function is accurate and precise for all shares that are actually withdraw-able, we argue that our implementation meets the spec — even though we don’t directly account for withdrawal fees.
Finally, our implementation does return “the exact amount of Vault shares that would be burned” because the fee shares are transferred not burned.
previewRedeem
Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block, given current on-chain conditions.
MUST return as close to and no more than the exact amount of
assets
that would be withdrawn in aredeem
call in the same transaction. i.e.redeem
should return the same or moreassets
aspreviewRedeem
if called in the same transaction.MUST NOT account for redemption limits like those returned from
maxRedeem
and should always act as though the redemption would be accepted, regardless if the user has enough shares, etc.MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees.
MUST NOT revert due to vault specific user/global limits. MAY revert due to other conditions that would also cause
redeem
to revert.
Note that any unfavorable discrepancy between convertToAssets
and previewRedeem
SHOULD be considered slippage in share price or some other type of condition, meaning the depositor will lose assets by redeeming.
Notes
Our implementation of previewRedeem(shares)
simply returns convertToAssets(shares)
.
We do have withdrawal fees, but they’re user-specific, and without having owner
as an argument, it’s impossible to account for them here. Instead, as a given user’s owed fees grow, the value returned by maxRedeem(owner)
will shrink. In other words, though balanceOf(owner)
stays the same, their redeemable balance shrinks over time.
Since we “MUST NOT account for redemption limits” in previewRedeem
, and since our previewRedeem
function is accurate and precise for all shares that are actually redeemable, we argue that our implementation meets the spec — even though we don’t directly account for withdrawal fees.
deposit
Mints shares
Vault shares to receiver
by depositing exactly assets
of underlying tokens.
MUST emit the
Deposit
eventMUST support EIP-20
approve
/transferFrom
on asset as a deposit flow. MAY support an additional flow in which the underlying tokens are owned by the Vault contract before thedeposit
execution, and are accounted for duringdeposit
.MUST revert if all of
assets
cannot be deposited (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc).
Notes
deposit
is much more efficient than mint
in our application, and is the recommended way of depositing. Our application does support the additional flow in which underlying tokens are owned by the Vault before the deposit
function, enabling straightforward Permit/Permit2 integration in the periphery.
mint
Mints exactly shares
Vault shares to receiver
by depositing assets
of underlying tokens.
MUST emit the
Deposit
event.MUST support EIP-20
approve
/transferFrom
on asset as a mint flow. MAY support an additional flow in which the underlying tokens are owned by the Vault contract before themint
execution, and are accounted for duringmint
.MUST revert if all of
shares
cannot be minted (due to deposit limit being reached, slippage, the user not approving enough underlying tokens to the Vault contract, etc).
Notes
Our implementation of mint
simply converts shares
to assets
and calls deposit
. It is less efficient and not recommended.
withdraw
Burns shares
from owner and sends exactly assets
of underlying tokens to receiver
.
MUST emit the
Withdraw
event.MUST support a withdraw flow where the shares are burned from
owner
directly whereowner
ismsg.sender
.MUST support a withdraw flow where the shares are burned from
owner
directly wheremsg.sender
has EIP-20 approval over the shares ofowner
.SHOULD check
msg.sender
can spend owner funds, assets needs to be converted to shares and shares should be checked for allowance.MUST revert if all of
assets
cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc).
Notes
Our implementation of withdraw
simply converts amount
to shares
and calls redeem
. It is less efficient and not recommended. It does not support the additional flow in which shares are transferred before the call to withdraw
, and you do not have to pre-request before withdrawing.
redeem
Burns exactly shares
from owner
and sends assets
of underlying tokens to receiver
.
MUST emit the
Withdraw
event.MUST support a redeem flow where the shares are burned from
owner
directly whereowner
ismsg.sender
.MUST support a redeem flow where the shares are burned from
owner
directly wheremsg.sender
has EIP-20 approval over the shares ofowner
.SHOULD check
msg.sender
can spend owner funds using allowance.MUST revert if all of
shares
cannot be redeemed (due to withdrawal limit being reached, slippage, the owner not having enough shares, etc).
Notes
If shares
is set to type(uint256).max
, the contract will attempt to redeem maxRedeem(owner)
shares. This uses extra gas, and is only necessary if maxRedeem(owner)
is changing over time, e.g. if owner
has a courier or if high utilization is constraining withdraw-able amounts. In all other cases, prefer getting maxRedeem(owner)
off-chain.
Last updated