// SPDX-License-Identifier: Apache-2.0 // https://docs.soliditylang.org/en/v0.8.10/style-guide.html pragma solidity ^0.8.10; import {IERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; import {ERC20} from "lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {IERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; import {ERC4626Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {IERC20MetadataUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/IERC20MetadataUpgradeable.sol"; import {SafeERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol"; import "lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol"; import "lib/openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol"; import "lib/openzeppelin-contracts-upgradeable/contracts/security/ReentrancyGuardUpgradeable.sol"; import {MathUpgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/utils/math/MathUpgradeable.sol"; import "src/IMoolaLendingPool.sol"; import {IERC20Upgradeable} from "lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/IERC20Upgradeable.sol"; import "src/IStableToken.sol"; import "src/StakedERC4626Upgradeable.sol"; import "src/IRegistry.sol"; /// @title SpiralsCUSDVault /// @author @douglasqian @no40 /// @notice This is a modification of the EIP-4626 tokenized vault standard /// for yield-bearing tokens where the yield accrued on cUSD via a lending protocol /// is held by the contract, giving the user a derivative token to redeem them at a /// later date. /// contract SpiralsCUSDVault is StakedERC4626Upgradeable { event Receive(address indexed sender, uint256 indexed amount); event DependenciesUpdated(address indexed moolaLendingPool); IRegistry internal c_celoRegistry = IRegistry(0x000000000000000000000000000000000000ce10); IMoolaLendingPool internal c_moolaLendingPool; function initialize(address _moolaLendingPoolAddress) external initializer { __Ownable_init(); __Pausable_init(); __ReentrancyGuard_init(); // Ensures that `_owner` is set. setDependencies(_moolaLendingPoolAddress); // Ensures that `_moolaLendingPoolAddress` has been sanitized. address cUSDAddress = address(getStableToken()); __ERC20_init("Spirals cUSD Vault Token", "spcUSD"); __ERC4626_init(IERC20MetadataUpgradeable(cUSDAddress)); __StakedERC4626_init( IERC20MetadataUpgradeable( c_moolaLendingPool.getReserveData(cUSDAddress).aTokenAddress ) ); } receive() external payable { emit Receive(msg.sender, msg.value); } /** * @notice Sets dependencies on contract (Moola contract addresses). */ // lendingPoolAddrProvider 0xD1088091A174d33412a968Fa34Cb67131188B332 // cUSD 0x765DE816845861e75A25fCA122bb6898B8B1282a function setDependencies(address _moolaLendingPoolAddress) public onlyOwner { require( IMoolaLendingPool(_moolaLendingPoolAddress) .getReserveData(address(getStableToken())) .aTokenAddress != address(0), "INVALID_LENDING_POOL" ); c_moolaLendingPool = IMoolaLendingPool(_moolaLendingPoolAddress); emit DependenciesUpdated(_moolaLendingPoolAddress); } /** * @dev Deposit cUSD into Moola cUSD lending pool. */ function stake(uint256 assets) internal virtual override { // Approve cUSD from contract -> Moola LP SafeERC20Upgradeable.safeApprove( IERC20Upgradeable(asset()), address(c_moolaLendingPool), assets ); c_moolaLendingPool.deposit(asset(), assets, address(this), 0); } /** * @dev Withdraw cUSD from Moola lending pool. */ function _withdrawFromStakedAsset(uint256 stakedAssets) internal virtual override returns (uint256 assets) { // c_moolaLendingPool.withdraw(asset(), stakedAssets, address(this)); return convertStakedToAssets(stakedAssets); } /** * @dev Send cUSD to receiver. */ function _sendAssetToReceiver( address, address receiver, uint256 assets ) internal virtual override { // TODO might need safeApprove? // TODO might be able to lp.withdraw straight into receiver? // SafeERC20Upgradeable.safeTransfer(asset(), receiver, assets); uint256 amountWithdrawn = c_moolaLendingPool.withdraw( asset(), convertAssetsToStaked(assets), receiver ); } /** * @dev cUSD -> spcUSD */ function _convertToShares(uint256 assets, MathUpgradeable.Rounding) internal pure override returns (uint256 shares) { return assets; // # assets = # shares for rebasing token } /** * @dev spcUSD -> cUSD */ function _convertToAssets(uint256 shares, MathUpgradeable.Rounding) internal pure override returns (uint256 assets) { return shares; // # assets = # shares for rebasing token } /** * @dev mcUSD -> cUSD */ function convertStakedToAssets(uint256 stakedAssets) public view virtual override returns (uint256) { return stakedAssets; } /** * @dev cUSD -> mcUSD */ function convertAssetsToStaked(uint256 assets) public view virtual override returns (uint256) { return assets; } /// @dev Returns a StableToken.sol interface for interacting with the smart contract. function getStableToken() internal view returns (IStableToken) { address stableTokenAddr = c_celoRegistry.getAddressForStringOrDie( "StableToken" ); return IStableToken(stableTokenAddr); } }