|
@@ -1,5 +1,5 @@
|
|
// SPDX-License-Identifier: MIT
|
|
// SPDX-License-Identifier: MIT
|
|
-// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/extensions/ERC4626.sol)
|
|
|
|
|
|
+// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC4626.sol)
|
|
|
|
|
|
pragma solidity ^0.8.0;
|
|
pragma solidity ^0.8.0;
|
|
|
|
|
|
@@ -26,76 +26,105 @@ import "../../../utils/math/Math.sol";
|
|
abstract contract ERC4626 is ERC20, IERC4626 {
|
|
abstract contract ERC4626 is ERC20, IERC4626 {
|
|
using Math for uint256;
|
|
using Math for uint256;
|
|
|
|
|
|
- IERC20Metadata private immutable _asset;
|
|
|
|
|
|
+ IERC20 private immutable _asset;
|
|
|
|
+ uint8 private immutable _decimals;
|
|
|
|
|
|
/**
|
|
/**
|
|
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
|
|
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
|
|
*/
|
|
*/
|
|
- constructor(IERC20Metadata asset_) {
|
|
|
|
|
|
+ constructor(IERC20 asset_) {
|
|
|
|
+ (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
|
|
|
|
+ _decimals = success ? assetDecimals : super.decimals();
|
|
_asset = asset_;
|
|
_asset = asset_;
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-asset}. */
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @dev Attempts to fetch the asset decimals. A return value of false indicates that the attempt failed in some way.
|
|
|
|
+ */
|
|
|
|
+ function _tryGetAssetDecimals(IERC20 asset_) private returns (bool, uint8) {
|
|
|
|
+ (bool success, bytes memory encodedDecimals) = address(asset_).call(
|
|
|
|
+ abi.encodeWithSelector(IERC20Metadata.decimals.selector)
|
|
|
|
+ );
|
|
|
|
+ if (success && encodedDecimals.length >= 32) {
|
|
|
|
+ uint256 returnedDecimals = abi.decode(encodedDecimals, (uint256));
|
|
|
|
+ if (returnedDecimals <= type(uint8).max) {
|
|
|
|
+ return (true, uint8(returnedDecimals));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return (false, 0);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @dev Decimals are read from the underlying asset in the constructor and cached. If this fails (e.g., the asset
|
|
|
|
+ * has not been created yet), the cached value is set to a default obtained by `super.decimals()` (which depends on
|
|
|
|
+ * inheritance but is most likely 18). Override this function in order to set a guaranteed hardcoded value.
|
|
|
|
+ * See {IERC20Metadata-decimals}.
|
|
|
|
+ */
|
|
|
|
+ function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
|
|
|
|
+ return _decimals;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /** @dev See {IERC4626-asset}. */
|
|
function asset() public view virtual override returns (address) {
|
|
function asset() public view virtual override returns (address) {
|
|
return address(_asset);
|
|
return address(_asset);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-totalAssets}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-totalAssets}. */
|
|
function totalAssets() public view virtual override returns (uint256) {
|
|
function totalAssets() public view virtual override returns (uint256) {
|
|
return _asset.balanceOf(address(this));
|
|
return _asset.balanceOf(address(this));
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-convertToShares}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-convertToShares}. */
|
|
function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {
|
|
function convertToShares(uint256 assets) public view virtual override returns (uint256 shares) {
|
|
return _convertToShares(assets, Math.Rounding.Down);
|
|
return _convertToShares(assets, Math.Rounding.Down);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-convertToAssets}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-convertToAssets}. */
|
|
function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {
|
|
function convertToAssets(uint256 shares) public view virtual override returns (uint256 assets) {
|
|
return _convertToAssets(shares, Math.Rounding.Down);
|
|
return _convertToAssets(shares, Math.Rounding.Down);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-maxDeposit}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-maxDeposit}. */
|
|
function maxDeposit(address) public view virtual override returns (uint256) {
|
|
function maxDeposit(address) public view virtual override returns (uint256) {
|
|
return _isVaultCollateralized() ? type(uint256).max : 0;
|
|
return _isVaultCollateralized() ? type(uint256).max : 0;
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-maxMint}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-maxMint}. */
|
|
function maxMint(address) public view virtual override returns (uint256) {
|
|
function maxMint(address) public view virtual override returns (uint256) {
|
|
return type(uint256).max;
|
|
return type(uint256).max;
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-maxWithdraw}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-maxWithdraw}. */
|
|
function maxWithdraw(address owner) public view virtual override returns (uint256) {
|
|
function maxWithdraw(address owner) public view virtual override returns (uint256) {
|
|
return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
|
|
return _convertToAssets(balanceOf(owner), Math.Rounding.Down);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-maxRedeem}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-maxRedeem}. */
|
|
function maxRedeem(address owner) public view virtual override returns (uint256) {
|
|
function maxRedeem(address owner) public view virtual override returns (uint256) {
|
|
return balanceOf(owner);
|
|
return balanceOf(owner);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-previewDeposit}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-previewDeposit}. */
|
|
function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
|
|
function previewDeposit(uint256 assets) public view virtual override returns (uint256) {
|
|
return _convertToShares(assets, Math.Rounding.Down);
|
|
return _convertToShares(assets, Math.Rounding.Down);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-previewMint}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-previewMint}. */
|
|
function previewMint(uint256 shares) public view virtual override returns (uint256) {
|
|
function previewMint(uint256 shares) public view virtual override returns (uint256) {
|
|
return _convertToAssets(shares, Math.Rounding.Up);
|
|
return _convertToAssets(shares, Math.Rounding.Up);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-previewWithdraw}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-previewWithdraw}. */
|
|
function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
|
|
function previewWithdraw(uint256 assets) public view virtual override returns (uint256) {
|
|
return _convertToShares(assets, Math.Rounding.Up);
|
|
return _convertToShares(assets, Math.Rounding.Up);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-previewRedeem}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-previewRedeem}. */
|
|
function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
|
|
function previewRedeem(uint256 shares) public view virtual override returns (uint256) {
|
|
return _convertToAssets(shares, Math.Rounding.Down);
|
|
return _convertToAssets(shares, Math.Rounding.Down);
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-deposit}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-deposit}. */
|
|
function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
|
|
function deposit(uint256 assets, address receiver) public virtual override returns (uint256) {
|
|
require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
|
|
require(assets <= maxDeposit(receiver), "ERC4626: deposit more than max");
|
|
|
|
|
|
@@ -105,7 +134,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
|
return shares;
|
|
return shares;
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-mint}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-mint}. */
|
|
function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
|
|
function mint(uint256 shares, address receiver) public virtual override returns (uint256) {
|
|
require(shares <= maxMint(receiver), "ERC4626: mint more than max");
|
|
require(shares <= maxMint(receiver), "ERC4626: mint more than max");
|
|
|
|
|
|
@@ -115,7 +144,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
|
return assets;
|
|
return assets;
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-withdraw}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-withdraw}. */
|
|
function withdraw(
|
|
function withdraw(
|
|
uint256 assets,
|
|
uint256 assets,
|
|
address receiver,
|
|
address receiver,
|
|
@@ -129,7 +158,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
|
return shares;
|
|
return shares;
|
|
}
|
|
}
|
|
|
|
|
|
- /** @dev See {IERC4262-redeem}. */
|
|
|
|
|
|
+ /** @dev See {IERC4626-redeem}. */
|
|
function redeem(
|
|
function redeem(
|
|
uint256 shares,
|
|
uint256 shares,
|
|
address receiver,
|
|
address receiver,
|
|
@@ -147,25 +176,47 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
|
* @dev Internal conversion function (from assets to shares) with support for rounding direction.
|
|
* @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
|
|
* Will revert if assets > 0, totalSupply > 0 and totalAssets = 0. That corresponds to a case where any asset
|
|
- * would represent an infinite amout of shares.
|
|
|
|
|
|
+ * would represent an infinite amount of shares.
|
|
*/
|
|
*/
|
|
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
|
|
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256 shares) {
|
|
uint256 supply = totalSupply();
|
|
uint256 supply = totalSupply();
|
|
return
|
|
return
|
|
(assets == 0 || supply == 0)
|
|
(assets == 0 || supply == 0)
|
|
- ? assets.mulDiv(10**decimals(), 10**_asset.decimals(), rounding)
|
|
|
|
|
|
+ ? _initialConvertToShares(assets, rounding)
|
|
: assets.mulDiv(supply, totalAssets(), rounding);
|
|
: assets.mulDiv(supply, totalAssets(), rounding);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
+ * @dev Internal conversion function (from assets to shares) to apply when the vault is empty.
|
|
|
|
+ *
|
|
|
|
+ * NOTE: Make sure to keep this function consistent with {_initialConvertToAssets} when overriding it.
|
|
|
|
+ */
|
|
|
|
+ function _initialConvertToShares(
|
|
|
|
+ uint256 assets,
|
|
|
|
+ Math.Rounding /*rounding*/
|
|
|
|
+ ) internal view virtual returns (uint256 shares) {
|
|
|
|
+ return assets;
|
|
|
|
+ }
|
|
|
|
+
|
|
/**
|
|
/**
|
|
* @dev Internal conversion function (from shares to assets) with support for rounding direction.
|
|
* @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) {
|
|
function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view virtual returns (uint256 assets) {
|
|
uint256 supply = totalSupply();
|
|
uint256 supply = totalSupply();
|
|
return
|
|
return
|
|
- (supply == 0)
|
|
|
|
- ? shares.mulDiv(10**_asset.decimals(), 10**decimals(), rounding)
|
|
|
|
- : shares.mulDiv(totalAssets(), supply, rounding);
|
|
|
|
|
|
+ (supply == 0) ? _initialConvertToAssets(shares, rounding) : shares.mulDiv(totalAssets(), supply, rounding);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ /**
|
|
|
|
+ * @dev Internal conversion function (from shares to assets) to apply when the vault is empty.
|
|
|
|
+ *
|
|
|
|
+ * NOTE: Make sure to keep this function consistent with {_initialConvertToShares} when overriding it.
|
|
|
|
+ */
|
|
|
|
+ function _initialConvertToAssets(
|
|
|
|
+ uint256 shares,
|
|
|
|
+ Math.Rounding /*rounding*/
|
|
|
|
+ ) internal view virtual returns (uint256 assets) {
|
|
|
|
+ return shares;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -182,7 +233,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
|
// calls the vault, which is assumed not malicious.
|
|
// 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
|
|
// Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the
|
|
- // assets are transfered and before the shares are minted, which is a valid state.
|
|
|
|
|
|
+ // assets are transferred and before the shares are minted, which is a valid state.
|
|
// slither-disable-next-line reentrancy-no-eth
|
|
// slither-disable-next-line reentrancy-no-eth
|
|
SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
|
|
SafeERC20.safeTransferFrom(_asset, caller, address(this), assets);
|
|
_mint(receiver, shares);
|
|
_mint(receiver, shares);
|
|
@@ -209,7 +260,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
|
// calls the vault, which is assumed not malicious.
|
|
// 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
|
|
// 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 transfered, which is a valid state.
|
|
|
|
|
|
+ // shares are burned and after the assets are transferred, which is a valid state.
|
|
_burn(owner, shares);
|
|
_burn(owner, shares);
|
|
SafeERC20.safeTransfer(_asset, receiver, assets);
|
|
SafeERC20.safeTransfer(_asset, receiver, assets);
|
|
|
|
|