// SPDX-License-Identifier: Apache-2.0 // https://docs.soliditylang.org/en/v0.8.10/style-guide.html pragma solidity ^0.8.10; import {ERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; /** * @dev Extension of ERC-4626 where the underlying asset is staked. * This contract tracks the staked asset contract as well, and handles * conversions between the two assets. */ abstract contract StakedERC4626Upgradeable is ERC4626Upgradeable, PausableUpgradeable, OwnableUpgradeable, ReentrancyGuardUpgradeable { IERC20MetadataUpgradeable private _stakedAsset; /** * @dev Set the underlying asset and staked asset contracts. * These must be an ERC20-compatible contracts (ERC20 or ERC777). */ function __StakedERC4626_init(IERC20MetadataUpgradeable stakedAsset_) internal onlyInitializing { __StakedERC4626_init_unchained(stakedAsset_); } function __StakedERC4626_init_unchained( IERC20MetadataUpgradeable stakedAsset_ ) internal onlyInitializing { _stakedAsset = stakedAsset_; } /* * VAULT ENTRY POINTS */ /** * @dev Extension of original "deposit" method with a post-deposit hook * to convert vault assets into staked assets. */ function deposit(uint256 assets, address receiver) public virtual override whenNotPaused returns (uint256 shares) { shares = super.deposit(assets, receiver); stake(convertToAssets(shares)); return shares; } /** * @dev Post-deposit hook to convert asset to stakedAsset in vault. */ function stake(uint256 assets) internal virtual; /** * @dev Common codeĀ for withdraw flow. */ function _withdraw( address caller, address receiver, address owner, uint256 assets, uint256 shares ) internal virtual override whenNotPaused { if (caller != owner) { _spendAllowance(owner, caller, shares); } _checkCanWithdraw(caller, owner, receiver, shares, assets); // Burn shares before subsequent withdraws. Do this before other // steps to protect from reentrant attacks. _burn(owner, shares); assets = _withdrawFromStakedAsset(convertAssetsToStaked(assets)); _sendAssetToReceiver(owner, receiver, assets); emit Withdraw(caller, receiver, owner, assets, shares); } /** * @dev Revert if withdrawal invalid for given inputs. Optional. */ function _checkCanWithdraw( address caller, address owner, address receiver, uint256 shares, uint256 assets ) internal virtual {} /** * @dev Withdraw staked asset into asset in vault. Reverts on error. */ function _withdrawFromStakedAsset(uint256 stakedAssets) internal virtual returns (uint256 assets); /** * @dev Post-withdraw hook to send converted assets to receiver */ function _sendAssetToReceiver( address owner, address receiver, uint256 assets ) internal virtual; /* * VAULT MATH */ /** * @dev Returns total value of staked ERC4626 vault, denominated in `_asset` */ function totalAssets() public view virtual override returns (uint256 assets) { return IERC20MetadataUpgradeable(asset()).balanceOf(address(this)) + convertStakedToAssets(_stakedAsset.balanceOf(address(this))); } /** * @dev Returns a breakdown of asset vs. staked asset stored in the vault, * value denominated in `_asset` */ function getVaultBreakdown() external view virtual returns (uint256, uint256) { return ( IERC20MetadataUpgradeable(asset()).balanceOf(address(this)), convertStakedToAssets(_stakedAsset.balanceOf(address(this))) ); } function convertStakedToAssets(uint256 stakedAssets) public view virtual returns (uint256 assets); function convertAssetsToStaked(uint256 assets) public view virtual returns (uint256 stakedAssets); }