// SPDX-License-Identifier: Apache-2.0 // https://docs.soliditylang.org/en/v0.8.10/style-guide.html pragma solidity ^0.8.10; import "lib/forge-std/src/console.sol"; 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 "src/IMoolaLendingPoolAddressesProvider.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 ERC4626Upgradeable, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable { address internal addr_lendingPoolAddressesProvider = 0xD1088091A174d33412a968Fa34Cb67131188B332; address internal addr_asset = 0x765DE816845861e75A25fCA122bb6898B8B1282a; //cUSD event CUSDDeposit(address indexed caller, uint256 indexed assetsDeposited, uint256 indexed sharesIssued); event CUSDWithdraw(address caller, address indexed receiver, uint256 indexed assetsWithdrawn, uint256 indexed sharesBurned); function initialize() public initializer { __ERC4626_init(IERC20MetadataUpgradeable(addr_asset)); __ERC20_init("Spirals cUSD Vault Token", "spcUSD"); __Ownable_init(); __Pausable_init(); __ReentrancyGuard_init(); } receive() external payable {} /** * @dev Deposit/mint common workflow. Slighly modified to handle edge * case where caller is the contract itself (avoid ERC20 transfer). */ function _deposit( address caller, address receiver, uint256 assets, uint256 shares ) internal override { // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. // // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the // assets are transfered and before the shares are minted, which is a valid state. // slither-disable-next-line reentrancy-no-eth if (caller != address(this)) { // Skip if _from and _to are the same. SafeERC20Upgradeable.safeTransferFrom( IERC20Upgradeable(addr_asset), caller, address(this), assets ); } IMoolaLendingPool m_lendingPool = IMoolaLendingPool(IMoolaLendingPoolAddressesProvider(addr_lendingPoolAddressesProvider).getLendingPool()); _mDeposit(addr_asset, assets, m_lendingPool); _mint(receiver, shares); emit CUSDDeposit(caller, assets, shares); } function _withdraw( address caller, address receiver, address owner, uint256 assets, uint256 shares ) internal override { // If _asset is ERC777, `transferFrom` can trigger a reenterancy BEFORE the transfer happens through the // `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, // calls the vault, which is assumed not malicious. // // Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the // assets are transfered and before the shares are minted, which is a valid state. // slither-disable-next-line reentrancy-no-eth if (caller != owner) { _spendAllowance(owner, caller, shares); } _burn(owner, shares); IMoolaLendingPool m_lendingPool = IMoolaLendingPool(IMoolaLendingPoolAddressesProvider(addr_lendingPoolAddressesProvider).getLendingPool()); _mWithdraw(addr_asset, assets, receiver, m_lendingPool); emit CUSDWithdraw(caller, receiver, assets, shares); } // function getMToken(address lpap, address asset) internal returns (IERC20) { // return IERC20(IMoolaLendingPool(IMoolaLendingPoolAddressesProvider(lpap).getLendingPool()).getReserveData(asset).aTokenAddress); // } function _mDeposit(address addr_asset, uint256 amount, IMoolaLendingPool m_lendingPool) internal { // Transfer asset from contract -> moola require( IERC20(addr_asset).approve(address(m_lendingPool), amount), "CANNOT_APPROVE_POOL_TRANSFER" ); m_lendingPool.deposit(addr_asset, amount, address(this), 0); } function _mWithdraw(address addr_asset, uint256 amount, address toUser, IMoolaLendingPool m_lendingPool) internal { // Transfer asset from moola -> contract m_lendingPool.withdraw(addr_asset, amount, toUser); } function _convertToShares(uint256 assets, MathUpgradeable.Rounding) internal view override returns (uint256 shares) { return assets; // # assets = # shares for rebasing token } function _convertToAssets(uint256 shares, MathUpgradeable.Rounding) internal view override returns (uint256 assets) { return shares; // # assets = # shares for rebasing token } }