mirror of
https://github.com/Instadapp/dsa-connectors.git
synced 2024-07-29 22:37:00 +00:00
Merge pull request #174 from chinmay0402/feature/compound-import-connector
Compound import connector
This commit is contained in:
commit
2c53087314
14
contracts/mainnet/connectors/compound-import/events.sol
Normal file
14
contracts/mainnet/connectors/compound-import/events.sol
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
contract Events {
|
||||||
|
event LogCompoundImport(
|
||||||
|
address indexed user,
|
||||||
|
address[] ctokens,
|
||||||
|
string[] supplyIds,
|
||||||
|
string[] borrowIds,
|
||||||
|
uint256[] supplyAmts,
|
||||||
|
uint256[] borrowAmts
|
||||||
|
);
|
||||||
|
}
|
222
contracts/mainnet/connectors/compound-import/helpers.sol
Normal file
222
contracts/mainnet/connectors/compound-import/helpers.sol
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
|
||||||
|
import { DSMath } from "../../common/math.sol";
|
||||||
|
import { Basic } from "../../common/basic.sol";
|
||||||
|
import { TokenInterface, AccountInterface } from "../../common/interfaces.sol";
|
||||||
|
import { ComptrollerInterface, CompoundMappingInterface, CETHInterface, CTokenInterface } from "./interface.sol";
|
||||||
|
|
||||||
|
abstract contract Helpers is DSMath, Basic {
|
||||||
|
/**
|
||||||
|
* @dev Compound CEth
|
||||||
|
*/
|
||||||
|
CETHInterface internal constant cEth =
|
||||||
|
CETHInterface(0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Compound Comptroller
|
||||||
|
*/
|
||||||
|
ComptrollerInterface internal constant troller =
|
||||||
|
ComptrollerInterface(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Compound Mapping
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
uint256[] borrowAmts;
|
||||||
|
uint256[] 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
|
||||||
|
* @param _cotkens array of ctoken addresses to enter compound market
|
||||||
|
*/
|
||||||
|
function _enterMarkets(address[] memory _cotkens) internal {
|
||||||
|
troller.enterMarkets(_cotkens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
uint256 _length = _importInputData.borrowIds.length;
|
||||||
|
data.borrowTokens = new address[](_length);
|
||||||
|
data.borrowCtokens = new CTokenInterface[](_length);
|
||||||
|
data.borrowCtokensAddr = new address[](_length);
|
||||||
|
data.borrowAmts = new uint256[](_length);
|
||||||
|
|
||||||
|
// populate the arrays with borrow tokens, cToken addresses and instances, and borrow amounts
|
||||||
|
for (uint256 i; i < _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
|
||||||
|
uint256 _length = _importInputData.supplyIds.length;
|
||||||
|
data.supplyTokens = new address[](_length);
|
||||||
|
data.supplyCtokens = new CTokenInterface[](_length);
|
||||||
|
data.supplyCtokensAddr = new address[](_length);
|
||||||
|
data.supplyAmts = new uint256[](_length);
|
||||||
|
|
||||||
|
// populate arrays with supply data (supply tokens address, cToken addresses, cToken instances and supply amounts)
|
||||||
|
for (uint256 i; i < _length; i++) {
|
||||||
|
(address _token, address _cToken) = compMapping.getMapping(
|
||||||
|
_importInputData.supplyIds[i]
|
||||||
|
);
|
||||||
|
|
||||||
|
require(
|
||||||
|
_token != address(0) && _cToken != address(0),
|
||||||
|
"ctoken mapping not found"
|
||||||
|
);
|
||||||
|
|
||||||
|
uint256 _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,
|
||||||
|
uint256[] memory _borrowAmts
|
||||||
|
) internal {
|
||||||
|
for (uint256 i; 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,
|
||||||
|
uint256[] memory _amts
|
||||||
|
) internal {
|
||||||
|
for (uint256 i; 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 _flashLoanFees flash loan fee (in percentage and scaled up to 10**2)
|
||||||
|
*/
|
||||||
|
function _borrowDebtPosition(
|
||||||
|
CTokenInterface[] memory _cTokenContracts,
|
||||||
|
uint256[] memory _amts,
|
||||||
|
uint256[] memory _flashLoanFees
|
||||||
|
) internal {
|
||||||
|
for (uint256 i; i < _cTokenContracts.length; i++)
|
||||||
|
if (_amts[i] > 0)
|
||||||
|
require(
|
||||||
|
_cTokenContracts[i].borrow(
|
||||||
|
add(_amts[i], _flashLoanFees[i])
|
||||||
|
) == 0,
|
||||||
|
"borrow-failed-collateral?"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
contracts/mainnet/connectors/compound-import/interface.sol
Normal file
100
contracts/mainnet/connectors/compound-import/interface.sol
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
|
||||||
|
interface TokenInterface {
|
||||||
|
function balanceOf(address) external view returns (uint256);
|
||||||
|
|
||||||
|
function allowance(address, address) external view returns (uint256);
|
||||||
|
|
||||||
|
function approve(address, uint256) external;
|
||||||
|
|
||||||
|
function transfer(address, uint256) external returns (bool);
|
||||||
|
|
||||||
|
function transferFrom(
|
||||||
|
address,
|
||||||
|
address,
|
||||||
|
uint256
|
||||||
|
) external returns (bool);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CTokenInterface {
|
||||||
|
function mint(uint256 mintAmount) external returns (uint256);
|
||||||
|
|
||||||
|
function redeem(uint256 redeemTokens) external returns (uint256);
|
||||||
|
|
||||||
|
function borrow(uint256 borrowAmount) external returns (uint256);
|
||||||
|
|
||||||
|
function repayBorrow(uint256 repayAmount) external returns (uint256);
|
||||||
|
|
||||||
|
function repayBorrowBehalf(address borrower, uint256 repayAmount)
|
||||||
|
external
|
||||||
|
returns (uint256); // For ERC20
|
||||||
|
|
||||||
|
function liquidateBorrow(
|
||||||
|
address borrower,
|
||||||
|
uint256 repayAmount,
|
||||||
|
address cTokenCollateral
|
||||||
|
) external returns (uint256);
|
||||||
|
|
||||||
|
function borrowBalanceCurrent(address account) external returns (uint256);
|
||||||
|
|
||||||
|
function redeemUnderlying(uint256 redeemAmount) external returns (uint256);
|
||||||
|
|
||||||
|
function exchangeRateCurrent() external returns (uint256);
|
||||||
|
|
||||||
|
function balanceOf(address owner) external view returns (uint256 balance);
|
||||||
|
|
||||||
|
function transferFrom(
|
||||||
|
address,
|
||||||
|
address,
|
||||||
|
uint256
|
||||||
|
) external returns (bool);
|
||||||
|
|
||||||
|
function allowance(address, address) external view returns (uint256);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CETHInterface {
|
||||||
|
function mint() external payable;
|
||||||
|
|
||||||
|
function repayBorrow() external payable;
|
||||||
|
|
||||||
|
function repayBorrowBehalf(address borrower) external payable;
|
||||||
|
|
||||||
|
function liquidateBorrow(address borrower, address cTokenCollateral)
|
||||||
|
external
|
||||||
|
payable;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ComptrollerInterface {
|
||||||
|
function enterMarkets(address[] calldata cTokens)
|
||||||
|
external
|
||||||
|
returns (uint256[] memory);
|
||||||
|
|
||||||
|
function exitMarket(address cTokenAddress) external returns (uint256);
|
||||||
|
|
||||||
|
function getAssetsIn(address account)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address[] memory);
|
||||||
|
|
||||||
|
function getAccountLiquidity(address account)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (
|
||||||
|
uint256,
|
||||||
|
uint256,
|
||||||
|
uint256
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CompoundMappingInterface {
|
||||||
|
function cTokenMapping(string calldata tokenId)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address);
|
||||||
|
|
||||||
|
function getMapping(string calldata tokenId)
|
||||||
|
external
|
||||||
|
view
|
||||||
|
returns (address, address);
|
||||||
|
}
|
110
contracts/mainnet/connectors/compound-import/main.sol
Normal file
110
contracts/mainnet/connectors/compound-import/main.sol
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
pragma solidity ^0.7.6;
|
||||||
|
pragma experimental ABIEncoderV2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title Compound-Import.
|
||||||
|
* @dev Lending & Borrowing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { TokenInterface, AccountInterface } from "../../common/interfaces.sol";
|
||||||
|
import { CompoundHelper } from "./helpers.sol";
|
||||||
|
import { Events } from "./events.sol";
|
||||||
|
|
||||||
|
contract CompoundImportResolver is CompoundHelper {
|
||||||
|
/**
|
||||||
|
* @notice this function performs the import of user's Compound positions into its DSA
|
||||||
|
* @dev called internally by the importCompound and migrateCompound functions
|
||||||
|
* @param _importInputData the struct containing borrowIds of the users borrowed tokens
|
||||||
|
* @param _flashLoanFees list of flash loan fees
|
||||||
|
*/
|
||||||
|
function _importCompound(
|
||||||
|
ImportInputData memory _importInputData,
|
||||||
|
uint256[] memory _flashLoanFees
|
||||||
|
) internal returns (string memory _eventName, bytes memory _eventParam) {
|
||||||
|
require(
|
||||||
|
AccountInterface(address(this)).isAuth(
|
||||||
|
_importInputData.userAccount
|
||||||
|
),
|
||||||
|
"user-account-not-auth"
|
||||||
|
);
|
||||||
|
|
||||||
|
require(_importInputData.supplyIds.length > 0, "0-length-not-allowed");
|
||||||
|
|
||||||
|
ImportData memory data;
|
||||||
|
|
||||||
|
uint256 _length = add(
|
||||||
|
_importInputData.supplyIds.length,
|
||||||
|
_importInputData.borrowIds.length
|
||||||
|
);
|
||||||
|
data.cTokens = new address[](_length);
|
||||||
|
|
||||||
|
// get info about all borrowings and lendings by the user on Compound
|
||||||
|
data = getBorrowAmounts(_importInputData, data);
|
||||||
|
data = getSupplyAmounts(_importInputData, data);
|
||||||
|
|
||||||
|
_enterMarkets(data.cTokens);
|
||||||
|
|
||||||
|
// pay back user's debt using flash loan funds
|
||||||
|
_repayUserDebt(
|
||||||
|
_importInputData.userAccount,
|
||||||
|
data.borrowCtokens,
|
||||||
|
data.borrowAmts
|
||||||
|
);
|
||||||
|
|
||||||
|
// transfer user's tokens to DSA
|
||||||
|
_transferTokensToDsa(
|
||||||
|
_importInputData.userAccount,
|
||||||
|
data.supplyCtokens,
|
||||||
|
data.supplyAmts
|
||||||
|
);
|
||||||
|
|
||||||
|
// borrow the earlier position from Compound with flash loan fee added
|
||||||
|
_borrowDebtPosition(
|
||||||
|
data.borrowCtokens,
|
||||||
|
data.borrowAmts,
|
||||||
|
_flashLoanFees
|
||||||
|
);
|
||||||
|
|
||||||
|
_eventName = "LogCompoundImport(address,address[],string[],string[],uint256[],uint256[])";
|
||||||
|
_eventParam = abi.encode(
|
||||||
|
_importInputData.userAccount,
|
||||||
|
data.cTokens,
|
||||||
|
_importInputData.supplyIds,
|
||||||
|
_importInputData.borrowIds,
|
||||||
|
data.supplyAmts,
|
||||||
|
data.borrowAmts
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @notice import Compound position of the address passed in as userAccount
|
||||||
|
* @dev internally calls _importContract to perform the actual import
|
||||||
|
* @param _userAccount address of user whose position is to be imported to DSA
|
||||||
|
* @param _supplyIds Ids of all tokens the user has supplied to Compound
|
||||||
|
* @param _borrowIds Ids of all token borrowed by the user
|
||||||
|
* @param _flashLoanFees list of flash loan fees
|
||||||
|
*/
|
||||||
|
function importCompound(
|
||||||
|
address _userAccount,
|
||||||
|
string[] memory _supplyIds,
|
||||||
|
string[] memory _borrowIds,
|
||||||
|
uint256[] memory _flashLoanFees
|
||||||
|
)
|
||||||
|
external
|
||||||
|
payable
|
||||||
|
returns (string memory _eventName, bytes memory _eventParam)
|
||||||
|
{
|
||||||
|
ImportInputData memory inputData = ImportInputData({
|
||||||
|
userAccount: _userAccount,
|
||||||
|
supplyIds: _supplyIds,
|
||||||
|
borrowIds: _borrowIds
|
||||||
|
});
|
||||||
|
|
||||||
|
(_eventName, _eventParam) = _importCompound(inputData, _flashLoanFees);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contract ConnectV2CompoundImport is CompoundImportResolver {
|
||||||
|
string public constant name = "Compound-Import-v2";
|
||||||
|
}
|
114
scripts/constant/abi/connectors/instapool-c.json
Normal file
114
scripts/constant/abi/connectors/instapool-c.json
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "internalType": "address", "name": "token", "type": "address" },
|
||||||
|
{ "indexed": false, "internalType": "uint256", "name": "tokenAmt", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "LogFlashBorrow",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "internalType": "address[]", "name": "token", "type": "address[]" },
|
||||||
|
{ "indexed": false, "internalType": "uint256[]", "name": "tokenAmts", "type": "uint256[]" }
|
||||||
|
],
|
||||||
|
"name": "LogFlashMultiBorrow",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "internalType": "address[]", "name": "token", "type": "address[]" },
|
||||||
|
{ "indexed": false, "internalType": "uint256[]", "name": "tokenAmts", "type": "uint256[]" }
|
||||||
|
],
|
||||||
|
"name": "LogFlashMultiPayback",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"anonymous": false,
|
||||||
|
"inputs": [
|
||||||
|
{ "indexed": false, "internalType": "address", "name": "token", "type": "address" },
|
||||||
|
{ "indexed": false, "internalType": "uint256", "name": "tokenAmt", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "LogFlashPayback",
|
||||||
|
"type": "event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{ "internalType": "address", "name": "token", "type": "address" },
|
||||||
|
{ "internalType": "uint256", "name": "amt", "type": "uint256" },
|
||||||
|
{ "internalType": "uint256", "name": "route", "type": "uint256" },
|
||||||
|
{ "internalType": "bytes", "name": "data", "type": "bytes" },
|
||||||
|
{ "internalType": "bytes", "name": "extraData", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"name": "flashBorrowAndCast",
|
||||||
|
"outputs": [
|
||||||
|
{ "internalType": "string", "name": "_eventName", "type": "string" },
|
||||||
|
{ "internalType": "bytes", "name": "_eventParam", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{ "internalType": "address[]", "name": "tokens_", "type": "address[]" },
|
||||||
|
{ "internalType": "uint256[]", "name": "amts_", "type": "uint256[]" },
|
||||||
|
{ "internalType": "uint256", "name": "route", "type": "uint256" },
|
||||||
|
{ "internalType": "bytes", "name": "data", "type": "bytes" },
|
||||||
|
{ "internalType": "bytes", "name": "extraData", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"name": "flashMultiBorrowAndCast",
|
||||||
|
"outputs": [
|
||||||
|
{ "internalType": "string", "name": "_eventName", "type": "string" },
|
||||||
|
{ "internalType": "bytes", "name": "_eventParam", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{ "internalType": "address[]", "name": "tokens_", "type": "address[]" },
|
||||||
|
{ "internalType": "uint256[]", "name": "amts_", "type": "uint256[]" },
|
||||||
|
{ "internalType": "uint256[]", "name": "getIds", "type": "uint256[]" },
|
||||||
|
{ "internalType": "uint256[]", "name": "setIds", "type": "uint256[]" }
|
||||||
|
],
|
||||||
|
"name": "flashMultiPayback",
|
||||||
|
"outputs": [
|
||||||
|
{ "internalType": "string", "name": "_eventName", "type": "string" },
|
||||||
|
{ "internalType": "bytes", "name": "_eventParam", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [
|
||||||
|
{ "internalType": "address", "name": "token", "type": "address" },
|
||||||
|
{ "internalType": "uint256", "name": "amt", "type": "uint256" },
|
||||||
|
{ "internalType": "uint256", "name": "getId", "type": "uint256" },
|
||||||
|
{ "internalType": "uint256", "name": "setId", "type": "uint256" }
|
||||||
|
],
|
||||||
|
"name": "flashPayback",
|
||||||
|
"outputs": [
|
||||||
|
{ "internalType": "string", "name": "_eventName", "type": "string" },
|
||||||
|
{ "internalType": "bytes", "name": "_eventParam", "type": "bytes" }
|
||||||
|
],
|
||||||
|
"stateMutability": "payable",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [],
|
||||||
|
"name": "instaPool",
|
||||||
|
"outputs": [{ "internalType": "contract InstaFlashV4Interface", "name": "", "type": "address" }],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"inputs": [],
|
||||||
|
"name": "name",
|
||||||
|
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
|
||||||
|
"stateMutability": "view",
|
||||||
|
"type": "function"
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,15 +1,16 @@
|
||||||
export const abis: Record<string, any> = {
|
export const abis: Record<string, any> = {
|
||||||
core: {
|
core: {
|
||||||
connectorsV2: require("./abi/core/connectorsV2.json"),
|
connectorsV2: require("./abi/core/connectorsV2.json"),
|
||||||
instaIndex: require("./abi/core/instaIndex.json"),
|
instaIndex: require("./abi/core/instaIndex.json")
|
||||||
},
|
},
|
||||||
connectors: {
|
connectors: {
|
||||||
"Basic-v1": require("./abi/connectors/basic.json"),
|
"Basic-v1": require("./abi/connectors/basic.json"),
|
||||||
basic: require("./abi/connectors/basic.json"),
|
basic: require("./abi/connectors/basic.json"),
|
||||||
auth: require("./abi/connectors/auth.json"),
|
auth: require("./abi/connectors/auth.json"),
|
||||||
"INSTAPOOL-A": require("./abi/connectors/instapool.json"),
|
"INSTAPOOL-A": require("./abi/connectors/instapool.json"),
|
||||||
|
"INSTAPOOL-C": require("./abi/connectors/instapool-c.json")
|
||||||
},
|
},
|
||||||
basic: {
|
basic: {
|
||||||
erc20: require("./abi/basics/erc20.json"),
|
erc20: require("./abi/basics/erc20.json")
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
|
|
254
test/mainnet/compound-import/compound-import.test.ts
Normal file
254
test/mainnet/compound-import/compound-import.test.ts
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
import { expect, should } from "chai";
|
||||||
|
import hre, { ethers, waffle } from "hardhat";
|
||||||
|
import type { Signer, Contract } from "ethers";
|
||||||
|
import { BigNumber } from "bignumber.js";
|
||||||
|
import { buildDSAv2 } from "../../../scripts/tests/buildDSAv2";
|
||||||
|
import { addresses } from "../../../scripts/tests/mainnet/addresses";
|
||||||
|
import { deployAndEnableConnector } from "../../../scripts/tests/deployAndEnableConnector";
|
||||||
|
import { abis } from "../../../scripts/constant/abis";
|
||||||
|
import { getMasterSigner } from "../../../scripts/tests/getMasterSigner";
|
||||||
|
import { parseEther, parseUnits } from "ethers/lib/utils";
|
||||||
|
import { encodeSpells } from "../../../scripts/tests/encodeSpells";
|
||||||
|
import encodeFlashcastData from "../../../scripts/tests/encodeFlashcastData";
|
||||||
|
import { ConnectV2CompoundImport__factory } from "../../../typechain";
|
||||||
|
const { provider } = waffle;
|
||||||
|
|
||||||
|
const cEthAddress = "0x4Ddc2D193948926D02f9B1fE9e1daa0718270ED5";
|
||||||
|
const cDaiAddress = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643";
|
||||||
|
const daiAddress = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
|
||||||
|
const comptrollerAddress = "0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B";
|
||||||
|
|
||||||
|
describe("Import Compound", function () {
|
||||||
|
const connectorName = "COMPOUND-IMPORT-X";
|
||||||
|
|
||||||
|
const cEthAbi = [
|
||||||
|
{
|
||||||
|
constant: false,
|
||||||
|
inputs: [],
|
||||||
|
name: "mint",
|
||||||
|
outputs: [],
|
||||||
|
payable: true,
|
||||||
|
stateMutability: "payable",
|
||||||
|
type: "function",
|
||||||
|
signature: "0x1249c58b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
constant: true,
|
||||||
|
inputs: [{ internalType: "address", name: "owner", type: "address" }],
|
||||||
|
name: "balanceOf",
|
||||||
|
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "view",
|
||||||
|
type: "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
constant: false,
|
||||||
|
inputs: [],
|
||||||
|
name: "exchangeRateCurrent",
|
||||||
|
outputs: [{ name: "", type: "uint256" }],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
type: "function"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
constant: false,
|
||||||
|
inputs: [
|
||||||
|
{ internalType: "address", name: "usr", type: "address" },
|
||||||
|
{ internalType: "uint256", name: "wad", type: "uint256" }
|
||||||
|
],
|
||||||
|
name: "approve",
|
||||||
|
outputs: [{ internalType: "bool", name: "", type: "bool" }],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
type: "function"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const cDaiAbi = [
|
||||||
|
{
|
||||||
|
constant: false,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
internalType: "uint256",
|
||||||
|
name: "borrowAmount",
|
||||||
|
type: "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: "borrow",
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
internalType: "uint256",
|
||||||
|
name: "",
|
||||||
|
type: "uint256"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
type: "function",
|
||||||
|
signature: "0xc5ebeaec"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
constant: false,
|
||||||
|
inputs: [{ internalType: "address", name: "account", type: "address" }],
|
||||||
|
name: "borrowBalanceCurrent",
|
||||||
|
outputs: [{ internalType: "uint256", name: "", type: "uint256" }],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
type: "function"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const comptrollerAbi = [
|
||||||
|
{
|
||||||
|
constant: false,
|
||||||
|
inputs: [
|
||||||
|
{
|
||||||
|
internalType: "address[]",
|
||||||
|
name: "cTokens",
|
||||||
|
type: "address[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
name: "enterMarkets",
|
||||||
|
outputs: [
|
||||||
|
{
|
||||||
|
internalType: "uint256[]",
|
||||||
|
name: "",
|
||||||
|
type: "uint256[]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
payable: false,
|
||||||
|
stateMutability: "nonpayable",
|
||||||
|
type: "function",
|
||||||
|
signature: "0xc2998238"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
let cEth: Contract, cDai: Contract, comptroller, Dai: any;
|
||||||
|
|
||||||
|
let dsaWallet0: any;
|
||||||
|
let masterSigner: Signer;
|
||||||
|
let instaConnectorsV2: Contract;
|
||||||
|
let connector: any;
|
||||||
|
|
||||||
|
const wallets = provider.getWallets();
|
||||||
|
const [wallet0, wallet1, wallet2, wallet3] = wallets;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
await hre.network.provider.request({
|
||||||
|
method: "hardhat_reset",
|
||||||
|
params: [
|
||||||
|
{
|
||||||
|
forking: {
|
||||||
|
// @ts-ignore
|
||||||
|
jsonRpcUrl: hre.config.networks.hardhat.forking.url,
|
||||||
|
blockNumber: 14441991
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
masterSigner = await getMasterSigner();
|
||||||
|
instaConnectorsV2 = await ethers.getContractAt(abis.core.connectorsV2, addresses.core.connectorsV2);
|
||||||
|
|
||||||
|
connector = await deployAndEnableConnector({
|
||||||
|
connectorName,
|
||||||
|
contractArtifact: ConnectV2CompoundImport__factory,
|
||||||
|
signer: masterSigner,
|
||||||
|
connectors: instaConnectorsV2
|
||||||
|
});
|
||||||
|
console.log("Connector address", connector.address);
|
||||||
|
|
||||||
|
cEth = new ethers.Contract(cEthAddress, cEthAbi);
|
||||||
|
cDai = new ethers.Contract(cDaiAddress, cDaiAbi);
|
||||||
|
Dai = new ethers.Contract(daiAddress, abis.basic.erc20);
|
||||||
|
comptroller = new ethers.Contract(comptrollerAddress, comptrollerAbi);
|
||||||
|
|
||||||
|
// deposit ether to Compound: ETH-A
|
||||||
|
await cEth.connect(wallet0).mint({
|
||||||
|
value: parseEther("9")
|
||||||
|
});
|
||||||
|
|
||||||
|
// enter markets with deposits
|
||||||
|
const cTokens = [cEth.address];
|
||||||
|
await comptroller.connect(wallet0).enterMarkets(cTokens);
|
||||||
|
|
||||||
|
// borrow dai from Compound: DAI-A
|
||||||
|
await cDai.connect(wallet0).borrow(parseUnits("100"));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Deployment", async () => {
|
||||||
|
it("Should set correct name", async () => {
|
||||||
|
expect(await connector.name()).to.eq("Compound-Import-v2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("checks", async () => {
|
||||||
|
it("Should check user COMPOUND position", async () => {
|
||||||
|
const ethExchangeRate = (await cEth.connect(wallet0).callStatic.exchangeRateCurrent()) / 1e28;
|
||||||
|
expect(new BigNumber(await cEth.connect(wallet0).balanceOf(wallet0.address)).dividedBy(1e8).toFixed(0)).to.eq(
|
||||||
|
new BigNumber(9).dividedBy(ethExchangeRate).toFixed(0)
|
||||||
|
);
|
||||||
|
expect(await Dai.connect(wallet0).balanceOf(wallet0.address)).to.eq("100000000000000000000");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("DSA wallet setup", async () => {
|
||||||
|
it("Should build DSA v2", async () => {
|
||||||
|
dsaWallet0 = await buildDSAv2(wallet0.address);
|
||||||
|
expect(!!dsaWallet0.address).to.be.true;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Deposit ETH into DSA wallet", async function () {
|
||||||
|
await wallet0.sendTransaction({
|
||||||
|
to: dsaWallet0.address,
|
||||||
|
value: ethers.utils.parseEther("10")
|
||||||
|
});
|
||||||
|
expect(await ethers.provider.getBalance(dsaWallet0.address)).to.be.gte(ethers.utils.parseEther("10"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Compound position migration", async () => {
|
||||||
|
it("Should migrate Compound position", async () => {
|
||||||
|
const tx0 = await cEth
|
||||||
|
.connect(wallet0)
|
||||||
|
.approve(dsaWallet0.address, await cEth.connect(wallet0).balanceOf(wallet0.address));
|
||||||
|
|
||||||
|
await tx0.wait();
|
||||||
|
|
||||||
|
// const amount0 = await cDai.connect(wallet0).callStatic.borrowBalanceCurrent(wallet0.address);
|
||||||
|
const amount0 = new BigNumber("100000007061117456728");
|
||||||
|
const amount = new BigNumber(amount0.toString()).multipliedBy(5).dividedBy(1e4);
|
||||||
|
|
||||||
|
const amountWithFee = amount0.plus(amount);
|
||||||
|
const flashSpells = [
|
||||||
|
{
|
||||||
|
connector: "COMPOUND-IMPORT-X",
|
||||||
|
method: "importCompound",
|
||||||
|
args: [wallet0.address, ["ETH-A"], ["DAI-A"], [amount.toFixed(0)]]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
connector: "INSTAPOOL-C",
|
||||||
|
method: "flashPayback",
|
||||||
|
args: [daiAddress, amountWithFee.toFixed(0), 0, 0]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const spells = [
|
||||||
|
{
|
||||||
|
connector: "INSTAPOOL-C",
|
||||||
|
method: "flashBorrowAndCast",
|
||||||
|
args: [daiAddress, amount0.toString(), 5, encodeFlashcastData(flashSpells), "0x"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
const tx = await dsaWallet0.connect(wallet0).cast(...encodeSpells(spells), wallet0.address);
|
||||||
|
const receipt = await tx.wait();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should check DSA COMPOUND position", async () => {
|
||||||
|
const ethExchangeRate = (await cEth.connect(wallet0).callStatic.exchangeRateCurrent()) / 1e28;
|
||||||
|
expect(new BigNumber(await cEth.connect(wallet0).balanceOf(dsaWallet0.address)).dividedBy(1e8).toFixed(0)).to.eq(
|
||||||
|
new BigNumber(9).dividedBy(ethExchangeRate).toFixed(0)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user