From 14f98dbb581a5365ce3f0c50bd850e499c554f72 Mon Sep 17 00:00:00 2001 From: Francisco Date: Wed, 28 Sep 2022 15:42:55 -0300 Subject: [PATCH] Improve handling and docs for ERC4626.decimals (#3733) --- contracts/token/ERC20/extensions/ERC4626.sol | 33 ++++++++++++++------ 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/contracts/token/ERC20/extensions/ERC4626.sol b/contracts/token/ERC20/extensions/ERC4626.sol index 3adb63d84..41ec9d685 100644 --- a/contracts/token/ERC20/extensions/ERC4626.sol +++ b/contracts/token/ERC20/extensions/ERC4626.sol @@ -33,18 +33,33 @@ abstract contract ERC4626 is ERC20, IERC4626 { * @dev Set the underlying asset contract. This must be an ERC20-compatible contract (ERC20 or ERC777). */ constructor(IERC20 asset_) { - uint8 decimals_; - try IERC20Metadata(address(asset_)).decimals() returns (uint8 value) { - decimals_ = value; - } catch { - decimals_ = super.decimals(); - } - + (bool success, uint8 assetDecimals) = _tryGetAssetDecimals(asset_); + _decimals = success ? assetDecimals : super.decimals(); _asset = asset_; - _decimals = decimals_; } - /** @dev See {IERC20Metadata-decimals}. */ + /** + * @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; }