Merge branch 'master' into next-v5.0
This commit is contained in:
@ -28,7 +28,7 @@ abstract contract ERC20Capped is ERC20 {
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {ERC20-_transfer}.
|
||||
* @dev See {ERC20-_update}.
|
||||
*/
|
||||
function _update(address from, address to, uint256 amount) internal virtual override {
|
||||
if (from == address(0)) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/ERC20Pausable.sol)
|
||||
// OpenZeppelin Contracts (last updated v4.8.2) (token/ERC20/extensions/ERC20Pausable.sol)
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
@ -12,6 +12,12 @@ import "../../../security/Pausable.sol";
|
||||
* Useful for scenarios such as preventing trades until the end of an evaluation
|
||||
* period, or having an emergency switch for freezing all token transfers in the
|
||||
* event of a large bug.
|
||||
*
|
||||
* IMPORTANT: This contract does not include public pause and unpause functions. In
|
||||
* addition to inheriting this contract, you must define both functions, invoking the
|
||||
* {Pausable-_pause} and {Pausable-_unpause} internal functions, with appropriate
|
||||
* access control, e.g. using {AccessControl} or {Ownable}. Not doing so will
|
||||
* make the contract unpausable.
|
||||
*/
|
||||
abstract contract ERC20Pausable is ERC20, Pausable {
|
||||
/**
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Votes.sol)
|
||||
// OpenZeppelin Contracts (last updated v4.8.1) (token/ERC20/extensions/ERC20Votes.sol)
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
@ -43,6 +43,13 @@ abstract contract ERC20Votes is ERC20, Votes {
|
||||
_transferVotingUnits(from, to, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the balance of `account`.
|
||||
*/
|
||||
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
|
||||
return balanceOf(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Get number of checkpoints for `account`.
|
||||
*/
|
||||
@ -53,14 +60,7 @@ abstract contract ERC20Votes is ERC20, Votes {
|
||||
/**
|
||||
* @dev Get the `pos`-th checkpoint for `account`.
|
||||
*/
|
||||
function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint memory) {
|
||||
function checkpoints(address account, uint32 pos) public view virtual returns (Checkpoints.Checkpoint224 memory) {
|
||||
return _checkpoints(account, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the balance of `account`.
|
||||
*/
|
||||
function _getVotingUnits(address account) internal view virtual override returns (uint256) {
|
||||
return balanceOf(account);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,28 +16,38 @@ import "../utils/SafeERC20.sol";
|
||||
* _Available since v4.2._
|
||||
*/
|
||||
abstract contract ERC20Wrapper is ERC20 {
|
||||
IERC20 public immutable underlying;
|
||||
IERC20 private immutable _underlying;
|
||||
|
||||
constructor(IERC20 underlyingToken) {
|
||||
underlying = underlyingToken;
|
||||
require(underlyingToken != this, "ERC20Wrapper: cannot self wrap");
|
||||
_underlying = underlyingToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev See {ERC20-decimals}.
|
||||
*/
|
||||
function decimals() public view virtual override returns (uint8) {
|
||||
try IERC20Metadata(address(underlying)).decimals() returns (uint8 value) {
|
||||
try IERC20Metadata(address(_underlying)).decimals() returns (uint8 value) {
|
||||
return value;
|
||||
} catch {
|
||||
return super.decimals();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Returns the address of the underlying ERC-20 token that is being wrapped.
|
||||
*/
|
||||
function underlying() public view returns (IERC20) {
|
||||
return _underlying;
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Allow a user to deposit underlying tokens and mint the corresponding number of wrapped tokens.
|
||||
*/
|
||||
function depositFor(address account, uint256 amount) public virtual returns (bool) {
|
||||
SafeERC20.safeTransferFrom(underlying, _msgSender(), address(this), amount);
|
||||
address sender = _msgSender();
|
||||
require(sender != address(this), "ERC20Wrapper: wrapper can't deposit");
|
||||
SafeERC20.safeTransferFrom(_underlying, sender, address(this), amount);
|
||||
_mint(account, amount);
|
||||
return true;
|
||||
}
|
||||
@ -47,7 +57,7 @@ abstract contract ERC20Wrapper is ERC20 {
|
||||
*/
|
||||
function withdrawTo(address account, uint256 amount) public virtual returns (bool) {
|
||||
_burn(_msgSender(), amount);
|
||||
SafeERC20.safeTransfer(underlying, account, amount);
|
||||
SafeERC20.safeTransfer(_underlying, account, amount);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -56,7 +66,7 @@ abstract contract ERC20Wrapper is ERC20 {
|
||||
* function that can be exposed with access control if desired.
|
||||
*/
|
||||
function _recover(address account) internal virtual returns (uint256) {
|
||||
uint256 value = underlying.balanceOf(address(this)) - totalSupply();
|
||||
uint256 value = _underlying.balanceOf(address(this)) - totalSupply();
|
||||
_mint(account, value);
|
||||
return value;
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/extensions/ERC4626.sol)
|
||||
// OpenZeppelin Contracts (last updated v4.8.1) (token/ERC20/extensions/ERC4626.sol)
|
||||
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
@ -17,28 +17,48 @@ import "../../../utils/math/Math.sol";
|
||||
* the ERC20 standard. Any additional extensions included along it would affect the "shares" token represented by this
|
||||
* contract and not the "assets" token which is an independent contract.
|
||||
*
|
||||
* CAUTION: When the vault is empty or nearly empty, deposits are at high risk of being stolen through frontrunning with
|
||||
* a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
|
||||
* [CAUTION]
|
||||
* ====
|
||||
* In empty (or nearly empty) ERC-4626 vaults, deposits are at high risk of being stolen through frontrunning
|
||||
* with a "donation" to the vault that inflates the price of a share. This is variously known as a donation or inflation
|
||||
* attack and is essentially a problem of slippage. Vault deployers can protect against this attack by making an initial
|
||||
* deposit of a non-trivial amount of the asset, such that price manipulation becomes infeasible. Withdrawals may
|
||||
* similarly be affected by slippage. Users can protect against this attack as well unexpected slippage in general by
|
||||
* similarly be affected by slippage. Users can protect against this attack as well as unexpected slippage in general by
|
||||
* verifying the amount received is as expected, using a wrapper that performs these checks such as
|
||||
* https://github.com/fei-protocol/ERC4626#erc4626router-and-base[ERC4626Router].
|
||||
*
|
||||
* Since v4.9, this implementation uses virtual assets and shares to mitigate that risk. The `_decimalsOffset()`
|
||||
* corresponds to an offset in the decimal representation between the underlying asset's decimals and the vault
|
||||
* decimals. This offset also determines the rate of virtual shares to virtual assets in the vault, which itself
|
||||
* determines the initial exchange rate. While not fully preventing the attack, analysis shows that the default offset
|
||||
* (0) makes it non-profitable, as a result of the value being captured by the virtual shares (out of the attacker's
|
||||
* donation) matching the attacker's expected gains. With a larger offset, the attack becomes orders of magnitude more
|
||||
* expensive than it is profitable. More details about the underlying math can be found
|
||||
* xref:erc4626.adoc#inflation-attack[here].
|
||||
*
|
||||
* The drawback of this approach is that the virtual shares do capture (a very small) part of the value being accrued
|
||||
* to the vault. Also, if the vault experiences losses, the users try to exit the vault, the virtual shares and assets
|
||||
* will cause the first user to exit to experience reduced losses in detriment to the last users that will experience
|
||||
* bigger losses. Developers willing to revert back to the pre-v4.9 behavior just need to override the
|
||||
* `_convertToShares` and `_convertToAssets` functions.
|
||||
*
|
||||
* To learn more, check out our xref:ROOT:erc4626.adoc[ERC-4626 guide].
|
||||
* ====
|
||||
*
|
||||
* _Available since v4.7._
|
||||
*/
|
||||
abstract contract ERC4626 is ERC20, IERC4626 {
|
||||
using Math for uint256;
|
||||
|
||||
IERC20 private immutable _asset;
|
||||
uint8 private immutable _decimals;
|
||||
uint8 private immutable _underlyingDecimals;
|
||||
|
||||
/**
|
||||
* @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777).
|
||||
*/
|
||||
constructor(IERC20 asset_) {
|
||||
(bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_);
|
||||
_decimals = success ? assetDecimals : super.decimals();
|
||||
_underlyingDecimals = success ? assetDecimals : 18;
|
||||
_asset = asset_;
|
||||
}
|
||||
|
||||
@ -59,13 +79,14 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
* @dev Decimals are computed by adding the decimal offset on top of the underlying asset's decimals. This
|
||||
* "original" value is cached during construction of the vault contract. If this read operation fails (e.g., the
|
||||
* asset has not been created yet), a default of 18 is used to represent the underlying asset's decimals.
|
||||
*
|
||||
* See {IERC20Metadata-decimals}.
|
||||
*/
|
||||
function decimals() public view virtual override(IERC20Metadata, ERC20) returns (uint8) {
|
||||
return _decimals;
|
||||
return _underlyingDecimals + _decimalsOffset();
|
||||
}
|
||||
|
||||
/** @dev See {IERC4626-asset}. */
|
||||
@ -90,7 +111,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
||||
|
||||
/** @dev See {IERC4626-maxDeposit}. */
|
||||
function maxDeposit(address) public view virtual override returns (uint256) {
|
||||
return _isVaultHealthy() ? type(uint256).max : 0;
|
||||
return type(uint256).max;
|
||||
}
|
||||
|
||||
/** @dev See {IERC4626-maxMint}. */
|
||||
@ -174,56 +195,23 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
||||
|
||||
/**
|
||||
* @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
|
||||
* would represent an infinite amount of shares.
|
||||
*/
|
||||
function _convertToShares(uint256 assets, Math.Rounding rounding) internal view virtual returns (uint256) {
|
||||
uint256 supply = totalSupply();
|
||||
return
|
||||
(assets == 0 || supply == 0)
|
||||
? _initialConvertToShares(assets, 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;
|
||||
return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, rounding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
uint256 supply = totalSupply();
|
||||
return
|
||||
(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) {
|
||||
return shares;
|
||||
return shares.mulDiv(totalAssets() + 1, totalSupply() + 10 ** _decimalsOffset(), rounding);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Deposit/mint common workflow.
|
||||
*/
|
||||
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual {
|
||||
// If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the
|
||||
// If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the
|
||||
// `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer,
|
||||
// calls the vault, which is assumed not malicious.
|
||||
//
|
||||
@ -262,10 +250,7 @@ abstract contract ERC4626 is ERC20, IERC4626 {
|
||||
emit Withdraw(caller, receiver, owner, assets, shares);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev Checks if vault is "healthy" in the sense of having assets backing the circulating shares.
|
||||
*/
|
||||
function _isVaultHealthy() private view returns (bool) {
|
||||
return totalAssets() > 0 || totalSupply() == 0;
|
||||
function _decimalsOffset() internal view virtual returns (uint8) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user