// SPDX-License-Identifier: Apache-2.0 // https://docs.soliditylang.org/en/v0.8.10/style-guide.html pragma solidity 0.8.11; import {IRegistry} from "src/interfaces/IRegistry.sol"; import {ImpactVault} from "src/vaults/ImpactVault.sol"; import {IMoolaLendingPool} from "src/interfaces/IMoolaLendingPool.sol"; import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol"; import {IERC4626Upgradeable} from "@openzeppelin/contracts-upgradeable/interfaces/IERC4626Upgradeable.sol"; import {SafeERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; /** * @title SpiralsCUSDImpactVault * @author @douglasqian @no40 * @notice Implementation of ImpactVault on the Celo USD stablecoin (cUSD). * cUSD deposited is deposited in Moola Market's cUSD lending pool as * collateral. No loan positions are taken out to ensure a good health factor * and minimizing chances of automatic liquidation. In return, this contract * receives a rebasing token (mcUSD) that can be redeemed 1-to-1 for cUSD. */ contract SpiralsCUSDImpactVault is ImpactVault { using SafeERC20Upgradeable for IERC20Upgradeable; event Receive(address indexed sender, uint256 indexed amount); event DependenciesUpdated( address indexed moolaLendingPool, address celoRegistry ); IRegistry internal c_celoRegistry; IMoolaLendingPool internal c_moolaLendingPool; IERC4626Upgradeable internal c_wmcUSD; receive() external payable { emit Receive(msg.sender, msg.value); } /** * Inititalize as ImpactVault. * asset -> cUSD * yieldAsset -> wmcUSD */ function initialize( address _moolaLendingPoolAddress, address _wmcUSDAddress, address _celoRegistryAddress, address _impactVaultManagerAddress ) external initializer { __Ownable_init(); __Pausable_init(); __ReentrancyGuard_init(); // Ensures that `_owner` is set. setDependencies(_moolaLendingPoolAddress, _celoRegistryAddress); // Ensures that `_moolaLendingPoolAddress` has been sanitized. IERC20Upgradeable cUSD = getStableToken(); c_wmcUSD = IERC4626Upgradeable(_wmcUSDAddress); require( c_wmcUSD.asset() == address(getMCUSD()), "NON_MATCHING_mcUSD_ADDRESS" ); __ERC20_init("Green Celo Dollar", "gcUSD"); __ImpactVault_init( cUSD, IERC20Upgradeable(c_wmcUSD), _impactVaultManagerAddress ); } /** * @notice Sets dependencies on contract (Moola contract addresses). */ function setDependencies( address _moolaLendingPoolAddress, address _celoRegistryAddress ) public onlyOwner { require( IRegistry(_celoRegistryAddress).getAddressForStringOrDie( "Validators" ) != address(0), "INVALID_REGISTRY_ADDRESS" ); c_moolaLendingPool = IMoolaLendingPool(_moolaLendingPoolAddress); c_celoRegistry = IRegistry(_celoRegistryAddress); // Keep this here becaue "getStableToken" has a dependency on // "c_celoRegistry" being set. require( IMoolaLendingPool(_moolaLendingPoolAddress) .getReserveData(address(getStableToken())) .aTokenAddress != address(0), "INVALID_LENDING_POOL" ); emit DependenciesUpdated( _moolaLendingPoolAddress, _celoRegistryAddress ); } /** * @dev Deposit cUSD into Moola lending pool and convert mcUSD to wmcUSD. */ function _stake(uint256 _amount) internal virtual override returns (uint256) { // Using SafeERC20Upgradeable // slither-disable-next-line unused-return asset.approve(address(c_moolaLendingPool), _amount); uint256 mcUSDBefore = getMCUSD().balanceOf(address(this)); c_moolaLendingPool.deposit(address(asset), _amount, address(this), 0); uint256 mcUSDReceived = getMCUSD().balanceOf(address(this)) - mcUSDBefore; // Convert mcUSD to wmcUSD. getMCUSD().approve(address(c_wmcUSD), mcUSDReceived); uint256 wmcUSDReceived = c_wmcUSD.deposit(mcUSDReceived, address(this)); return convertToAsset(wmcUSDReceived); } /** * @dev Withdraws cUSD from wmcUSD and then converts it to mcUSD from * Moola lending pool into cUSD to send to receiver. */ function _withdraw(address _receiver, uint256 _amount) internal virtual override { // Withdraw mcUSD assets to this vault. uint256 mcUSDBefore = getMCUSD().balanceOf(address(this)); c_wmcUSD.withdraw(_amount, address(this), address(this)); uint256 mcUSDReceived = getMCUSD().balanceOf(address(this)) - mcUSDBefore; // Burn mcUSD to withdraw cUSD to receiver. // withdraw here returns _amount, return value not needed // slither-disable-next-line unused-return c_moolaLendingPool.withdraw(address(asset), mcUSDReceived, _receiver); } /** * @dev mcUSD -> cUSD */ function convertToUSD(uint256 _amountAsset) public view virtual override returns (uint256 usdAmount) { return _amountAsset; } /** * @dev wmcUSD -> mcUSD */ function convertToAsset(uint256 _amountYieldAsset) public view virtual override returns (uint256 amountAsset) { return c_wmcUSD.convertToAssets(_amountYieldAsset); } /** * @dev mcUSD -> wmcUSD */ function convertToYieldAsset(uint256 _amountAsset) public view virtual override returns (uint256 amountYieldAsset) { return c_wmcUSD.convertToShares(_amountAsset); } /** * @dev Returns the StableToken contract (cUSD). */ function getStableToken() internal view returns (IERC20Upgradeable) { address stableTokenAddr = c_celoRegistry.getAddressForStringOrDie( "StableToken" ); return IERC20Upgradeable(stableTokenAddr); } /** * @dev Returns the Moola cUSD AToken contract (mcUSD). */ function getMCUSD() internal view returns (IERC20Upgradeable) { return IERC20Upgradeable( c_moolaLendingPool .getReserveData(address(getStableToken())) .aTokenAddress ); } /// @dev Get APR from Moola cUSD deposit pool (18 decimals). function getAPY() public view override returns (uint256) { uint256 liquidityRate = c_moolaLendingPool .getReserveData(address(getStableToken())) .currentLiquidityRate; // All Aave math performed in RAY (1e27). return liquidityRate / 1e9; } }