From 8e74f758620dac39e0148020b127e97d62f3cd1a Mon Sep 17 00:00:00 2001 From: Chinmay Chougaonkar Date: Mon, 7 Mar 2022 20:10:17 +0530 Subject: [PATCH] refactored compound-import contracts --- .../connectors/compound-import/events.sol | 4 +- .../connectors/compound-import/helpers.sol | 179 ++++++++++++++++- .../connectors/compound-import/main.sol | 184 +----------------- 3 files changed, 183 insertions(+), 184 deletions(-) diff --git a/contracts/mainnet/connectors/compound-import/events.sol b/contracts/mainnet/connectors/compound-import/events.sol index d3125959..09e3e8f2 100644 --- a/contracts/mainnet/connectors/compound-import/events.sol +++ b/contracts/mainnet/connectors/compound-import/events.sol @@ -7,7 +7,7 @@ contract Events { address[] ctokens, string[] supplyIds, string[] borrowIds, - uint[] supplyAmts, - uint[] borrowAmts + uint256[] supplyAmts, + uint256[] borrowAmts ); } \ No newline at end of file diff --git a/contracts/mainnet/connectors/compound-import/helpers.sol b/contracts/mainnet/connectors/compound-import/helpers.sol index c6042b9c..78e600b1 100644 --- a/contracts/mainnet/connectors/compound-import/helpers.sol +++ b/contracts/mainnet/connectors/compound-import/helpers.sol @@ -2,7 +2,8 @@ pragma solidity ^0.7.6; import { DSMath } from "../../common/math.sol"; import { Basic } from "../../common/basic.sol"; -import { ComptrollerInterface, CompoundMappingInterface, CETHInterface } from "./interface.sol"; +import { TokenInterface, AccountInterface } from "../../common/interfaces.sol"; +import { ComptrollerInterface, CompoundMappingInterface, CETHInterface, CTokenInterface } from "./interface.sol"; abstract contract Helpers is DSMath, Basic { @@ -21,6 +22,24 @@ abstract contract Helpers is DSMath, Basic { */ CompoundMappingInterface internal constant compMapping = CompoundMappingInterface(0xe7a85d0adDB972A4f0A4e57B698B37f171519e88); + struct ImportData { + address[] cTokens; // is the list of all tokens the user has interacted with (supply/borrow) -> used to enter markets + uint[] borrowAmts; + uint[] supplyAmts; + address[] borrowTokens; + address[] supplyTokens; + CTokenInterface[] borrowCtokens; + CTokenInterface[] supplyCtokens; + address[] supplyCtokensAddr; + address[] borrowCtokensAddr; + } + + struct ImportInputData { + address userAccount; + string[] supplyIds; + string[] borrowIds; + } + /** * @dev enter compound market */ @@ -38,4 +57,162 @@ abstract contract Helpers is DSMath, Basic { troller.enterMarkets(toEnter); } } +} + +contract CompoundHelper is Helpers { + + /** + * @notice fetch the borrow details of the user + * @dev approve the cToken to spend (borrowed amount of) tokens to allow for repaying later + * @param _importInputData the struct containing borrowIds of the users borrowed tokens + * @param data struct used to store the final data on which the CompoundHelper contract functions operate + * @return ImportData the final value of param data + */ + function getBorrowAmounts ( + ImportInputData memory _importInputData, + ImportData memory data + ) internal returns(ImportData memory) { + if (_importInputData.borrowIds.length > 0) { + // initialize arrays for borrow data + data.borrowTokens = new address[](_importInputData.borrowIds.length); + data.borrowCtokens = new CTokenInterface[](_importInputData.borrowIds.length); + data.borrowCtokensAddr = new address[](_importInputData.borrowIds.length); + data.borrowAmts = new uint[](_importInputData.borrowIds.length); + + // check for repeated tokens + for (uint i = 0; i < _importInputData.borrowIds.length; i++) { + bytes32 i_hash = keccak256(abi.encode(_importInputData.borrowIds[i])); + for (uint j = i + 1; j < _importInputData.borrowIds.length; j++) { + bytes32 j_hash = keccak256(abi.encode(_importInputData.borrowIds[j])); + require(i_hash != j_hash, "token-repeated"); + } + } + + // populate the arrays with borrow tokens, cToken addresses and instances, and borrow amounts + for (uint i = 0; i < _importInputData.borrowIds.length; i++) { + (address _token, address _cToken) = compMapping.getMapping(_importInputData.borrowIds[i]); + + require(_token != address(0) && _cToken != address(0), "ctoken mapping not found"); + + data.cTokens[i] = _cToken; + + data.borrowTokens[i] = _token; + data.borrowCtokens[i] = CTokenInterface(_cToken); + data.borrowCtokensAddr[i] = _cToken; + data.borrowAmts[i] = data.borrowCtokens[i].borrowBalanceCurrent(_importInputData.userAccount); + + // give the resp. cToken address approval to spend tokens + if (_token != ethAddr && data.borrowAmts[i] > 0) { + // will be required when repaying the borrow amount on behalf of the user + TokenInterface(_token).approve(_cToken, data.borrowAmts[i]); + } + } + } + return data; + } + + /** + * @notice fetch the supply details of the user + * @dev only reads data from blockchain hence view + * @param _importInputData the struct containing supplyIds of the users supplied tokens + * @param data struct used to store the final data on which the CompoundHelper contract functions operate + * @return ImportData the final value of param data + */ + function getSupplyAmounts ( + ImportInputData memory _importInputData, + ImportData memory data + ) internal view returns(ImportData memory) { + // initialize arrays for supply data + data.supplyTokens = new address[](_importInputData.supplyIds.length); + data.supplyCtokens = new CTokenInterface[](_importInputData.supplyIds.length); + data.supplyCtokensAddr = new address[](_importInputData.supplyIds.length); + data.supplyAmts = new uint[](_importInputData.supplyIds.length); + + // check for repeated tokens + for (uint i = 0; i < _importInputData.supplyIds.length; i++) { + bytes32 i_hash = keccak256(abi.encode(_importInputData.supplyIds[i])); + for (uint j = i + 1; j < _importInputData.supplyIds.length; j++) { + bytes32 j_hash = keccak256(abi.encode(_importInputData.supplyIds[j])); + require(i_hash != j_hash, "token-repeated"); + } + } + + // populate arrays with supply data (supply tokens address, cToken addresses, cToken instances and supply amounts) + for (uint i = 0; i < _importInputData.supplyIds.length; i++) { + (address _token, address _cToken) = compMapping.getMapping(_importInputData.supplyIds[i]); + + require(_token != address(0) && _cToken != address(0), "ctoken mapping not found"); + + uint _supplyIndex = add(i, _importInputData.borrowIds.length); + data.cTokens[_supplyIndex] = _cToken; + + data.supplyTokens[i] = _token; + data.supplyCtokens[i] = CTokenInterface(_cToken); + data.supplyCtokensAddr[i] = (_cToken); + data.supplyAmts[i] = data.supplyCtokens[i].balanceOf(_importInputData.userAccount); + } + return data; + } + + /** + * @notice repays the debt taken by user on Compound on its behalf to free its collateral for transfer + * @dev uses the cEth contract for ETH repays, otherwise the general cToken interface + * @param _userAccount the user address for which debt is to be repayed + * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has debt positions + * @param _borrowAmts array containing the amount borrowed for each token + */ + function _repayUserDebt( + address _userAccount, + CTokenInterface[] memory _cTokenContracts, + uint[] memory _borrowAmts + ) internal { + for(uint i = 0; i < _cTokenContracts.length; i++){ + if(_borrowAmts[i] > 0){ + if(address(_cTokenContracts[i]) == address(cEth)){ + cEth.repayBorrowBehalf{value: _borrowAmts[i]}(_userAccount); + } + else{ + require(_cTokenContracts[i].repayBorrowBehalf(_userAccount, _borrowAmts[i]) == 0, "repayOnBehalf-failed"); + } + } + } + } + + /** + * @notice used to transfer user's supply position on Compound to DSA + * @dev uses the transferFrom token in cToken contracts to transfer positions, requires approval from user first + * @param _userAccount address of the user account whose position is to be transferred + * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has supply positions + * @param _amts array containing the amount supplied for each token + */ + function _transferTokensToDsa( + address _userAccount, + CTokenInterface[] memory _cTokenContracts, + uint[] memory _amts + ) internal { + for(uint i = 0; i < _cTokenContracts.length; i++) { + if(_amts[i] > 0) { + require(_cTokenContracts[i].transferFrom(_userAccount, address(this), _amts[i]), "ctoken-transfer-failed-allowance?"); + } + } + } + + /** + * @notice borrows the user's debt positions from Compound via DSA, so that its debt positions get imported to DSA + * @dev actually borrow some extra amount than the original position to cover the flash loan fee + * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has debt positions + * @param _amts array containing the amounts the user had borrowed originally from Compound plus the flash loan fee + * @param _flashLoanFee flash loan fee (in percentage and scaled up to 10**2) + */ + function _borrowDebtPosition( + CTokenInterface[] memory _cTokenContracts, + uint256[] memory _amts, + uint256 _flashLoanFee + ) internal { + for (uint i = 0; i < _cTokenContracts.length; i++) { + if (_amts[i] > 0) { + require(_cTokenContracts[i].borrow(add(_amts[i], mul(_amts[i], mul(_flashLoanFee, 10**14)))) == 0, "borrow-failed-collateral?"); + } + } + } } \ No newline at end of file diff --git a/contracts/mainnet/connectors/compound-import/main.sol b/contracts/mainnet/connectors/compound-import/main.sol index 43c51c70..fbea89a3 100644 --- a/contracts/mainnet/connectors/compound-import/main.sol +++ b/contracts/mainnet/connectors/compound-import/main.sol @@ -2,8 +2,7 @@ pragma solidity ^0.7.6; pragma experimental ABIEncoderV2; import { TokenInterface, AccountInterface } from "../../common/interfaces.sol"; -import { CTokenInterface } from "./interface.sol"; -import { Helpers } from "./helpers.sol"; +import { CompoundHelper } from "./helpers.sol"; import { Events } from "./events.sol"; // 1. Get info for all the assets the user has supplied as collateral and the assets he borrowed. @@ -11,184 +10,7 @@ import { Events } from "./events.sol"; // 3. After paying the debt, transfer the user's tokens from EOA to DSA. // 4. Then borrow debt of same tokens but include flash loan fee in it. -contract CompoundHelper is Helpers, Events { - /** - * @notice repays the debt taken by user on Compound on its behalf to free its collateral for transfer - * @dev uses the cEth contract for ETH repays, otherwise the general cToken interface - * @param _userAccount the user address for which debt is to be repayed - * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has debt positions - * @param _borrowAmts array containing the amount borrowed for each token - */ - function _repayUserDebt( - address _userAccount, - CTokenInterface[] memory _cTokenContracts, - uint[] memory _borrowAmts - ) internal { - for(uint i = 0; i < _cTokenContracts.length; i++){ - if(_borrowAmts[i] > 0){ - if(address(_cTokenContracts[i]) == address(cEth)){ - cEth.repayBorrowBehalf{value: _borrowAmts[i]}(_userAccount); - } - else{ - require(_cTokenContracts[i].repayBorrowBehalf(_userAccount, _borrowAmts[i]) == 0, "repayOnBehalf-failed"); - } - } - } - } - - /** - * @notice used to transfer user's supply position on Compound to DSA - * @dev uses the transferFrom token in cToken contracts to transfer positions, requires approval from user first - * @param _userAccount address of the user account whose position is to be transferred - * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has supply positions - * @param _amts array containing the amount supplied for each token - */ - function _transferTokensToDsa( - address _userAccount, - CTokenInterface[] memory _cTokenContracts, - uint[] memory _amts - ) internal { - for(uint i = 0; i < _cTokenContracts.length; i++) { - if(_amts[i] > 0) { - require(_cTokenContracts[i].transferFrom(_userAccount, address(this), _amts[i]), "ctoken-transfer-failed-allowance?"); - } - } - } - - /** - * @notice borrows the user's debt positions from Compound via DSA, so that its debt positions get imported to DSA - * @dev actually borrow some extra amount than the original position to cover the flash loan fee - * @param _cTokenContracts array containing all interfaces to the cToken contracts in which the user has debt positions - * @param _amts array containing the amounts the user had borrowed originally from Compound plus the flash loan fee - * @param _flashLoanFee flash loan fee (in percentage and scaled up to 10**2) - */ - function _borrowDebtPosition( - CTokenInterface[] memory _cTokenContracts, - uint256[] memory _amts, - uint256 _flashLoanFee - ) internal { - for (uint i = 0; i < _cTokenContracts.length; i++) { - if (_amts[i] > 0) { - require(_cTokenContracts[i].borrow(add(_amts[i], mul(_amts[i], mul(_flashLoanFee, 10**14)))) == 0, "borrow-failed-collateral?"); - } - } - } -} - -contract CompoundResolver is CompoundHelper { - struct ImportData { - address[] cTokens; // is the list of all tokens the user has interacted with (supply/borrow) -> used to enter markets - uint[] borrowAmts; - uint[] supplyAmts; - address[] borrowTokens; - address[] supplyTokens; - CTokenInterface[] borrowCtokens; - CTokenInterface[] supplyCtokens; - address[] supplyCtokensAddr; - address[] borrowCtokensAddr; - } - - struct ImportInputData { - address userAccount; - string[] supplyIds; - string[] borrowIds; - } - - /** - * @notice fetch the borrow details of the user - * @dev approve the cToken to spend (borrowed amount of) tokens to allow for repaying later - * @param _importInputData the struct containing borrowIds of the users borrowed tokens - * @param data struct used to store the final data on which the CompoundHelper contract functions operate - * @return ImportData the final value of param data - */ - function getBorrowAmounts ( - ImportInputData memory _importInputData, - ImportData memory data - ) internal returns(ImportData memory) { - if (_importInputData.borrowIds.length > 0) { - // initialize arrays for borrow data - data.borrowTokens = new address[](_importInputData.borrowIds.length); - data.borrowCtokens = new CTokenInterface[](_importInputData.borrowIds.length); - data.borrowCtokensAddr = new address[](_importInputData.borrowIds.length); - data.borrowAmts = new uint[](_importInputData.borrowIds.length); - - // check for repeated tokens - for (uint i = 0; i < _importInputData.borrowIds.length; i++) { - bytes32 i_hash = keccak256(abi.encode(_importInputData.borrowIds[i])); - for (uint j = i + 1; j < _importInputData.borrowIds.length; j++) { - bytes32 j_hash = keccak256(abi.encode(_importInputData.borrowIds[j])); - require(i_hash != j_hash, "token-repeated"); - } - } - - // populate the arrays with borrow tokens, cToken addresses and instances, and borrow amounts - for (uint i = 0; i < _importInputData.borrowIds.length; i++) { - (address _token, address _cToken) = compMapping.getMapping(_importInputData.borrowIds[i]); - - require(_token != address(0) && _cToken != address(0), "ctoken mapping not found"); - - data.cTokens[i] = _cToken; - - data.borrowTokens[i] = _token; - data.borrowCtokens[i] = CTokenInterface(_cToken); - data.borrowCtokensAddr[i] = _cToken; - data.borrowAmts[i] = data.borrowCtokens[i].borrowBalanceCurrent(_importInputData.userAccount); - - // give the resp. cToken address approval to spend tokens - if (_token != ethAddr && data.borrowAmts[i] > 0) { - // will be required when repaying the borrow amount on behalf of the user - TokenInterface(_token).approve(_cToken, data.borrowAmts[i]); - } - } - } - return data; - } - - /** - * @notice fetch the supply details of the user - * @dev only reads data from blockchain hence view - * @param _importInputData the struct containing supplyIds of the users supplied tokens - * @param data struct used to store the final data on which the CompoundHelper contract functions operate - * @return ImportData the final value of param data - */ - function getSupplyAmounts ( - ImportInputData memory _importInputData, - ImportData memory data - ) internal view returns(ImportData memory) { - // initialize arrays for supply data - data.supplyTokens = new address[](_importInputData.supplyIds.length); - data.supplyCtokens = new CTokenInterface[](_importInputData.supplyIds.length); - data.supplyCtokensAddr = new address[](_importInputData.supplyIds.length); - data.supplyAmts = new uint[](_importInputData.supplyIds.length); - - // check for repeated tokens - for (uint i = 0; i < _importInputData.supplyIds.length; i++) { - bytes32 i_hash = keccak256(abi.encode(_importInputData.supplyIds[i])); - for (uint j = i + 1; j < _importInputData.supplyIds.length; j++) { - bytes32 j_hash = keccak256(abi.encode(_importInputData.supplyIds[j])); - require(i_hash != j_hash, "token-repeated"); - } - } - - // populate arrays with supply data (supply tokens address, cToken addresses, cToken instances and supply amounts) - for (uint i = 0; i < _importInputData.supplyIds.length; i++) { - (address _token, address _cToken) = compMapping.getMapping(_importInputData.supplyIds[i]); - - require(_token != address(0) && _cToken != address(0), "ctoken mapping not found"); - - uint _supplyIndex = add(i, _importInputData.borrowIds.length); - data.cTokens[_supplyIndex] = _cToken; - - data.supplyTokens[i] = _token; - data.supplyCtokens[i] = CTokenInterface(_cToken); - data.supplyCtokensAddr[i] = (_cToken); - data.supplyAmts[i] = data.supplyCtokens[i].balanceOf(_importInputData.userAccount); - } - return data; - } -} - -contract CompoundImport is CompoundResolver { +contract CompoundImportResolver is CompoundHelper { /** * @notice this function performs the import of user's Compound positions into its DSA @@ -282,6 +104,6 @@ contract CompoundImport is CompoundResolver { } } -contract ConnectV2CompoundImport is CompoundImport { +contract ConnectV2CompoundImport is CompoundImportResolver { string public constant name = "Compound-Import-v2"; } \ No newline at end of file