ERC4626.sol 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. // SPDX-License-Identifier: MIT
  2. // OpenZeppelin Contracts (last updated v4.8.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: When the vault is empty or nearly empty, deposits are at high risk of being stolen through frontrunning with
  18. * a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
  19. * attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
  20. * deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
  21. * similarly be affected by slippage. Users can protect against this attack as well unexpected slippage in general by
  22. * verifying the amount received is as expected, using a wrapper that performs these checks such as
  23. * https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
  24. *
  25. * _Available since v4.7._
  26. */
  27. abstract contract ERC4626 is ERC20, IERC4626 {
  28. using Math for uint256;
  29. IERC20 private immutable _asset;
  30. uint8 private immutable _decimals;
  31. /**
  32. * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
  33. */
  34. constructor(IERC20 asset_) {
  35. (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
  36. _decimals = success ? assetDecimals : super.decimals();
  37. _asset = asset_;
  38. }
  39. /**
  40. * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
  41. */
  42. function _tryGetAssetDecimals(IERC20 asset_) private view returns (bool, uint8) {
  43. (bool success, bytes memory encodedDecimals) = address(asset_).staticcall(
  44. abi.encodeWithSelector(IERC20Metadata.decimals.selector)
  45. );
  46. if (success && encodedDecimals.length >= 32) {
  47. uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
  48. if (returnedDecimals <= type(uint8).max) {
  49. return (true, uint8(returnedDecimals));
  50. }
  51. }
  52. return (false, 0);
  53. }
  54. /**
  55. * @dev Decimals are read from the underlying asset in the constructor and cached. If this fails (e.g., the asset
  56. * has not been created yet), the cached value is set to a default obtained by `super.decimals()` (which depends on
  57. * inheritance but is most likely 18). Override this function in order to set a guaranteed hardcoded value.
  58. * See {IERC20Metadata-decimals}.
  59. */
  60. function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
  61. return _decimals;
  62. }
  63. /** @dev See {IERC4626-asset}. */
  64. function asset() public view virtual override returns (address) {
  65. return address(_asset);
  66. }
  67. /** @dev See {IERC4626-totalAssets}. */
  68. function totalAssets() public view virtual override returns (uint256) {
  69. return _asset.balanceOf(address(this));
  70. }
  71. /** @dev See {IERC4626-convertToShares}. */
  72. function convertToShares(uint256 assets) public view virtual override returns (uint256) {
  73. return _convertToShares(assets, Math.Rounding.Down);
  74. }
  75. /** @dev See {IERC4626-convertToAssets}. */
  76. function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
  77. return _convertToAssets(shares, Math.Rounding.Down);
  78. }
  79. /** @dev See {IERC4626-maxDeposit}. */
  80. function maxDeposit(address) public view virtual override returns (uint256) {
  81. return _isVaultHealthy() ? type(uint256).max : 0;
  82. }
  83. /** @dev See {IERC4626-maxMint}. */
  84. function maxMint(address) public view virtual override returns (uint256) {
  85. return type(uint256).max;
  86. }
  87. /** @dev See {IERC4626-maxWithdraw}. */
  88. function maxWithdraw(address owner) public view virtual override returns (uint256) {
  89. return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
  90. }
  91. /** @dev See {IERC4626-maxRedeem}. */
  92. function maxRedeem(address owner) public view virtual override returns (uint256) {
  93. return balanceOf(owner);
  94. }
  95. /** @dev See {IERC4626-previewDeposit}. */
  96. function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
  97. return _convertToShares(assets, Math.Rounding.Down);
  98. }
  99. /** @dev See {IERC4626-previewMint}. */
  100. function previewMint(uint256 shares) public view virtual override returns (uint256) {
  101. return _convertToAssets(shares, Math.Rounding.Up);
  102. }
  103. /** @dev See {IERC4626-previewWithdraw}. */
  104. function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
  105. return _convertToShares(assets, Math.Rounding.Up);
  106. }
  107. /** @dev See {IERC4626-previewRedeem}. */
  108. function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
  109. return _convertToAssets(shares, Math.Rounding.Down);
  110. }
  111. /** @dev See {IERC4626-deposit}. */
  112. function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
  113. require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
  114. uint256 shares = previewDeposit(assets);
  115. _deposit(_msgSender(), receiver, assets, shares);
  116. return shares;
  117. }
  118. /** @dev See {IERC4626-mint}.
  119. *
  120. * As opposed to {deposit}, minting is allowed even if the vault is in a state where the price of a share is zero.
  121. * In this case, the shares will be minted without requiring any assets to be deposited.
  122. */
  123. function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
  124. require(shares <= maxMint(receiver), "ERC4626: mint more than max");
  125. uint256 assets = previewMint(shares);
  126. _deposit(_msgSender(), receiver, assets, shares);
  127. return assets;
  128. }
  129. /** @dev See {IERC4626-withdraw}. */
  130. function withdraw(uint256 assets, address receiver, address owner) public virtual override returns (uint256) {
  131. require(assets <= maxWithdraw(owner), "ERC4626: withdraw more than max");
  132. uint256 shares = previewWithdraw(assets);
  133. _withdraw(_msgSender(), receiver, owner, assets, shares);
  134. return shares;
  135. }
  136. /** @dev See {IERC4626-redeem}. */
  137. function redeem(uint256 shares, address receiver, address owner) public virtual override returns (uint256) {
  138. require(shares <= maxRedeem(owner), "ERC4626: redeem more than max");
  139. uint256 assets = previewRedeem(shares);
  140. _withdraw(_msgSender(), receiver, owner, assets, shares);
  141. return assets;
  142. }
  143. /**
  144. * @dev Internal conversion function (from assets to shares) with support for rounding direction.
  145. *
  146. * Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
  147. * would represent an infinite amount of shares.
  148. */
  149. function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
  150. uint256 supply = totalSupply();
  151. return
  152. (assets == 0 || supply == 0)
  153. ? _initialConvertToShares(assets, rounding)
  154. : assets.mulDiv(supply, totalAssets(), rounding);
  155. }
  156. /**
  157. * @dev Internal conversion function (from assets to shares) to apply when the vault is empty.
  158. *
  159. * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it.
  160. */
  161. function _initialConvertToShares(
  162. uint256 assets,
  163. Math.Rounding /*rounding*/
  164. ) internal view virtual returns (uint256 shares) {
  165. return assets;
  166. }
  167. /**
  168. * @dev Internal conversion function (from shares to assets) with support for rounding direction.
  169. */
  170. function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256) {
  171. uint256 supply = totalSupply();
  172. return
  173. (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding);
  174. }
  175. /**
  176. * @dev Internal conversion function (from shares to assets) to apply when the vault is empty.
  177. *
  178. * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it.
  179. */
  180. function _initialConvertToAssets(
  181. uint256 shares,
  182. Math.Rounding /*rounding*/
  183. ) internal view virtual returns (uint256) {
  184. return shares;
  185. }
  186. /**
  187. * @dev Deposit/mint common workflow.
  188. */
  189. function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
  190. // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the
  191. // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
  192. // calls the vault, which is assumed not malicious.
  193. //
  194. // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
  195. // assets are transferred and before the shares are minted, which is a valid state.
  196. // slither-disable-next-line reentrancy-no-eth
  197. SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
  198. _mint(receiver, shares);
  199. emit Deposit(caller, receiver, assets, shares);
  200. }
  201. /**
  202. * @dev Withdraw/redeem common workflow.
  203. */
  204. function _withdraw(
  205. address caller,
  206. address receiver,
  207. address owner,
  208. uint256 assets,
  209. uint256 shares
  210. ) internal virtual {
  211. if (caller != owner) {
  212. _spendAllowance(owner, caller, shares);
  213. }
  214. // If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the
  215. // `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer,
  216. // calls the vault, which is assumed not malicious.
  217. //
  218. // Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the
  219. // shares are burned and after the assets are transferred, which is a valid state.
  220. _burn(owner, shares);
  221. SafeERC20.safeTransfer(_asset, receiver, assets);
  222. emit Withdraw(caller, receiver, owner, assets, shares);
  223. }
  224. /**
  225. * @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares.
  226. */
  227. function _isVaultHealthy() private view returns (bool) {
  228. return totalAssets() > 0 || totalSupply() == 0;
  229. }
  230. }