diff --git a/contracts/FuseFeeDistributor.sol b/contracts/FuseFeeDistributor.sol index eddd2f1de..83b012121 100644 --- a/contracts/FuseFeeDistributor.sol +++ b/contracts/FuseFeeDistributor.sol @@ -10,18 +10,115 @@ import "./compound/ErrorReporter.sol"; import "./external/compound/IComptroller.sol"; import "./compound/CErc20Delegator.sol"; import "./compound/CErc20PluginDelegate.sol"; +import "./compound/CErc20WrappingDelegate.sol"; import "./midas/SafeOwnableUpgradeable.sol"; import "./utils/PatchedStorage.sol"; import "./oracles/BasePriceOracle.sol"; import { CTokenExtensionInterface } from "./compound/CTokenInterfaces.sol"; import { DiamondExtension, DiamondBase } from "./midas/DiamondExtension.sol"; +struct CDelegateUpgradeData { + address implementation; + bool allowResign; + bytes becomeImplementationData; +} + +abstract contract FeeDistributorStorage { + /** + * @notice The proportion of Fuse pool interest taken as a protocol fee (scaled by 1e18). + */ + uint256 public defaultInterestFeeRate; + + /** + * @dev Minimum borrow balance (in ETH) per user per Fuse pool asset (only checked on new borrows, not redemptions). + */ + uint256 public minBorrowEth; + + /** + * @dev Maximum supply balance (in ETH) per user per Fuse pool asset. + * No longer used as of `Rari-Capital/compound-protocol` version `fuse-v1.1.0`. + */ + uint256 public maxSupplyEth; + + /** + * @dev Maximum utilization rate (scaled by 1e18) for Fuse pool assets (only checked on new borrows, not redemptions). + * No longer used as of `Rari-Capital/compound-protocol` version `fuse-v1.1.0`. + */ + uint256 public maxUtilizationRate; + + /** + * @dev Whitelisted Comptroller implementation contract addresses for each existing implementation. + */ + mapping(address => mapping(address => bool)) public comptrollerImplementationWhitelist; + + /** + * @dev Whitelisted CErc20Delegate implementation contract addresses and `allowResign` values for each existing implementation. + */ + mapping(address => mapping(address => mapping(bool => bool))) public cErc20DelegateWhitelist; + + /** + * @dev Whitelisted CEtherDelegate implementation contract addresses and `allowResign` values for each existing implementation. + */ + /// keep this in the storage to not break the layout + mapping(address => mapping(address => mapping(bool => bool))) public cEtherDelegateWhitelist; + + /** + * @dev Latest Comptroller implementation for each existing implementation. + */ + mapping(address => address) internal _latestComptrollerImplementation; + /** + * @dev Latest CErc20Delegate implementation for each existing implementation. + */ + mapping(address => CDelegateUpgradeData) public _latestCErc20Delegate; + + /** + * @dev Latest CEtherDelegate implementation for each existing implementation. + */ + /// keep this in the storage to not break the layout + mapping(address => CDelegateUpgradeData) public _latestCEtherDelegate; + + /** + * @notice Maps Unitroller (Comptroller proxy) addresses to the proportion of Fuse pool interest taken as a protocol fee (scaled by 1e18). + * @dev A value of 0 means unset whereas a negative value means 0. + */ + mapping(address => int256) public customInterestFeeRates; + + /** + * @dev used as salt for the creation of new markets + */ + uint256 public marketsCounter; + + /** + * @dev Latest Plugin implementation for each existing implementation. + */ + mapping(address => address) public _latestPluginImplementation; + + /** + * @dev Whitelisted Plugin implementation contract addresses for each existing implementation. + */ + mapping(address => mapping(address => bool)) public pluginImplementationWhitelist; + + mapping(address => DiamondExtension[]) public comptrollerExtensions; + + mapping(address => DiamondExtension[]) public cErc20DelegateExtensions; + + /** + * @dev Whitelisted erc20Wrapping implementation contract addresses for each existing implementation. + */ + mapping(address => mapping(address => bool)) public erc20WrapperUpgradeWhitelist; + + /** + * @dev Latest erc20Wrapping implementation for each existing implementation. + */ + mapping(address => address) public _latestERC20WrapperForUnderlying; +} + /** * @title FuseFeeDistributor * @author David Lucid (https://github.com/davidlucid) * @notice FuseFeeDistributor controls and receives protocol fees from Fuse pools and relays admin actions to Fuse pools. */ -contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { +contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage, FeeDistributorStorage { using AddressUpgradeable for address; using SafeERC20Upgradeable for IERC20Upgradeable; @@ -37,11 +134,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { maxUtilizationRate = type(uint256).max; } - /** - * @notice The proportion of Fuse pool interest taken as a protocol fee (scaled by 1e18). - */ - uint256 public defaultInterestFeeRate; - /** * @dev Sets the default proportion of Fuse pool interest taken as a protocol fee. * @param _defaultInterestFeeRate The default proportion of Fuse pool interest taken as a protocol fee (scaled by 1e18). @@ -69,23 +161,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { } } - /** - * @dev Minimum borrow balance (in ETH) per user per Fuse pool asset (only checked on new borrows, not redemptions). - */ - uint256 public minBorrowEth; - - /** - * @dev Maximum supply balance (in ETH) per user per Fuse pool asset. - * No longer used as of `Rari-Capital/compound-protocol` version `fuse-v1.1.0`. - */ - uint256 public maxSupplyEth; - - /** - * @dev Maximum utilization rate (scaled by 1e18) for Fuse pool assets (only checked on new borrows, not redemptions). - * No longer used as of `Rari-Capital/compound-protocol` version `fuse-v1.1.0`. - */ - uint256 public maxUtilizationRate; - /** * @dev Sets the proportion of Fuse pool interest taken as a protocol fee. * @param _minBorrowEth Minimum borrow balance (in ETH) per user per Fuse pool asset (only checked on new borrows, not redemptions). @@ -159,11 +234,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { return proxy; } - /** - * @dev Whitelisted Comptroller implementation contract addresses for each existing implementation. - */ - mapping(address => mapping(address => bool)) public comptrollerImplementationWhitelist; - /** * @dev Adds/removes Comptroller implementations to the whitelist. * @param oldImplementations The old `Comptroller` implementation addresses to upgrade from for each `newImplementations` to upgrade to. @@ -185,11 +255,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { comptrollerImplementationWhitelist[oldImplementations[i]][newImplementations[i]] = statuses[i]; } - /** - * @dev Whitelisted CErc20Delegate implementation contract addresses and `allowResign` values for each existing implementation. - */ - mapping(address => mapping(address => mapping(bool => bool))) public cErc20DelegateWhitelist; - /** * @dev Adds/removes CErc20Delegate implementations to the whitelist. * @param oldImplementations The old `CErc20Delegate` implementation addresses to upgrade from for each `newImplementations` to upgrade to. @@ -214,17 +279,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { cErc20DelegateWhitelist[oldImplementations[i]][newImplementations[i]][allowResign[i]] = statuses[i]; } - /** - * @dev Whitelisted CEtherDelegate implementation contract addresses and `allowResign` values for each existing implementation. - */ - /// keep this in the storage to not break the layout - mapping(address => mapping(address => mapping(bool => bool))) public cEtherDelegateWhitelist; - - /** - * @dev Latest Comptroller implementation for each existing implementation. - */ - mapping(address => address) internal _latestComptrollerImplementation; - /** * @dev Latest Comptroller implementation for each existing implementation. */ @@ -247,23 +301,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { _latestComptrollerImplementation[oldImplementation] = newImplementation; } - struct CDelegateUpgradeData { - address implementation; - bool allowResign; - bytes becomeImplementationData; - } - - /** - * @dev Latest CErc20Delegate implementation for each existing implementation. - */ - mapping(address => CDelegateUpgradeData) public _latestCErc20Delegate; - - /** - * @dev Latest CEtherDelegate implementation for each existing implementation. - */ - /// keep this in the storage to not break the layout - mapping(address => CDelegateUpgradeData) public _latestCEtherDelegate; - /** * @dev Latest CErc20Delegate implementation for each existing implementation. */ @@ -304,27 +341,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { ); } - /** - * @notice Maps Unitroller (Comptroller proxy) addresses to the proportion of Fuse pool interest taken as a protocol fee (scaled by 1e18). - * @dev A value of 0 means unset whereas a negative value means 0. - */ - mapping(address => int256) public customInterestFeeRates; - - /** - * @dev used as salt for the creation of new markets - */ - uint256 public marketsCounter; - - /** - * @dev Latest Plugin implementation for each existing implementation. - */ - mapping(address => address) public _latestPluginImplementation; - - /** - * @dev Whitelisted Plugin implementation contract addresses for each existing implementation. - */ - mapping(address => mapping(address => bool)) public pluginImplementationWhitelist; - /** * @dev Adds/removes plugin implementations to the whitelist. * @param oldImplementations The old plugin implementation addresses to upgrade from for each `newImplementations` to upgrade to. @@ -346,6 +362,19 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { pluginImplementationWhitelist[oldImplementations[i]][newImplementations[i]] = statuses[i]; } + function _editERC20WrapperUpgradeWhitelist( + address[] calldata oldWrappers, + address[] calldata newWrappers, + bool[] calldata statuses + ) external onlyOwner { + require( + newWrappers.length > 0 && newWrappers.length == oldWrappers.length && newWrappers.length == statuses.length, + "empty array or lengths not equal" + ); + for (uint256 i = 0; i < newWrappers.length; i++) + erc20WrapperUpgradeWhitelist[oldWrappers[i]][newWrappers[i]] = statuses[i]; + } + /** * @dev Latest Plugin implementation for each existing implementation. */ @@ -365,6 +394,17 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { _latestPluginImplementation[oldImplementation] = newImplementation; } + function latestERC20WrapperForUnderlying(address oldWrapper) external view returns (address) { + return + _latestERC20WrapperForUnderlying[oldWrapper] != address(0) + ? _latestERC20WrapperForUnderlying[oldWrapper] + : oldWrapper; + } + + function _setLatestERC20WrapperForUnderlying(address oldWrapper, address newWrapper) external onlyOwner { + _latestERC20WrapperForUnderlying[oldWrapper] = newWrapper; + } + /** * @dev Upgrades a plugin of a CErc20PluginDelegate market to the latest implementation * @param cDelegator the proxy address @@ -406,8 +446,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { customInterestFeeRates[comptroller] = rate; } - mapping(address => DiamondExtension[]) public comptrollerExtensions; - function getComptrollerExtensions(address comptroller) external view returns (DiamondExtension[] memory) { return comptrollerExtensions[comptroller]; } @@ -424,8 +462,6 @@ contract FuseFeeDistributor is SafeOwnableUpgradeable, PatchedStorage { DiamondBase(pool)._registerExtension(extensionToAdd, extensionToReplace); } - mapping(address => DiamondExtension[]) public cErc20DelegateExtensions; - function getCErc20DelegateExtensions(address cErc20Delegate) external view returns (DiamondExtension[] memory) { return cErc20DelegateExtensions[cErc20Delegate]; } diff --git a/contracts/compound/CErc20PluginDelegate.sol b/contracts/compound/CErc20PluginDelegate.sol index 659cf4415..8d1d05097 100644 --- a/contracts/compound/CErc20PluginDelegate.sol +++ b/contracts/compound/CErc20PluginDelegate.sol @@ -59,6 +59,8 @@ contract CErc20PluginDelegate is CErc20Delegate { plugin.redeem(plugin.balanceOf(address(this)), address(this), address(this)); } + emit NewPluginImplementation(address(plugin), _plugin); + plugin = IERC4626(_plugin); EIP20Interface(underlying).approve(_plugin, type(uint256).max); @@ -67,8 +69,6 @@ contract CErc20PluginDelegate is CErc20Delegate { if (amount != 0) { deposit(amount); } - - emit NewPluginImplementation(address(plugin), _plugin); } /*** CToken Overrides ***/ diff --git a/contracts/compound/CErc20WrappingDelegate.sol b/contracts/compound/CErc20WrappingDelegate.sol new file mode 100644 index 000000000..7001faab5 --- /dev/null +++ b/contracts/compound/CErc20WrappingDelegate.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +import { CErc20Delegate } from "./CErc20Delegate.sol"; +import { EIP20Interface } from "./EIP20Interface.sol"; +import { IFuseFeeDistributor } from "../compound/IFuseFeeDistributor.sol"; +import { MidasERC20Wrapper } from "../midas/MidasERC20Wrapper.sol"; + +contract CErc20WrappingDelegate is CErc20Delegate { + event NewErc20WrappingImplementation(address oldImpl, address newImpl); + + MidasERC20Wrapper public underlyingWrapper; + + function _becomeImplementation(bytes memory data) public virtual override { + require(msg.sender == address(this) || hasAdminRights(), "only self and admins can call _becomeImplementation"); + + if (address(underlyingWrapper) == address(0)) { + EIP20Interface asErc20 = EIP20Interface(underlying); + underlyingWrapper = new MidasERC20Wrapper(underlying, asErc20.name(), asErc20.symbol(), asErc20.decimals()); + underlying = address(underlyingWrapper); + } else { + address _newWrapper = abi.decode(data, (address)); + if (_newWrapper == address(0)) { + _newWrapper = IFuseFeeDistributor(fuseAdmin).latestERC20WrapperForUnderlying(address(underlyingWrapper)); + } + + if (_newWrapper != address(0) && _newWrapper != address(underlyingWrapper)) { + _updateWrapper(_newWrapper); + } + } + } + + function _updateWrapper(address _newWrapper) public { + require(msg.sender == address(this) || hasAdminRights(), "only self and admins can call _updateWrapper"); + + address oldImplementation = address(underlyingWrapper) != address(0) ? address(underlyingWrapper) : _newWrapper; + + require( + IFuseFeeDistributor(fuseAdmin).erc20WrapperUpgradeWhitelist(oldImplementation, _newWrapper), + "erc20Wrapping implementation not whitelisted" + ); + + if (address(underlyingWrapper) != address(0) && underlyingWrapper.balanceOf(address(this)) != 0) { + doTransferOut(address(this), underlyingWrapper.balanceOf(address(this))); + } + + emit NewErc20WrappingImplementation(address(underlyingWrapper), _newWrapper); + + underlyingWrapper = MidasERC20Wrapper(_newWrapper); + + EIP20Interface(underlying).approve(_newWrapper, type(uint256).max); + + uint256 amount = EIP20Interface(underlying).balanceOf(address(this)); + + if (amount != 0) { + doTransferIn(address(this), amount); + } + } + + function doTransferIn(address from, uint256 amount) internal virtual override returns (uint256) { + require(EIP20Interface(underlying).transferFrom(from, address(this), amount), "send"); + require(EIP20Interface(underlying).approve(address(underlyingWrapper), amount), "approve wrapper"); + underlyingWrapper.depositFor(address(this), amount); + return amount; + } + + function doTransferOut(address to, uint256 amount) internal virtual override { + underlyingWrapper.withdrawTo(to, amount); + } + + function contractType() external pure virtual override returns (string memory) { + return "CErc20WrappingDelegate"; + } +} diff --git a/contracts/compound/Comptroller.sol b/contracts/compound/Comptroller.sol index cc56ae55e..79102b520 100644 --- a/contracts/compound/Comptroller.sol +++ b/contracts/compound/Comptroller.sol @@ -2,6 +2,7 @@ pragma solidity >=0.8.0; import { CTokenInterface, CErc20Interface } from "./CTokenInterfaces.sol"; +import { CDelegateInterface } from "./CDelegateInterface.sol"; import { ComptrollerErrorReporter } from "./ErrorReporter.sol"; import { Exponential } from "./Exponential.sol"; import { PriceOracle } from "./PriceOracle.sol"; @@ -1281,14 +1282,23 @@ contract Comptroller is ComptrollerV3Storage, ComptrollerInterface, ComptrollerE fuseAdminHasRights = true; // Deploy via Fuse admin - CTokenInterface cToken = CTokenInterface(IFuseFeeDistributor(fuseAdmin).deployCErc20(constructorData)); + address marketAddress = IFuseFeeDistributor(fuseAdmin).deployCErc20(constructorData); // Reset Fuse admin rights to the original value fuseAdminHasRights = oldFuseAdminHasRights; - // Support market here in the Comptroller - uint256 err = _supportMarket(cToken); - // Set collateral factor - return err == uint256(Error.NO_ERROR) ? _setCollateralFactor(cToken, collateralFactorMantissa) : err; + CTokenInterface cToken = CTokenInterface(marketAddress); + if (!compareStrings(CDelegateInterface(marketAddress).contractType(), "CErc20WrappingDelegate")) { + // Support market here in the Comptroller + uint256 err = _supportMarket(cToken); + // Set collateral factor + return err == uint256(Error.NO_ERROR) ? _setCollateralFactor(cToken, collateralFactorMantissa) : err; + } + + return uint256(Error.NO_ERROR); + } + + function compareStrings(string memory a, string memory b) public pure returns (bool) { + return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b)))); } /** diff --git a/contracts/compound/IFuseFeeDistributor.sol b/contracts/compound/IFuseFeeDistributor.sol index 63d5344f7..37b41c7a2 100644 --- a/contracts/compound/IFuseFeeDistributor.sol +++ b/contracts/compound/IFuseFeeDistributor.sol @@ -20,6 +20,11 @@ interface IFuseFeeDistributor { view returns (bool); + function erc20WrapperUpgradeWhitelist(address oldImplementation, address newImplementation) + external + view + returns (bool); + function cErc20DelegateWhitelist( address oldImplementation, address newImplementation, @@ -39,6 +44,8 @@ interface IFuseFeeDistributor { function latestPluginImplementation(address oldImplementation) external view returns (address); + function latestERC20WrapperForUnderlying(address oldImplementation) external view returns (address); + function getComptrollerExtensions(address comptroller) external view returns (address[] memory); function getCErc20DelegateExtensions(address cErc20Delegate) external view returns (address[] memory); diff --git a/contracts/midas/MidasERC20Wrapper.sol b/contracts/midas/MidasERC20Wrapper.sol new file mode 100644 index 000000000..4c2ac8240 --- /dev/null +++ b/contracts/midas/MidasERC20Wrapper.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +import { ERC20Wrapper, ERC20 } from "openzeppelin-contracts/token/ERC20/extensions/ERC20Wrapper.sol"; +import { IERC20 } from "openzeppelin-contracts/token/ERC20/IERC20.sol"; + +contract MidasERC20Wrapper is ERC20Wrapper { + address private _owner; + uint8 private _decimals; + + // string private _nameOverride; + // string private _symbolOverride; + + constructor( + address underlyingToken_, + string memory name_, + string memory symbol_, + uint8 decimals_ + ) ERC20(name_, symbol_) ERC20Wrapper(IERC20(underlyingToken_)) { + _owner = msg.sender; + _decimals = decimals_; + } + + function decimals() public view override returns (uint8) { + return _decimals; + } + + function recover(address token) public returns (uint256) { + if (token == address(this)) { + return _recover(_owner); + } else { + uint256 balance = IERC20(token).balanceOf(address(this)); + return IERC20(token).transfer(_owner, balance) ? balance : 0; + } + } + + // function name() public view virtual override returns (string memory) { + // if (bytes(_nameOverride).length == 0) { + // return super.name(); + // } else { + // return _nameOverride; + // } + // } + // + // function symbol() public view virtual override returns (string memory) { + // if (bytes(_symbolOverride).length == 0) { + // return super.symbol(); + // } else { + // return _symbolOverride; + // } + // } + // + // function _overrideNameAndSymbol(string memory name_, string memory symbol_) external { + // require(msg.sender == _owner, "!owner"); + // _name = name_; + // _symbol = symbol_; + // } +} diff --git a/contracts/test/ContractsUpgradesTest.t.sol b/contracts/test/ContractsUpgradesTest.t.sol index 5ccfabb60..c21f4704f 100644 --- a/contracts/test/ContractsUpgradesTest.t.sol +++ b/contracts/test/ContractsUpgradesTest.t.sol @@ -43,8 +43,8 @@ contract ContractsUpgradesTest is BaseTest { } // after upgrade - FusePoolDirectory newImpl = FusePoolDirectory(contractToTest); - address ownerAfter = newImpl.owner(); + FusePoolDirectory fpd = FusePoolDirectory(contractToTest); + address ownerAfter = fpd.owner(); emit log_address(ownerAfter); (, FusePoolDirectory.FusePool[] memory poolsAfter) = oldImpl.getActivePools(); @@ -57,6 +57,7 @@ contract ContractsUpgradesTest is BaseTest { function testFuseFeeDistributorUpgrade() public fork(BSC_MAINNET) { address oldCercDelegate = 0x94C50805bC16737ead84e25Cd5Aa956bCE04BBDF; + address oldComptrollerImpl = 0xC634898F59391bfd66eDDC9eB4298A8E9643596c; // before upgrade FuseFeeDistributor ffdProxy = FuseFeeDistributor(payable(ap.getAddress("FuseFeeDistributor"))); @@ -71,6 +72,9 @@ contract ContractsUpgradesTest is BaseTest { // if (whitelistedBefore) emit log("whitelisted before"); // else emit log("should be whitelisted"); + address comptrollerExtensionBefore = address(ffdProxy.comptrollerExtensions(oldComptrollerImpl, 0)); + emit log_named_address("comp ext before", comptrollerExtensionBefore); + // upgrade { FuseFeeDistributor newImpl = new FuseFeeDistributor(); @@ -94,12 +98,15 @@ contract ContractsUpgradesTest is BaseTest { emit log_address(ownerAfter); // if (whitelistedAfter) emit log("whitelisted After"); // else emit log("should be whitelisted"); + address comptrollerExtensionAfter = address(ffdProxy.comptrollerExtensions(oldComptrollerImpl, 0)); + emit log_named_address("comp ext after", comptrollerExtensionAfter); assertEq(latestCErc20DelegateBefore, latestCErc20DelegateAfter, "latest delegates do not match"); assertEq(marketsCounterBefore, marketsCounterAfter, "markets counter does not match"); // assertEq(whitelistedBefore, whitelistedAfter, "whitelisted status does not match"); assertEq(ownerBefore, ownerAfter, "owner mismatch"); + assertEq(comptrollerExtensionBefore, comptrollerExtensionAfter, "comp ext mismatch"); } function testMarketsLatestImplementationsBsc() public fork(BSC_MAINNET) { diff --git a/contracts/test/DeployMarkets.t.sol b/contracts/test/DeployMarkets.t.sol index c4b0b7aad..63f090e00 100644 --- a/contracts/test/DeployMarkets.t.sol +++ b/contracts/test/DeployMarkets.t.sol @@ -21,6 +21,7 @@ import { ComptrollerFirstExtension } from "../compound/ComptrollerFirstExtension import { CErc20Delegate } from "../compound/CErc20Delegate.sol"; import { CErc20PluginDelegate } from "../compound/CErc20PluginDelegate.sol"; import { CErc20PluginRewardsDelegate } from "../compound/CErc20PluginRewardsDelegate.sol"; +import { CErc20WrappingDelegate } from "../compound/CErc20WrappingDelegate.sol"; import { CErc20Delegator } from "../compound/CErc20Delegator.sol"; import { ComptrollerInterface } from "../compound/ComptrollerInterface.sol"; import { InterestRateModel } from "../compound/InterestRateModel.sol"; @@ -31,6 +32,7 @@ import { MockERC4626 } from "../midas/strategies/MockERC4626.sol"; import { MockERC4626Dynamic } from "../midas/strategies/MockERC4626Dynamic.sol"; import { CTokenFirstExtension, DiamondExtension } from "../compound/CTokenFirstExtension.sol"; import { MidasFlywheelCore } from "../midas/strategies/flywheel/MidasFlywheelCore.sol"; +import { MidasERC20Wrapper } from "../midas/MidasERC20Wrapper.sol"; contract DeployMarketsTest is Test { MockERC20 underlyingToken; @@ -42,6 +44,7 @@ contract DeployMarketsTest is Test { CErc20Delegate cErc20Delegate; CErc20PluginDelegate cErc20PluginDelegate; CErc20PluginRewardsDelegate cErc20PluginRewardsDelegate; + CErc20WrappingDelegate cErc20WrappingDelegate; MockERC4626 mockERC4626; MockERC4626Dynamic mockERC4626Dynamic; @@ -77,21 +80,19 @@ contract DeployMarketsTest is Test { cErc20Delegate = new CErc20Delegate(); cErc20PluginDelegate = new CErc20PluginDelegate(); cErc20PluginRewardsDelegate = new CErc20PluginRewardsDelegate(); + cErc20WrappingDelegate = new CErc20WrappingDelegate(); DiamondExtension[] memory cErc20DelegateExtensions = new DiamondExtension[](1); cErc20DelegateExtensions[0] = new CTokenFirstExtension(); fuseAdmin._setCErc20DelegateExtensions(address(cErc20Delegate), cErc20DelegateExtensions); fuseAdmin._setCErc20DelegateExtensions(address(cErc20PluginDelegate), cErc20DelegateExtensions); fuseAdmin._setCErc20DelegateExtensions(address(cErc20PluginRewardsDelegate), cErc20DelegateExtensions); - - for (uint256 i = 0; i < 7; i++) { - t.push(true); - f.push(false); - } + fuseAdmin._setCErc20DelegateExtensions(address(cErc20WrappingDelegate), cErc20DelegateExtensions); oldCErC20Implementations.push(address(0)); oldCErC20Implementations.push(address(0)); oldCErC20Implementations.push(address(0)); + oldCErC20Implementations.push(address(0)); oldCErC20Implementations.push(address(cErc20Delegate)); oldCErC20Implementations.push(address(cErc20Delegate)); oldCErC20Implementations.push(address(cErc20PluginDelegate)); @@ -100,11 +101,17 @@ contract DeployMarketsTest is Test { newCErc20Implementations.push(address(cErc20Delegate)); newCErc20Implementations.push(address(cErc20PluginDelegate)); newCErc20Implementations.push(address(cErc20PluginRewardsDelegate)); + newCErc20Implementations.push(address(cErc20WrappingDelegate)); newCErc20Implementations.push(address(cErc20PluginDelegate)); newCErc20Implementations.push(address(cErc20PluginRewardsDelegate)); newCErc20Implementations.push(address(cErc20PluginDelegate)); newCErc20Implementations.push(address(cErc20PluginRewardsDelegate)); + for (uint256 i = 0; i < newCErc20Implementations.length; i++) { + t.push(true); + f.push(false); + } + fuseAdmin._editCErc20DelegateWhitelist(oldCErC20Implementations, newCErc20Implementations, f, t); } @@ -269,6 +276,63 @@ contract DeployMarketsTest is Test { assertEq(underlyingToken.balanceOf(address(mockERC4626Dynamic)), 10000000); } + function testDeployCErc20WrappingDelegate() public { + vm.roll(1); + comptroller._deployMarket( + false, + abi.encode( + address(underlyingToken), + comptroller, + payable(address(fuseAdmin)), + InterestRateModel(address(interestModel)), + "cUnderlyingToken", + "CUT", + address(cErc20WrappingDelegate), + abi.encode(address(0)), + uint256(1), + uint256(0) + ), + 0.9e18 + ); + + // TODO configure the oracle + // then call support market and set collateral factor + + CTokenInterface[] memory allMarkets = comptroller.asComptrollerFirstExtension().getAllMarkets(); + CErc20WrappingDelegate cToken = CErc20WrappingDelegate(address(allMarkets[allMarkets.length - 1])); + + underlyingToken.approve(address(cToken), 1e36); + address[] memory cTokens = new address[](1); + cTokens[0] = address(cToken); + comptroller.enterMarkets(cTokens); + vm.roll(1); + + cToken.mint(10000000); + assertEq(cToken.totalSupply(), 10000000 * 5); + MidasERC20Wrapper wrapper = cToken.underlyingWrapper(); + assertEq(wrapper.balanceOf(address(cToken)), 10000000); + assertEq(underlyingToken.balanceOf(address(wrapper)), 10000000); + + // deploy a second of the same underlying + vm.roll(1); + comptroller._deployMarket( + false, + abi.encode( + address(underlyingToken), + comptroller, + payable(address(fuseAdmin)), + InterestRateModel(address(interestModel)), + "cUnderlyingToken", + "CUT", + address(cErc20WrappingDelegate), + abi.encode(address(0)), + uint256(1), + uint256(0) + ), + 0.9e18 + ); + } + function testAutImplementationCErc20Delegate() public { mockERC4626 = new MockERC4626(ERC20(address(underlyingToken)));