Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ERC4626Upgradeable

Inherits: Initializable, ERC20Upgradeable, IERC4626

*Implementation of the ERC-4626 "Tokenized Vault Standard" as defined in https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. This extension allows the minting and burning of "shares" (represented using the ERC-20 inheritance) in exchange for underlying "assets" through standardized deposit, {mint}, {redeem} and {burn} workflows. This contract extends the ERC-20 standard. Any additional extensions included along it would affect the "shares" token represented by this contract and not the "assets" token which is an independent contract. [CAUTION]

In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by verifying the amount received is as expected, using a wrapper that performs these checks such as https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router]. Since v4.9, this implementation introduces configurable virtual assets and shares to help developers mitigate that risk. The _decimalsOffset() corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset (0) makes it non-profitable even if an attacker is able to capture value from multiple user deposits, as a result of the value being captured by the virtual shares (out of the attacker's donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more expensive than it is profitable. More details about the underlying math can be found xref:ROOT:erc4626.adoc#inflation-attack[here]. The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets will cause the first user to exit to experience reduced losses in detriment to the last users that will experience bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the _convertToShares and _convertToAssets functions. To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide]. ====*

State Variables

ERC4626StorageLocation

bytes32 private constant ERC4626StorageLocation = 0x0773e532dfede91f04b12a73d3d2acd361424f41f76b4fb79f090161e36b4e00;

Functions

_getERC4626Storage

function _getERC4626Storage() private pure returns (ERC4626Storage storage $);

__ERC4626_init

Set the underlying asset contract. This must be an ERC20-compatible contract (ERC-20 or ERC-777).

function __ERC4626_init(IERC20 asset_) internal onlyInitializing;

__ERC4626_init_unchained

function __ERC4626_init_unchained(IERC20 asset_) internal onlyInitializing;

_tryGetAssetDecimals

Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.

function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool ok, uint8 assetDecimals);

decimals

Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals. See IERC20Metadata-decimals.

function decimals() public view virtual override(IERC20Metadata, ERC20Upgradeable) returns (uint8);

asset

function asset() public view virtual returns (address);

totalAssets

function totalAssets() public view virtual returns (uint256);

convertToShares

function convertToShares(uint256 assets) public view virtual returns (uint256);

convertToAssets

function convertToAssets(uint256 shares) public view virtual returns (uint256);

maxDeposit

function maxDeposit(address) public view virtual returns (uint256);

maxMint

function maxMint(address) public view virtual returns (uint256);

maxWithdraw

function maxWithdraw(address owner) public view virtual returns (uint256);

maxRedeem

function maxRedeem(address owner) public view virtual returns (uint256);

previewDeposit

function previewDeposit(uint256 assets) public view virtual returns (uint256);

previewMint

function previewMint(uint256 shares) public view virtual returns (uint256);

previewWithdraw

function previewWithdraw(uint256 assets) public view virtual returns (uint256);

previewRedeem

function previewRedeem(uint256 shares) public view virtual returns (uint256);

deposit

function deposit(uint256 assets, address receiver) public virtual returns (uint256);

mint

function mint(uint256 shares, address receiver) public virtual returns (uint256);

withdraw

function withdraw(uint256 assets, address receiver, address owner) public virtual returns (uint256);

redeem

function redeem(uint256 shares, address receiver, address owner) public virtual returns (uint256);

_convertToShares

Internal conversion function (from assets to shares) with support for rounding direction.

function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256);

_convertToAssets

Internal conversion function (from shares to assets) with support for rounding direction.

function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256);

_deposit

Deposit/mint common workflow.

function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual;

_withdraw

Withdraw/redeem common workflow.

function _withdraw(address caller, address receiver, address owner, uint256 assets, uint256 shares) internal virtual;

_decimalsOffset

function _decimalsOffset() internal view virtual returns (uint8);

Errors

ERC4626ExceededMaxDeposit

Attempted to deposit more assets than the max amount for receiver.

error ERC4626ExceededMaxDeposit(address receiver, uint256 assets, uint256 max);

ERC4626ExceededMaxMint

Attempted to mint more shares than the max amount for receiver.

error ERC4626ExceededMaxMint(address receiver, uint256 shares, uint256 max);

ERC4626ExceededMaxWithdraw

Attempted to withdraw more assets than the max amount for receiver.

error ERC4626ExceededMaxWithdraw(address owner, uint256 assets, uint256 max);

ERC4626ExceededMaxRedeem

Attempted to redeem more shares than the max amount for receiver.

error ERC4626ExceededMaxRedeem(address owner, uint256 shares, uint256 max);

Structs

ERC4626Storage

Note: storage-location: erc7201:openzeppelin.storage.ERC4626

struct ERC4626Storage {
    IERC20 _asset;
    uint8 _underlyingDecimals;
}