ERC4626.sol 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/extensions/ERC4626.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. * CAUTION: Deposits and withdrawals may incur unexpected slippage. Users should verify that the amount received of
  18. * shares or assets is as expected. EOAs should operate through a wrapper that performs these checks such as
  19. * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
  20. *
  21. * _Available since v4.7._
  22. */
  23. abstract contract ERC4626 is ERC20, IERC4626 {
  24. using Math for uint256;
  25. IERC20 private immutable _asset;
  26. uint8 private immutable _decimals;
  27. /**
  28. * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
  29. */
  30. constructor(IERC20 asset_) {
  31. uint8 decimals_;
  32. try IERC20Metadata(address(asset_)).decimals() returns (uint8 value) {
  33. decimals_ = value;
  34. } catch {
  35. decimals_ = super.decimals();
  36. }
  37. _asset = asset_;
  38. _decimals = decimals_;
  39. }
  40. /** @dev See {IERC20Metadata-decimals}. */
  41. function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
  42. return _decimals;
  43. }
  44. /** @dev See {IERC4626-asset}. */
  45. function asset() public view virtual override returns (address) {
  46. return address(_asset);
  47. }
  48. /** @dev See {IERC4626-totalAssets}. */
  49. function totalAssets() public view virtual override returns (uint256) {
  50. return _asset.balanceOf(address(this));
  51. }
  52. /** @dev See {IERC4626-convertToShares}. */
  53. function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {
  54. return _convertToShares(assets, Math.Rounding.Down);
  55. }
  56. /** @dev See {IERC4626-convertToAssets}. */
  57. function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {
  58. return _convertToAssets(shares, Math.Rounding.Down);
  59. }
  60. /** @dev See {IERC4626-maxDeposit}. */
  61. function maxDeposit(address) public view virtual override returns (uint256) {
  62. return _isVaultCollateralized() ? type(uint256).max : 0;
  63. }
  64. /** @dev See {IERC4626-maxMint}. */
  65. function maxMint(address) public view virtual override returns (uint256) {
  66. return type(uint256).max;
  67. }
  68. /** @dev See {IERC4626-maxWithdraw}. */
  69. function maxWithdraw(address owner) public view virtual override returns (uint256) {
  70. return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
  71. }
  72. /** @dev See {IERC4626-maxRedeem}. */
  73. function maxRedeem(address owner) public view virtual override returns (uint256) {
  74. return balanceOf(owner);
  75. }
  76. /** @dev See {IERC4626-previewDeposit}. */
  77. function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
  78. return _convertToShares(assets, Math.Rounding.Down);
  79. }
  80. /** @dev See {IERC4626-previewMint}. */
  81. function previewMint(uint256 shares) public view virtual override returns (uint256) {
  82. return _convertToAssets(shares, Math.Rounding.Up);
  83. }
  84. /** @dev See {IERC4626-previewWithdraw}. */
  85. function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
  86. return _convertToShares(assets, Math.Rounding.Up);
  87. }
  88. /** @dev See {IERC4626-previewRedeem}. */
  89. function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
  90. return _convertToAssets(shares, Math.Rounding.Down);
  91. }
  92. /** @dev See {IERC4626-deposit}. */
  93. function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
  94. require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
  95. uint256 shares = previewDeposit(assets);
  96. _deposit(_msgSender(), receiver, assets, shares);
  97. return shares;
  98. }
  99. /** @dev See {IERC4626-mint}. */
  100. function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
  101. require(shares <= maxMint(receiver), "ERC4626: mint more than max");
  102. uint256 assets = previewMint(shares);
  103. _deposit(_msgSender(), receiver, assets, shares);
  104. return assets;
  105. }
  106. /** @dev See {IERC4626-withdraw}. */
  107. function withdraw(
  108. uint256 assets,
  109. address receiver,
  110. address owner
  111. ) public virtual override returns (uint256) {
  112. require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");
  113. uint256 shares = previewWithdraw(assets);
  114. _withdraw(_msgSender(), receiver, owner, assets, shares);
  115. return shares;
  116. }
  117. /** @dev See {IERC4626-redeem}. */
  118. function redeem(
  119. uint256 shares,
  120. address receiver,
  121. address owner
  122. ) public virtual override returns (uint256) {
  123. require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
  124. uint256 assets = previewRedeem(shares);
  125. _withdraw(_msgSender(), receiver, owner, assets, shares);
  126. return assets;
  127. }
  128. /**
  129. * @dev Internal conversion function (from assets to shares) with support for rounding direction.
  130. *
  131. * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
  132. * would represent an infinite amount of shares.
  133. */
  134. function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
  135. uint256 supply = totalSupply();
  136. return
  137. (assets == 0 || supply == 0)
  138. ? _initialConvertToShares(assets, rounding)
  139. : assets.mulDiv(supply, totalAssets(), rounding);
  140. }
  141. /**
  142. * @dev Internal conversion function (from assets to shares) to apply when the vault is empty.
  143. *
  144. * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it.
  145. */
  146. function _initialConvertToShares(
  147. uint256 assets,
  148. Math.Rounding /*rounding*/
  149. ) internal view virtual returns (uint256 shares) {
  150. return assets;
  151. }
  152. /**
  153. * @dev Internal conversion function (from shares to assets) with support for rounding direction.
  154. */
  155. function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256 assets) {
  156. uint256 supply = totalSupply();
  157. return
  158. (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding);
  159. }
  160. /**
  161. * @dev Internal conversion function (from shares to assets) to apply when the vault is empty.
  162. *
  163. * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it.
  164. */
  165. function _initialConvertToAssets(
  166. uint256 shares,
  167. Math.Rounding /*rounding*/
  168. ) internal view virtual returns (uint256 assets) {
  169. return shares;
  170. }
  171. /**
  172. * @dev Deposit/mint common workflow.
  173. */
  174. function _deposit(
  175. address caller,
  176. address receiver,
  177. uint256 assets,
  178. uint256 shares
  179. ) internal virtual {
  180. // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the
  181. // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
  182. // calls the vault, which is assumed not malicious.
  183. //
  184. // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
  185. // assets are transferred and before the shares are minted, which is a valid state.
  186. // slither-disable-next-line reentrancy-no-eth
  187. SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
  188. _mint(receiver, shares);
  189. emit Deposit(caller, receiver, assets, shares);
  190. }
  191. /**
  192. * @dev Withdraw/redeem common workflow.
  193. */
  194. function _withdraw(
  195. address caller,
  196. address receiver,
  197. address owner,
  198. uint256 assets,
  199. uint256 shares
  200. ) internal virtual {
  201. if (caller != owner) {
  202. _spendAllowance(owner, caller, shares);
  203. }
  204. // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
  205. // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
  206. // calls the vault, which is assumed not malicious.
  207. //
  208. // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
  209. // shares are burned and after the assets are transferred, which is a valid state.
  210. _burn(owner, shares);
  211. SafeERC20.safeTransfer(_asset, receiver, assets);
  212. emit Withdraw(caller, receiver, owner, assets, shares);
  213. }
  214. function _isVaultCollateralized() private view returns (bool) {
  215. return totalAssets() > 0 || totalSupply() == 0;
  216. }
  217. }