ERC4626.sol 8.0 KB

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