-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Permissioned ERC4626 that invests into Compound. Also a user with HARVEST_ROLE can claim the rewards that would be swapped for the asset and supplied again to Compound (accounting then as a profit for all the LPs). Also did a refactoring in SharedSmartVault, moving the permissions to PermissionedERC4626. Code should be complete, but no real tests have been made.
- Loading branch information
Showing
7 changed files
with
324 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.16; | ||
|
||
import {ICompoundV3} from "./interfaces/ICompoundV3.sol"; | ||
import {ICometRewards} from "./interfaces/ICometRewards.sol"; | ||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; | ||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import {MathUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; | ||
import {SwapLibrary} from "@ensuro/swaplibrary/contracts/SwapLibrary.sol"; | ||
import {PermissionedERC4626} from "./PermissionedERC4626.sol"; | ||
|
||
/** | ||
* @title SharedSmartVault | ||
* | ||
* @custom:security-contact security@ensuro.co | ||
* @author Ensuro | ||
*/ | ||
contract CompoundV3ERC4626 is PermissionedERC4626 { | ||
using SafeERC20 for IERC20Metadata; | ||
using SwapLibrary for SwapLibrary.SwapConfig; | ||
|
||
bytes32 public constant HARVEST_ROLE = keccak256("HARVEST_ROLE"); | ||
|
||
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable | ||
ICompoundV3 internal immutable _cToken; | ||
ICometRewards internal immutable _rewardsManager; | ||
|
||
SwapLibrary.SwapConfig _swapConfig; | ||
|
||
/// @custom:oz-upgrades-unsafe-allow constructor | ||
constructor(ICompoundV3 cToken_, ICometRewards rewardsManager_) { | ||
_cToken = cToken_; | ||
_rewardsManager = rewardsManager_; | ||
_disableInitializers(); | ||
} | ||
|
||
/** | ||
* @dev Initializes the SharedSmartVault | ||
*/ | ||
function initialize( | ||
string memory name_, | ||
string memory symbol_, | ||
address admin_, | ||
SwapLibrary.SwapConfig calldata swapConfig_ | ||
) public virtual initializer { | ||
__CompoundV3ERC4626_init(name_, symbol_, admin_, swapConfig_); | ||
} | ||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __CompoundV3ERC4626_init( | ||
string memory name_, | ||
string memory symbol_, | ||
address admin_, | ||
SwapLibrary.SwapConfig calldata swapConfig_ | ||
) internal onlyInitializing { | ||
__PermissionedERC4626_init(name_, symbol_, admin_, IERC20Upgradeable(_cToken.baseToken())); | ||
__CompoundV3ERC4626_init_unchained(swapConfig_); | ||
} | ||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __CompoundV3ERC4626_init_unchained(SwapLibrary.SwapConfig calldata swapConfig_) internal onlyInitializing { | ||
swapConfig_.validate(); | ||
_swapConfig = swapConfig_; | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-maxWithdraw}. | ||
*/ | ||
function maxWithdraw(address owner) public view virtual override returns (uint256) { | ||
if (_cToken.isWithdrawPaused()) return 0; | ||
return super.maxWithdraw(owner); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-maxRedeem}. | ||
*/ | ||
function maxRedeem(address owner) public view virtual override returns (uint256) { | ||
if (_cToken.isWithdrawPaused()) return 0; | ||
return super.maxRedeem(owner); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-maxDeposit}. | ||
*/ | ||
function maxDeposit(address owner) public view virtual override returns (uint256) { | ||
if (_cToken.isSupplyPaused()) return 0; | ||
return super.maxDeposit(owner); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-maxMint}. | ||
*/ | ||
function maxMint(address owner) public view virtual override returns (uint256) { | ||
if (_cToken.isSupplyPaused()) return 0; | ||
return super.maxMint(owner); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-totalAssets}. | ||
*/ | ||
function totalAssets() public view virtual override returns (uint256 assets) { | ||
return _cToken.balanceOf(address(this)); | ||
} | ||
|
||
function _withdraw( | ||
address caller, | ||
address receiver, | ||
address owner, | ||
uint256 assets, | ||
uint256 shares | ||
) internal virtual override { | ||
_cToken.withdraw(address(asset()), assets); | ||
super._withdraw(caller, receiver, owner, assets, shares); | ||
} | ||
|
||
function _deposit(address caller, address receiver, uint256 assets, uint256 shares) internal virtual override { | ||
// Transfers the assets from the caller and supplies to compound | ||
super._deposit(caller, receiver, assets, shares); | ||
_supply(assets); | ||
} | ||
|
||
function _supply(uint256 assets) internal { | ||
IERC20Metadata(asset()).approve(address(_cToken), assets); | ||
_cToken.supply(address(asset()), assets); | ||
} | ||
|
||
function harvestRewards(uint256 price) external onlyRole(HARVEST_ROLE) { | ||
(address reward, , ) = _rewardsManager.rewardConfig(address(_cToken)); | ||
if (reward == address(0)) return; | ||
_rewardsManager.claim(address(_cToken), address(this), true); | ||
|
||
uint256 earned = IERC20Metadata(reward).balanceOf(address(this)); | ||
uint256 reinvestAmount = _swapConfig.exactInput(reward, asset(), earned, price); | ||
_supply(reinvestAmount); | ||
} | ||
|
||
/** | ||
* @dev This empty reserved space is put in place to allow future versions to add new | ||
* variables without shifting down storage in the inheritance chain. | ||
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps | ||
*/ | ||
uint256[48] private __gap; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity 0.8.16; | ||
|
||
import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; | ||
import {ERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol"; | ||
import {ICallable} from "./interfaces/ICallable.sol"; | ||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; | ||
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol"; | ||
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; | ||
import {MathUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/math/MathUpgradeable.sol"; | ||
|
||
contract PermissionedERC4626 is AccessControlUpgradeable, UUPSUpgradeable, ERC4626Upgradeable { | ||
using SafeERC20 for IERC20Metadata; | ||
|
||
bytes32 public constant LP_ROLE = keccak256("LP_ROLE"); | ||
bytes32 public constant GUARDIAN_ROLE = keccak256("GUARDIAN_ROLE"); | ||
|
||
error InvalidAsset(address asset); | ||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __PermissionedERC4626_init( | ||
string memory name_, | ||
string memory symbol_, | ||
address admin_, | ||
IERC20Upgradeable asset_ | ||
) internal onlyInitializing { | ||
__UUPSUpgradeable_init(); | ||
__AccessControl_init(); | ||
if (address(asset_) == address(0)) revert InvalidAsset(address(0)); | ||
__ERC4626_init(asset_); | ||
__ERC20_init(name_, symbol_); | ||
__PermissionedERC4626_init_unchained(admin_); | ||
} | ||
|
||
// solhint-disable-next-line func-name-mixedcase | ||
function __PermissionedERC4626_init_unchained(address admin_) internal onlyInitializing { | ||
_setupRole(DEFAULT_ADMIN_ROLE, admin_); | ||
} | ||
|
||
// solhint-disable-next-line no-empty-blocks | ||
function _authorizeUpgrade(address newImpl) internal view override onlyRole(GUARDIAN_ROLE) {} | ||
|
||
/** | ||
* @dev See {IERC4626-mint}. | ||
*/ | ||
function mint(uint256 assets, address receiver) public virtual override onlyRole(LP_ROLE) returns (uint256) { | ||
return super.mint(assets, receiver); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-deposit}. | ||
*/ | ||
function deposit(uint256 assets, address receiver) public virtual override onlyRole(LP_ROLE) returns (uint256) { | ||
return super.deposit(assets, receiver); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-withdraw}. | ||
*/ | ||
function withdraw( | ||
uint256 assets, | ||
address receiver, | ||
address owner | ||
) public virtual override onlyRole(LP_ROLE) returns (uint256) { | ||
return super.withdraw(assets, receiver, owner); | ||
} | ||
|
||
/** | ||
* @dev See {IERC4626-redeem}. | ||
*/ | ||
function redeem( | ||
uint256 assets, | ||
address receiver, | ||
address owner | ||
) public virtual override onlyRole(LP_ROLE) returns (uint256) { | ||
return super.withdraw(assets, receiver, owner); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
|
||
/** | ||
* @dev Methods of the CometRewards interface we use | ||
* Full interface in | ||
* https://github.com/compound-finance/comet/blob/main/contracts/CometRewards.sol | ||
*/ | ||
interface ICometRewards { | ||
function rewardConfig(address cToken) external returns (address token, uint64 rescaleFactor, bool shouldUpscale); | ||
|
||
function claim(address comet, address src, bool shouldAccrue) external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
pragma solidity ^0.8.0; | ||
|
||
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; | ||
|
||
/** | ||
* @dev Methods of the CompoundV3 interface we use | ||
* Full interface in | ||
* https://github.com/compound-finance/comet/blob/main/contracts/CometExtInterface.sol | ||
* https://github.com/compound-finance/comet/blob/main/contracts/CometMainInterface.sol | ||
*/ | ||
interface ICompoundV3 is IERC20Metadata { | ||
/** | ||
* @dev Executes the collector and withdrawer task | ||
*/ | ||
function baseToken() external view returns (address); | ||
|
||
function isSupplyPaused() external view returns (bool); | ||
function isWithdrawPaused() external view returns (bool); | ||
|
||
function withdraw(address asset, uint amount) external; | ||
function supply(address asset, uint amount) external; | ||
} |
Oops, something went wrong.