123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222 |
- // SPDX-License-Identifier: MIT
- // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/extensions/ERC4626.sol)
- pragma solidity ^0.8.0;
- import "../ERC20.sol";
- import "../utils/SafeERC20.sol";
- import "../../../interfaces/IERC4626.sol";
- import "../../../utils/math/Math.sol";
- /**
- * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in
- * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626].
- *
- * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for
- * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
- * the ERC20 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: Deposits and withdrawals may incur unexpected slippage. Users should verify that the amount received of
- * shares or assets is as expected. EOAs should operate through a wrapper that performs these checks such as
- * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
- *
- * _Available since v4.7._
- */
- abstract contract ERC4626 is ERC20, IERC4626 {
- using Math for uint256;
- IERC20Metadata private immutable _asset;
- /**
- * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
- */
- constructor(IERC20Metadata asset_) {
- _asset = asset_;
- }
- /** @dev See {IERC4626-asset}. */
- function asset() public view virtual override returns (address) {
- return address(_asset);
- }
- /** @dev See {IERC4626-totalAssets}. */
- function totalAssets() public view virtual override returns (uint256) {
- return _asset.balanceOf(address(this));
- }
- /** @dev See {IERC4626-convertToShares}. */
- function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {
- return _convertToShares(assets, Math.Rounding.Down);
- }
- /** @dev See {IERC4626-convertToAssets}. */
- function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {
- return _convertToAssets(shares, Math.Rounding.Down);
- }
- /** @dev See {IERC4626-maxDeposit}. */
- function maxDeposit(address) public view virtual override returns (uint256) {
- return _isVaultCollateralized() ? type(uint256).max : 0;
- }
- /** @dev See {IERC4626-maxMint}. */
- function maxMint(address) public view virtual override returns (uint256) {
- return type(uint256).max;
- }
- /** @dev See {IERC4626-maxWithdraw}. */
- function maxWithdraw(address owner) public view virtual override returns (uint256) {
- return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
- }
- /** @dev See {IERC4626-maxRedeem}. */
- function maxRedeem(address owner) public view virtual override returns (uint256) {
- return balanceOf(owner);
- }
- /** @dev See {IERC4626-previewDeposit}. */
- function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
- return _convertToShares(assets, Math.Rounding.Down);
- }
- /** @dev See {IERC4626-previewMint}. */
- function previewMint(uint256 shares) public view virtual override returns (uint256) {
- return _convertToAssets(shares, Math.Rounding.Up);
- }
- /** @dev See {IERC4626-previewWithdraw}. */
- function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
- return _convertToShares(assets, Math.Rounding.Up);
- }
- /** @dev See {IERC4626-previewRedeem}. */
- function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
- return _convertToAssets(shares, Math.Rounding.Down);
- }
- /** @dev See {IERC4626-deposit}. */
- function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
- require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
- uint256 shares = previewDeposit(assets);
- _deposit(_msgSender(), receiver, assets, shares);
- return shares;
- }
- /** @dev See {IERC4626-mint}. */
- function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
- require(shares <= maxMint(receiver), "ERC4626: mint more than max");
- uint256 assets = previewMint(shares);
- _deposit(_msgSender(), receiver, assets, shares);
- return assets;
- }
- /** @dev See {IERC4626-withdraw}. */
- function withdraw(
- uint256 assets,
- address receiver,
- address owner
- ) public virtual override returns (uint256) {
- require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");
- uint256 shares = previewWithdraw(assets);
- _withdraw(_msgSender(), receiver, owner, assets, shares);
- return shares;
- }
- /** @dev See {IERC4626-redeem}. */
- function redeem(
- uint256 shares,
- address receiver,
- address owner
- ) public virtual override returns (uint256) {
- require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
- uint256 assets = previewRedeem(shares);
- _withdraw(_msgSender(), receiver, owner, assets, shares);
- return assets;
- }
- /**
- * @dev Internal conversion function (from assets to shares) with support for rounding direction.
- *
- * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
- * would represent an infinite amount of shares.
- */
- function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
- uint256 supply = totalSupply();
- return
- (assets == 0 || supply == 0)
- ? assets.mulDiv(10**decimals(), 10**_asset.decimals(), rounding)
- : assets.mulDiv(supply, totalAssets(), rounding);
- }
- /**
- * @dev Internal conversion function (from shares to assets) with support for rounding direction.
- */
- function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256 assets) {
- uint256 supply = totalSupply();
- return
- (supply == 0)
- ? shares.mulDiv(10**_asset.decimals(), 10**decimals(), rounding)
- : shares.mulDiv(totalAssets(), supply, rounding);
- }
- /**
- * @dev Deposit/mint common workflow.
- */
- function _deposit(
- address caller,
- address receiver,
- uint256 assets,
- uint256 shares
- ) internal virtual {
- // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the
- // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
- // calls the vault, which is assumed not malicious.
- //
- // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
- // assets are transferred and before the shares are minted, which is a valid state.
- // slither-disable-next-line reentrancy-no-eth
- SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
- _mint(receiver, shares);
- emit Deposit(caller, receiver, assets, shares);
- }
- /**
- * @dev Withdraw/redeem common workflow.
- */
- function _withdraw(
- address caller,
- address receiver,
- address owner,
- uint256 assets,
- uint256 shares
- ) internal virtual {
- if (caller != owner) {
- _spendAllowance(owner, caller, shares);
- }
- // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
- // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
- // calls the vault, which is assumed not malicious.
- //
- // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
- // shares are burned and after the assets are transferred, which is a valid state.
- _burn(owner, shares);
- SafeERC20.safeTransfer(_asset, receiver, assets);
- emit Withdraw(caller, receiver, owner, assets, shares);
- }
- function _isVaultCollateralized() private view returns (bool) {
- return totalAssets() > 0 || totalSupply() == 0;
- }
- }
|