Merge branch 'master' into next-v5.0

This commit is contained in:
Francisco Giordano
2023-05-16 00:07:07 -03:00
308 changed files with 21085 additions and 11515 deletions

View File

@ -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)) {

View File

@ -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 {
/**

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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;
}
}