ERC4626.sol 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.0;
  3. import "../ERC20.sol";
  4. import "../utils/SafeERC20.sol";
  5. import "../../../interfaces/IERC4626.sol";
  6. import "../../../utils/math/Math.sol";
  7. /**
  8. * @dev Implementation of the ERC4626 "Tokenized Vault Standard" as defined in
  9. * https://eips.ethereum.org/EIPS/eip-4626[EIP-4626].
  10. *
  11. * This extension allows the minting and burning of "shares" (represented using the ERC20 inheritance) in exchange for
  12. * underlying "assets" through standardized {deposit}, {mint}, {redeem} and {burn} workflows. This contract extends
  13. * the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this
  14. * contract and not the "assets" token which is an independent contract.
  15. *
  16. * CAUTION: Deposits and withdrawals may incur unexpected slippage. Users should verify that the amount received of
  17. * shares or assets is as expected. EOAs should operate through a wrapper that performs these checks such as
  18. * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
  19. *
  20. * _Available since v4.7._
  21. */
  22. abstract contract ERC4626 is ERC20, IERC4626 {
  23. using Math for uint256;
  24. IERC20Metadata private immutable _asset;
  25. /**
  26. * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
  27. */
  28. constructor(IERC20Metadata asset_) {
  29. _asset = asset_;
  30. }
  31. /** @dev See {IERC4262-asset} */
  32. function asset() public view virtual override returns (address) {
  33. return address(_asset);
  34. }
  35. /** @dev See {IERC4262-totalAssets} */
  36. function totalAssets() public view virtual override returns (uint256) {
  37. return _asset.balanceOf(address(this));
  38. }
  39. /** @dev See {IERC4262-convertToShares} */
  40. function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {
  41. return _convertToShares(assets, Math.Rounding.Down);
  42. }
  43. /** @dev See {IERC4262-convertToAssets} */
  44. function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {
  45. return _convertToAssets(shares, Math.Rounding.Down);
  46. }
  47. /** @dev See {IERC4262-maxDeposit} */
  48. function maxDeposit(address) public view virtual override returns (uint256) {
  49. return _isVaultCollateralized() ? type(uint256).max : 0;
  50. }
  51. /** @dev See {IERC4262-maxMint} */
  52. function maxMint(address) public view virtual override returns (uint256) {
  53. return type(uint256).max;
  54. }
  55. /** @dev See {IERC4262-maxWithdraw} */
  56. function maxWithdraw(address owner) public view virtual override returns (uint256) {
  57. return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
  58. }
  59. /** @dev See {IERC4262-maxRedeem} */
  60. function maxRedeem(address owner) public view virtual override returns (uint256) {
  61. return balanceOf(owner);
  62. }
  63. /** @dev See {IERC4262-previewDeposit} */
  64. function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
  65. return _convertToShares(assets, Math.Rounding.Down);
  66. }
  67. /** @dev See {IERC4262-previewMint} */
  68. function previewMint(uint256 shares) public view virtual override returns (uint256) {
  69. return _convertToAssets(shares, Math.Rounding.Up);
  70. }
  71. /** @dev See {IERC4262-previewWithdraw} */
  72. function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
  73. return _convertToShares(assets, Math.Rounding.Up);
  74. }
  75. /** @dev See {IERC4262-previewRedeem} */
  76. function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
  77. return _convertToAssets(shares, Math.Rounding.Down);
  78. }
  79. /** @dev See {IERC4262-deposit} */
  80. function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
  81. require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
  82. uint256 shares = previewDeposit(assets);
  83. _deposit(_msgSender(), receiver, assets, shares);
  84. return shares;
  85. }
  86. /** @dev See {IERC4262-mint} */
  87. function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
  88. require(shares <= maxMint(receiver), "ERC4626: mint more than max");
  89. uint256 assets = previewMint(shares);
  90. _deposit(_msgSender(), receiver, assets, shares);
  91. return assets;
  92. }
  93. /** @dev See {IERC4262-withdraw} */
  94. function withdraw(
  95. uint256 assets,
  96. address receiver,
  97. address owner
  98. ) public virtual override returns (uint256) {
  99. require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");
  100. uint256 shares = previewWithdraw(assets);
  101. _withdraw(_msgSender(), receiver, owner, assets, shares);
  102. return shares;
  103. }
  104. /** @dev See {IERC4262-redeem} */
  105. function redeem(
  106. uint256 shares,
  107. address receiver,
  108. address owner
  109. ) public virtual override returns (uint256) {
  110. require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
  111. uint256 assets = previewRedeem(shares);
  112. _withdraw(_msgSender(), receiver, owner, assets, shares);
  113. return assets;
  114. }
  115. /**
  116. * @dev Internal convertion function (from assets to shares) with support for rounding direction
  117. *
  118. * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
  119. * would represent an infinite amout of shares.
  120. */
  121. function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
  122. uint256 supply = totalSupply();
  123. return
  124. (assets == 0 || supply == 0)
  125. ? assets.mulDiv(10**decimals(), 10**_asset.decimals(), rounding)
  126. : assets.mulDiv(supply, totalAssets(), rounding);
  127. }
  128. /**
  129. * @dev Internal convertion function (from shares to assets) with support for rounding direction
  130. */
  131. function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256 assets) {
  132. uint256 supply = totalSupply();
  133. return
  134. (supply == 0)
  135. ? shares.mulDiv(10**_asset.decimals(), 10**decimals(), rounding)
  136. : shares.mulDiv(totalAssets(), supply, rounding);
  137. }
  138. /**
  139. * @dev Deposit/mint common workflow
  140. */
  141. function _deposit(
  142. address caller,
  143. address receiver,
  144. uint256 assets,
  145. uint256 shares
  146. ) internal virtual {
  147. // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the
  148. // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
  149. // calls the vault, which is assumed not malicious.
  150. //
  151. // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
  152. // assets are transfered and before the shares are minted, which is a valid state.
  153. // slither-disable-next-line reentrancy-no-eth
  154. SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
  155. _mint(receiver, shares);
  156. emit Deposit(caller, receiver, assets, shares);
  157. }
  158. /**
  159. * @dev Withdraw/redeem common workflow
  160. */
  161. function _withdraw(
  162. address caller,
  163. address receiver,
  164. address owner,
  165. uint256 assets,
  166. uint256 shares
  167. ) internal virtual {
  168. if (caller != owner) {
  169. _spendAllowance(owner, caller, shares);
  170. }
  171. // If _asset is ERC777, `transfer` can trigger trigger a reentrancy AFTER the transfer happens through the
  172. // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
  173. // calls the vault, which is assumed not malicious.
  174. //
  175. // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
  176. // shares are burned and after the assets are transfered, which is a valid state.
  177. _burn(owner, shares);
  178. SafeERC20.safeTransfer(_asset, receiver, assets);
  179. emit Withdraw(caller, receiver, owner, assets, shares);
  180. }
  181. function _isVaultCollateralized() private view returns (bool) {
  182. return totalAssets() > 0 || totalSupply() == 0;
  183. }
  184. }