# 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.

<details>

<summary><code>asset</code></summary>

<mark style="color:green;">The address of the underlying token used for the Vault for accounting, depositing, and withdrawing.</mark>

* MUST be an EIP-20 token contract.
* MUST NOT revert

- [x] Compliant

</details>

<details>

<summary><code>totalAssets</code></summary>

<mark style="color:green;">Total amount of the underlying asset that is “managed” by Vault.</mark>

* 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.

- [x] Compliant

</details>

<details>

<summary><code>convertToShares</code></summary>

<mark style="color:green;">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.</mark>

* 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.

* [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

`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:

```solidity
(, uint256 totalAssets, , uint256 totalSupply) = lender.stats();
assertApproxEqAbs(lender.convertToAssets(totalSupply), totalAssets, 1);
assertApproxEqAbs(lender.convertToShares(totalAssets), totalSupply, 1);
```

</details>

<details>

<summary><code>convertToAssets</code></summary>

<mark style="color:green;">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.</mark>

* 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.

* [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

`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:

```solidity
(, uint256 totalAssets, , uint256 totalSupply) = lender.stats();
assertApproxEqAbs(lender.convertToAssets(totalSupply), totalAssets, 1);
assertApproxEqAbs(lender.convertToShares(totalAssets), totalSupply, 1);
```

</details>

<details>

<summary><code>maxDeposit</code></summary>

<mark style="color:green;">Maximum amount of the underlying asset that can be deposited into the Vault for the receiver through a deposit call.</mark>

* MUST return the maximum amount of assets `deposit` would allow to be deposited for `receiver` 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 on `balanceOf` of `asset`.
* 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.

- [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

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.

</details>

<details>

<summary><code>maxMint</code></summary>

<mark style="color:green;">Maximum amount of shares that can be minted from the Vault for the receiver through a mint call.</mark>

* MUST return the maximum amount of shares `mint` would allow to be deposited to `receiver` 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 on `balanceOf` of `asset`.
* 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.

- [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

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.

</details>

<details>

<summary><code>maxWithdraw</code></summary>

<mark style="color:green;">Maximum amount of the underlying asset that can be withdrawn from the owner balance in the Vault, through a withdraw call.</mark>

* MUST return the maximum amount of assets that could be transferred from `owner` through `withdraw` 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.

- [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

Our implementation of `maxWithdraw(owner)` simply returns `convertToAssets(maxRedeem(owner))`.

</details>

<details>

<summary><code>maxRedeem</code></summary>

<mark style="color:green;">Maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, through a redeem call.</mark>

* MUST return the maximum amount of shares that could be transferred from `owner` through `redeem` 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.

- [x] Compliant

</details>

<details>

<summary>previewDeposit</summary>

<mark style="color:green;">Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given current on-chain conditions.</mark>

* 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 more `shares` as `previewDeposit` 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.

* [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

Our implementation of `previewDeposit(assets)` simply returns `convertToShares(assets)`, as we have no deposit fees or slippage.

</details>

<details>

<summary>previewMint</summary>

<mark style="color:green;">Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given current on-chain conditions.</mark>

* 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 fewer `assets` as `previewMint` 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.

* [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

Our implementation of `previewMint(assets)` simply returns `convertToAssets(shares)` (rounding **up**), as we have no deposit fees or slippage.

</details>

<details>

<summary>previewWithdraw</summary>

<mark style="color:green;">Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, given current on-chain conditions.</mark>

* 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 fewer `shares` as `previewWithdraw` 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.

* [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

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*.

</details>

<details>

<summary>previewRedeem</summary>

<mark style="color:green;">Allows an on-chain or off-chain user to simulate the effects of their redemption at the current block, given current on-chain conditions.</mark>

* MUST return as close to and no more than the exact amount of `assets` that would be withdrawn in a `redeem` call in the same transaction. i.e. `redeem` should return the same or more `assets` as `previewRedeem` 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.

* [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

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.

</details>

<details>

<summary>deposit</summary>

<mark style="color:green;">Mints</mark> <mark style="color:green;"></mark><mark style="color:green;">`shares`</mark> <mark style="color:green;"></mark><mark style="color:green;">Vault shares to</mark> <mark style="color:green;"></mark><mark style="color:green;">`receiver`</mark> <mark style="color:green;"></mark><mark style="color:green;">by depositing exactly</mark> <mark style="color:green;"></mark><mark style="color:green;">`assets`</mark> <mark style="color:green;"></mark><mark style="color:green;">of underlying tokens.</mark>

* MUST emit the `Deposit` event
* MUST 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 the `deposit` execution, and are accounted for during `deposit`.
* 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).

- [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

`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.

</details>

<details>

<summary>mint</summary>

<mark style="color:green;">Mints exactly</mark> <mark style="color:green;"></mark><mark style="color:green;">`shares`</mark> <mark style="color:green;"></mark><mark style="color:green;">Vault shares to</mark> <mark style="color:green;"></mark><mark style="color:green;">`receiver`</mark> <mark style="color:green;"></mark><mark style="color:green;">by depositing</mark> <mark style="color:green;"></mark><mark style="color:green;">`assets`</mark> <mark style="color:green;"></mark><mark style="color:green;">of underlying tokens.</mark>

* 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 the `mint` execution, and are accounted for during `mint`.
* 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).

- [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

Our implementation of `mint` simply converts `shares` to `assets` and calls `deposit`. It is less efficient and not recommended.

</details>

<details>

<summary>withdraw</summary>

<mark style="color:green;">Burns</mark> <mark style="color:green;"></mark><mark style="color:green;">`shares`</mark> <mark style="color:green;"></mark><mark style="color:green;">from owner and sends exactly</mark> <mark style="color:green;"></mark><mark style="color:green;">`assets`</mark> <mark style="color:green;"></mark><mark style="color:green;">of underlying tokens to</mark> <mark style="color:green;"></mark><mark style="color:green;">`receiver`</mark><mark style="color:green;">.</mark>

* MUST emit the `Withdraw` event.
* MUST support a withdraw flow where the shares are burned from `owner` directly where `owner` is `msg.sender`.
* MUST support a withdraw flow where the shares are burned from `owner` directly where `msg.sender` has EIP-20 approval over the shares of `owner`.
* 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).

- [x] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

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.

</details>

<details>

<summary>redeem</summary>

<mark style="color:green;">Burns exactly</mark> <mark style="color:green;"></mark><mark style="color:green;">`shares`</mark> <mark style="color:green;"></mark><mark style="color:green;">from</mark> <mark style="color:green;"></mark><mark style="color:green;">`owner`</mark> <mark style="color:green;"></mark><mark style="color:green;">and sends</mark> <mark style="color:green;"></mark><mark style="color:green;">`assets`</mark> <mark style="color:green;"></mark><mark style="color:green;">of underlying tokens to</mark> <mark style="color:green;"></mark><mark style="color:green;">`receiver`</mark><mark style="color:green;">.</mark>

* MUST emit the `Withdraw` event.
* MUST support a redeem flow where the shares are burned from `owner` directly where `owner` is `msg.sender`.
* MUST support a redeem flow where the shares are burned from `owner` directly where `msg.sender` has EIP-20 approval over the shares of `owner`.
* 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).

- [ ] Compliant

***

<mark style="color:yellow;">**Notes**</mark>

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.

</details>
