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
depositwould allow to be deposited forreceiverand 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 onbalanceOfofasset.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 - 1if 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
mintwould allow to be deposited toreceiverand 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 onbalanceOfofasset.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 - 1if 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
ownerthroughwithdrawand 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
ownerthroughredeemand 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
depositcall in the same transaction. i.e.depositshould return the same or moresharesaspreviewDepositif called in the same transaction.MUST NOT account for deposit limits like those returned from
maxDepositand 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
depositto 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
mintcall in the same transaction. i.e.mintshould return the same or fewerassetsaspreviewMintif called in the same transaction.MUST NOT account for mint limits like those returned from
maxMintand 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
mintto 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
withdrawcall in the same transaction. i.e.withdrawshould return the same or fewersharesaspreviewWithdrawif called in the same transaction.MUST NOT account for withdrawal limits like those returned from
maxWithdrawand 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
withdrawto 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
assetsthat would be withdrawn in aredeemcall in the same transaction. i.e.redeemshould return the same or moreassetsaspreviewRedeemif called in the same transaction.MUST NOT account for redemption limits like those returned from
maxRedeemand 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
redeemto 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
DepositeventMUST support EIP-20
approve/transferFromon asset as a deposit flow. MAY support an additional flow in which the underlying tokens are owned by the Vault contract before thedepositexecution, and are accounted for duringdeposit.MUST revert if all of
assetscannot 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
Depositevent.MUST support EIP-20
approve/transferFromon asset as a mint flow. MAY support an additional flow in which the underlying tokens are owned by the Vault contract before themintexecution, and are accounted for duringmint.MUST revert if all of
sharescannot 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
Withdrawevent.MUST support a withdraw flow where the shares are burned from
ownerdirectly whereownerismsg.sender.MUST support a withdraw flow where the shares are burned from
ownerdirectly wheremsg.senderhas EIP-20 approval over the shares ofowner.SHOULD check
msg.sendercan spend owner funds, assets needs to be converted to shares and shares should be checked for allowance.MUST revert if all of
assetscannot 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
Withdrawevent.MUST support a redeem flow where the shares are burned from
ownerdirectly whereownerismsg.sender.MUST support a redeem flow where the shares are burned from
ownerdirectly wheremsg.senderhas EIP-20 approval over the shares ofowner.SHOULD check
msg.sendercan spend owner funds using allowance.MUST revert if all of
sharescannot 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