diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 00000000..47f24e11 --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,30 @@ +# This is a basic workflow that is manually triggered + +name: Manual workflow + +# Controls when the action will run. Workflow runs when manually triggered using the UI +# or API. +on: + workflow_dispatch: + # Inputs the workflow accepts. + inputs: + name: + # Friendly description to be shown in the UI instead of 'name' + description: 'Person to greet' + # Default value if no value is explicitly provided + default: 'World' + # Input has to be provided for the workflow to run + required: true + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "greet" + greet: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Runs a single command using the runners shell + - name: Send greeting + run: echo "Hello ${{ github.event.inputs.name }}" diff --git a/.github/workflows/status.yml b/.github/workflows/status.yml new file mode 100644 index 00000000..9dd6986c --- /dev/null +++ b/.github/workflows/status.yml @@ -0,0 +1,26 @@ +name: PR status checks +on: + pull_request: + types: [assigned, opened, synchronize, reopened] +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + node-version: [16.x] + steps: + - uses: actions/checkout@v1 + with: + # Checkout the head ref instead of the PR branch that github creates. + ref: ${{ github.head_ref }} + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - name: Install and build + run: | + npm install + - name: Run status checks + run: node ./status-checks + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 00000000..31354ec1 --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 00000000..dc49f0ab --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/bin/sh +. "$(dirname "$0")/_/husky.sh" + +npm run check-husky diff --git a/contracts/mainnet/connectors/aave/v2-amm/main.sol b/contracts/mainnet/connectors/aave/v2-amm/main.sol index 5f89d04a..55090a97 100644 --- a/contracts/mainnet/connectors/aave/v2-amm/main.sol +++ b/contracts/mainnet/connectors/aave/v2-amm/main.sol @@ -9,6 +9,7 @@ import { AaveInterface } from "./interface.sol"; abstract contract AaveResolver is Events, Helpers { /** * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to Aave v2 AMM for lending / collaterization. * @param token token address to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -52,6 +53,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw deposited token from Aave v2 AMM * @param token token address to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -87,6 +89,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token using Aave v2 AMM * @param token token address to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to borrow. * @param rateMode type of borrow debt.(For Stable: 1, Variable: 2) @@ -118,6 +121,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Payback borrowed ETH/ERC20_Token. + * @notice Payback debt owed on Aave v2 AMM. * @param token token address to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to payback. * @param rateMode type of borrow debt.(For Stable: 1, Variable: 2) @@ -156,6 +160,7 @@ abstract contract AaveResolver is Events, Helpers { /** * @dev Enable collateral + * @notice Enable an array of tokens as collateral * @param tokens Array of tokens to enable collateral */ function enableCollateral( diff --git a/contracts/mainnet/connectors/chi/main.sol b/contracts/mainnet/connectors/chi/main.sol index b808f9c1..d707019a 100644 --- a/contracts/mainnet/connectors/chi/main.sol +++ b/contracts/mainnet/connectors/chi/main.sol @@ -5,7 +5,8 @@ import { Events } from "./events.sol"; abstract contract ChiResolver is Events, Helpers { /** - * @dev Mint CHI token. + * @dev Mint token. + * @notice Mint CHI token. * @param amt token amount to mint. */ function mint(uint amt) public payable returns (string memory _eventName, bytes memory _eventParam) { @@ -18,7 +19,8 @@ abstract contract ChiResolver is Events, Helpers { } /** - * @dev burn CHI token. + * @dev Burn token. + * @notice burns CHI token. * @param amt token amount to burn. */ function burn(uint amt) public payable returns (string memory _eventName, bytes memory _eventParam) { diff --git a/contracts/mainnet/connectors/dydx/main.sol b/contracts/mainnet/connectors/dydx/main.sol index 5cfa9aeb..a113c2ea 100644 --- a/contracts/mainnet/connectors/dydx/main.sol +++ b/contracts/mainnet/connectors/dydx/main.sol @@ -9,6 +9,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Deposit ETH/ERC20_Token. + * @notice Deposit a token to dYdX for lending / collaterization. * @param token token address to deposit.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -46,6 +47,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Withdraw ETH/ERC20_Token. + * @notice Withdraw deposited token from dYdX. * @param token token address to withdraw.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -82,6 +84,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Borrow ETH/ERC20_Token. + * @notice Borrow a token using dYdX * @param token token address to borrow.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to borrow. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -115,6 +118,7 @@ abstract contract DyDxResolver is Events, Helpers { /** * @dev Payback borrowed ETH/ERC20_Token. + * @notice Payback debt owed. * @param token token address to payback.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount to payback. * @param getId Get token amount at this ID from `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors/fee/main.sol b/contracts/mainnet/connectors/fee/main.sol index e08a1bdf..2c4e89e8 100644 --- a/contracts/mainnet/connectors/fee/main.sol +++ b/contracts/mainnet/connectors/fee/main.sol @@ -6,6 +6,7 @@ import { Basic } from "../../common/basic.sol"; abstract contract FeeResolver is DSMath, Basic { /** * @dev Calculate fee + * @notice Calculates fee on a given amount * @param amount token amount to caculate fee. * @param fee fee percentage. Eg: 1% => 1e17, 100% => 1e18. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -31,6 +32,7 @@ abstract contract FeeResolver is DSMath, Basic { /** * @dev Calculate amount minus fee + * @notice Calculates amount minus fee * @param amount token amount to caculate fee. * @param fee fee percentage. Eg: 1% => 1e17, 100% => 1e18. * @param getId Get token amount at this ID from `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors/gelato/main.sol b/contracts/mainnet/connectors/gelato/main.sol index aebc48a2..b852f1d5 100644 --- a/contracts/mainnet/connectors/gelato/main.sol +++ b/contracts/mainnet/connectors/gelato/main.sol @@ -13,12 +13,15 @@ abstract contract GelatoResolver is DSMath, Basic, Events { // ===== Gelato ENTRY APIs ====== /** - * @dev Enables first time users to pre-fund eth, whitelist an executor & register the + * @dev Enables to use Gelato + * @notice Enables first time users to pre-fund eth, whitelist an executor & register the * ProviderModuleDSA.sol to be able to use Gelato * @param _executor address of single execot node or gelato'S decentralized execution market * @param _taskSpecs enables external providers to whitelist TaskSpecs on gelato * @param _modules address of ProviderModuleDSA * @param _ethToDeposit amount of eth to deposit on Gelato, only for self-providers + * @param _getId get token amount at this IDs from `InstaMemory` Contract. + * @param _setId set token amount at this IDs in `InstaMemory` Contract. */ function multiProvide( address _executor, @@ -44,7 +47,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Submits a single, one-time task to Gelato + * @dev Submit task + * @notice Submits a single, one-time task to Gelato * @param _provider Consists of proxy module address (DSA) and provider address () * who will pay for the transaction execution * @param _task Task specifying the condition and the action connectors @@ -62,7 +66,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Submits single or mulitple Task Sequences to Gelato + * @dev Submit Task Sequences + * @notice Submits single or mulitple Task Sequences to Gelato * @param _provider Consists of proxy module address (DSA) and provider address () * who will pay for the transaction execution * @param _tasks A sequence of Tasks, can be a single or multiples @@ -87,7 +92,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Submits single or mulitple Task Chains to Gelato + * @dev Submit Task Chains + * @notice Submits single or mulitple Task Chains to Gelato * @param _provider Consists of proxy module address (DSA) and provider address () * who will pay for the transaction execution * @param _tasks A sequence of Tasks, can be a single or multiples @@ -115,11 +121,15 @@ abstract contract GelatoResolver is DSMath, Basic, Events { // ===== Gelato EXIT APIs ====== /** - * @dev Withdraws funds from Gelato, de-whitelists TaskSpecs and Provider Modules + * @dev Withdraws ETH, de-whitelists Tasks and Modules + * in one tx + * @notice Withdraws funds from Gelato, de-whitelists TaskSpecs and Provider Modules * in one tx * @param _withdrawAmount Amount of ETH to withdraw from Gelato * @param _taskSpecs List of Task Specs to de-whitelist, default empty [] * @param _modules List of Provider Modules to de-whitelist, default empty [] + * @param _getId get token amount at this IDs from `InstaMemory` Contract. + * @param _setId set token amount at this IDs in `InstaMemory` Contract. */ function multiUnprovide( uint256 _withdrawAmount, @@ -146,7 +156,8 @@ abstract contract GelatoResolver is DSMath, Basic, Events { } /** - * @dev Cancels outstanding Tasks + * @dev Cancel Tasks + * @notice Cancels outstanding Tasks on Gelato * @param _taskReceipts List of Task Receipts to cancel */ function multiCancelTasks(TaskReceipt[] calldata _taskReceipts) diff --git a/contracts/mainnet/connectors/instapool/main.sol b/contracts/mainnet/connectors/instapool/main.sol index 9f0504bc..96f75ce8 100644 --- a/contracts/mainnet/connectors/instapool/main.sol +++ b/contracts/mainnet/connectors/instapool/main.sol @@ -9,6 +9,7 @@ import { Events } from "./events.sol"; abstract contract LiquidityManage is Helpers, Events { /** * @dev Deposit Liquidity in InstaPool. + * @notice Deposit Liquidity in InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -40,7 +41,8 @@ abstract contract LiquidityManage is Helpers, Events { } /** - * @dev Withdraw Liquidity in InstaPool. + * @dev Withdraw Liquidity from InstaPool. + * @notice Withdraw Liquidity from InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -64,8 +66,10 @@ abstract contract LiquidityManage is Helpers, Events { abstract contract LiquidityAccessHelper is LiquidityManage { /** - * @dev Add Fee Amount to borrowed flashloan/ - * @param amt Get token amount at this ID from `InstaMemory` Contract. + * @dev Add Fee Amount to borrowed flashloan. + * @notice Add Fee Amount to borrowed flashloan. + * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. */ @@ -81,6 +85,7 @@ abstract contract LiquidityAccessHelper is LiquidityManage { contract LiquidityAccess is LiquidityAccessHelper { /** * @dev Access Token Liquidity from InstaPool. + * @notice Take flashloan from InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amt token amount. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -109,6 +114,7 @@ contract LiquidityAccess is LiquidityAccessHelper { /** * @dev Return Token Liquidity from InstaPool. + * @notice Payback borrowed flashloan to InstaPool. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param getId Get token amount at this ID from `InstaMemory` Contract. * @param setId Set token amount at this ID in `InstaMemory` Contract. @@ -139,6 +145,7 @@ contract LiquidityAccess is LiquidityAccessHelper { /** * @dev Return Token Liquidity from InstaPool and Transfer 20% of Collected Fee to `origin`. + * @notice Payback borrowed flashloan to InstaPool and Transfer 20% of Collected Fee to `origin`. * @param origin origin address to transfer 20% of the collected fee. * @param token token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -177,6 +184,7 @@ contract LiquidityAccess is LiquidityAccessHelper { contract LiquidityAccessMulti is LiquidityAccess { /** * @dev Access Multiple Token liquidity from InstaPool. + * @notice Take multiple tokens flashloan from Instapool. * @param tokens Array of token addresses.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param amts Array of token amount. * @param getId get token amounts at this IDs from `InstaMemory` Contract. @@ -206,6 +214,7 @@ contract LiquidityAccessMulti is LiquidityAccess { /** * @dev Return Multiple token liquidity from InstaPool. + * @notice Payback borrowed multiple tokens flashloan to Instapool. * @param tokens Array of token addresses.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param getId get token amounts at this IDs from `InstaMemory` Contract. * @param setId set token amounts at this IDs in `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors/kyber/main.sol b/contracts/mainnet/connectors/kyber/main.sol index eb90ac80..c1ef72cd 100644 --- a/contracts/mainnet/connectors/kyber/main.sol +++ b/contracts/mainnet/connectors/kyber/main.sol @@ -7,6 +7,7 @@ import { Events } from "./events.sol"; abstract contract KyberResolver is Helpers, Events { /** * @dev Sell ETH/ERC20_Token. + * @notice Sell tokens using Kyber. * @param buyAddr buying token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAddr selling token amount.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAmt selling token amount. diff --git a/contracts/mainnet/connectors/oasis/main.sol b/contracts/mainnet/connectors/oasis/main.sol index 311bdc08..5742b3a5 100644 --- a/contracts/mainnet/connectors/oasis/main.sol +++ b/contracts/mainnet/connectors/oasis/main.sol @@ -14,6 +14,7 @@ contract OasisResolver is DSMath, Basic, Events { /** * @dev Buy ETH/ERC20_Token. + * @notice Buy tokens using Oasis. * @param buyAddr buying token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAddr selling token amount.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param buyAmt buying token amount. @@ -64,6 +65,7 @@ contract OasisResolver is DSMath, Basic, Events { /** * @dev Sell ETH/ERC20_Token. + * @notice Sell tokens using Oasis. * @param buyAddr buying token address.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAddr selling token amount.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) * @param sellAmt selling token amount. diff --git a/contracts/mainnet/connectors/reflexer/main.sol b/contracts/mainnet/connectors/reflexer/main.sol index f00dcb1a..bba0ea5a 100644 --- a/contracts/mainnet/connectors/reflexer/main.sol +++ b/contracts/mainnet/connectors/reflexer/main.sol @@ -8,6 +8,7 @@ import { SafeEngineLike, TokenJoinInterface } from "./interface.sol"; abstract contract GebResolver is Helpers, Events { /** * @dev Open Safe + * @notice Open a Reflexer Safe. * @param colType Type of Collateral.(eg: 'ETH-A') */ function open(string calldata colType) external payable returns (string memory _eventName, bytes memory _eventParam) { @@ -21,6 +22,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Close Safe + * @notice Close a Reflexer Safe. * @param safe Safe ID to close. */ function close(uint256 safe) external payable returns (string memory _eventName, bytes memory _eventParam) { @@ -39,6 +41,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Deposit ETH/ERC20_Token Collateral. + * @notice Deposit collateral to a Reflexer safe * @param safe Safe ID. * @param amt token amount to deposit. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -85,6 +88,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Withdraw ETH/ERC20_Token Collateral. + * @notice Withdraw collateral from a Reflexer Safe * @param safe Safe ID. * @param amt token amount to withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -140,6 +144,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Borrow Coin. + * @notice Borrow Coin using a Reflexer safe * @param safe Safe ID. * @param amt token amount to borrow. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -188,6 +193,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Payback borrowed Coin. + * @notice Payback Coin debt owed by a Reflexer safe * @param safe Safe ID. * @param amt token amount to payback. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -233,6 +239,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Withdraw leftover ETH/ERC20_Token after Liquidation. + * @notice Withdraw leftover collateral after Liquidation. * @param safe Safe ID. * @param amt token amount to Withdraw. * @param getId Get token amount at this ID from `InstaMemory` Contract. @@ -283,8 +290,10 @@ abstract contract GebResolver is Helpers, Events { SafeEngineLike safeEngineContract; TokenInterface tokenContract; } + /** * @dev Deposit ETH/ERC20_Token Collateral and Borrow Coin. + * @notice Deposit collateral and borrow Coin. * @param safe Safe ID. * @param depositAmt token deposit amount to Withdraw. * @param borrowAmt token borrow amount to Withdraw. @@ -365,6 +374,7 @@ abstract contract GebResolver is Helpers, Events { /** * @dev Exit Coin from handler. + * @notice Exit Coin from handler. * @param safe Safe ID. * @param amt token amount to exit. * @param getId Get token amount at this ID from `InstaMemory` Contract. diff --git a/contracts/mainnet/connectors_old/refinance.sol b/contracts/mainnet/connectors_old/refinance.sol new file mode 100644 index 00000000..8f992569 --- /dev/null +++ b/contracts/mainnet/connectors_old/refinance.sol @@ -0,0 +1,1055 @@ +pragma solidity ^0.7.0; +pragma experimental ABIEncoderV2; + +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/SafeERC20.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +interface TokenInterface { + function approve(address, uint256) external; + function transfer(address, uint) external; + function transferFrom(address, address, uint) external; + function deposit() external payable; + function withdraw(uint) external; + function balanceOf(address) external view returns (uint); + function decimals() external view returns (uint); +} + +// Compound Helpers +interface CTokenInterface { + function mint(uint mintAmount) external returns (uint); + function redeem(uint redeemTokens) external returns (uint); + function borrow(uint borrowAmount) external returns (uint); + function repayBorrow(uint repayAmount) external returns (uint); + + function borrowBalanceCurrent(address account) external returns (uint); + function redeemUnderlying(uint redeemAmount) external returns (uint); + + function balanceOf(address owner) external view returns (uint256 balance); +} + +interface CETHInterface { + function mint() external payable; + function repayBorrow() external payable; +} + +interface CompoundMappingInterface { + function cTokenMapping(string calldata tokenId) external view returns (address); + function getMapping(string calldata tokenId) external view returns (address, address); +} + +interface ComptrollerInterface { + function enterMarkets(address[] calldata cTokens) external returns (uint[] memory); +} +// End Compound Helpers + +// Aave v1 Helpers +interface AaveV1Interface { + function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external payable; + function redeemUnderlying( + address _reserve, + address payable _user, + uint256 _amount, + uint256 _aTokenBalanceAfterRedeem + ) external; + + function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral) external; + function getUserReserveData(address _reserve, address _user) external view returns ( + uint256 currentATokenBalance, + uint256 currentBorrowBalance, + uint256 principalBorrowBalance, + uint256 borrowRateMode, + uint256 borrowRate, + uint256 liquidityRate, + uint256 originationFee, + uint256 variableBorrowIndex, + uint256 lastUpdateTimestamp, + bool usageAsCollateralEnabled + ); + function borrow(address _reserve, uint256 _amount, uint256 _interestRateMode, uint16 _referralCode) external; + function repay(address _reserve, uint256 _amount, address payable _onBehalfOf) external payable; +} + +interface AaveV1ProviderInterface { + function getLendingPool() external view returns (address); + function getLendingPoolCore() external view returns (address); +} + +interface AaveV1CoreInterface { + function getReserveATokenAddress(address _reserve) external view returns (address); +} + +interface ATokenV1Interface { + function redeem(uint256 _amount) external; + function balanceOf(address _user) external view returns(uint256); + function principalBalanceOf(address _user) external view returns(uint256); + + function allowance(address, address) external view returns (uint); + function approve(address, uint) external; + function transfer(address, uint) external returns (bool); + function transferFrom(address, address, uint) external returns (bool); +} +// End Aave v1 Helpers + +// Aave v2 Helpers +interface AaveV2Interface { + function deposit(address _asset, uint256 _amount, address _onBehalfOf, uint16 _referralCode) external; + function withdraw(address _asset, uint256 _amount, address _to) external; + function borrow( + address _asset, + uint256 _amount, + uint256 _interestRateMode, + uint16 _referralCode, + address _onBehalfOf + ) external; + function repay(address _asset, uint256 _amount, uint256 _rateMode, address _onBehalfOf) external; + function setUserUseReserveAsCollateral(address _asset, bool _useAsCollateral) external; +} + +interface AaveV2LendingPoolProviderInterface { + function getLendingPool() external view returns (address); +} + +// Aave Protocol Data Provider +interface AaveV2DataProviderInterface { + function getUserReserveData(address _asset, address _user) external view returns ( + uint256 currentATokenBalance, + uint256 currentStableDebt, + uint256 currentVariableDebt, + uint256 principalStableDebt, + uint256 scaledVariableDebt, + uint256 stableBorrowRate, + uint256 liquidityRate, + uint40 stableRateLastUpdated, + bool usageAsCollateralEnabled + ); +} +// End Aave v2 Helpers + +contract DSMath { + + uint constant WAD = 10 ** 18; + uint constant RAY = 10 ** 27; + + function add(uint x, uint y) internal pure returns (uint z) { + require((z = x + y) >= x, "math-not-safe"); + } + + function sub(uint x, uint y) internal pure returns (uint z) { + require((z = x - y) <= x, "sub-overflow"); + } + + function mul(uint x, uint y) internal pure returns (uint z) { + require(y == 0 || (z = x * y) / y == x, "math-not-safe"); + } + + function wmul(uint x, uint y) internal pure returns (uint z) { + z = add(mul(x, y), WAD / 2) / WAD; + } +} + +contract Helpers is DSMath { + + using SafeERC20 for IERC20; + + enum Protocol { + Aave, + AaveV2, + Compound + } + + address payable constant feeCollector = 0xb1DC62EC38E6E3857a887210C38418E4A17Da5B2; + + /** + * @dev Return ethereum address + */ + function getEthAddr() internal pure returns (address) { + return 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; // ETH Address + } + + /** + * @dev Return Weth address + */ + function getWethAddr() internal pure returns (address) { + return 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; // Mainnet WETH Address + // return 0xd0A1E359811322d97991E03f863a0C30C2cF029C; // Kovan WETH Address + } + + /** + * @dev Return InstaDApp Mapping Address + */ + function getMappingAddr() internal pure returns (address) { + return 0xA8F9D4aA7319C54C04404765117ddBf9448E2082; // CompoundMapping Address + } + + /** + * @dev Return Compound Comptroller Address + */ + function getComptrollerAddress() internal pure returns (address) { + return 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B; + } + + /** + * @dev get Aave Provider + */ + function getAaveProvider() internal pure returns (AaveV1ProviderInterface) { + return AaveV1ProviderInterface(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8); //mainnet + // return AaveV1ProviderInterface(0x506B0B2CF20FAA8f38a4E2B524EE43e1f4458Cc5); //kovan + } + + /** + * @dev get Aave Lending Pool Provider + */ + function getAaveV2Provider() internal pure returns (AaveV2LendingPoolProviderInterface) { + return AaveV2LendingPoolProviderInterface(0xB53C1a33016B2DC2fF3653530bfF1848a515c8c5); //mainnet + // return AaveV2LendingPoolProviderInterface(0x652B2937Efd0B5beA1c8d54293FC1289672AFC6b); //kovan + } + + /** + * @dev get Aave Protocol Data Provider + */ + function getAaveV2DataProvider() internal pure returns (AaveV2DataProviderInterface) { + return AaveV2DataProviderInterface(0x057835Ad21a177dbdd3090bB1CAE03EaCF78Fc6d); //mainnet + // return AaveV2DataProviderInterface(0x744C1aaA95232EeF8A9994C4E0b3a89659D9AB79); //kovan + } + + /** + * @dev get Referral Code + */ + function getReferralCode() internal pure returns (uint16) { + return 3228; + } + + function getWithdrawBalance(AaveV1Interface aave, address token) internal view returns (uint bal) { + (bal, , , , , , , , , ) = aave.getUserReserveData(token, address(this)); + } + + function getPaybackBalance(AaveV1Interface aave, address token) internal view returns (uint bal, uint fee) { + (, bal, , , , , fee, , , ) = aave.getUserReserveData(token, address(this)); + } + + function getTotalBorrowBalance(AaveV1Interface aave, address token) internal view returns (uint amt) { + (, uint bal, , , , , uint fee, , , ) = aave.getUserReserveData(token, address(this)); + amt = add(bal, fee); + } + + function getWithdrawBalanceV2(AaveV2DataProviderInterface aaveData, address token) internal view returns (uint bal) { + (bal, , , , , , , , ) = aaveData.getUserReserveData(token, address(this)); + } + + function getPaybackBalanceV2(AaveV2DataProviderInterface aaveData, address token, uint rateMode) internal view returns (uint bal) { + if (rateMode == 1) { + (, bal, , , , , , , ) = aaveData.getUserReserveData(token, address(this)); + } else { + (, , bal, , , , , , ) = aaveData.getUserReserveData(token, address(this)); + } + } + + function getIsColl(AaveV1Interface aave, address token) internal view returns (bool isCol) { + (, , , , , , , , , isCol) = aave.getUserReserveData(token, address(this)); + } + + function getIsCollV2(AaveV2DataProviderInterface aaveData, address token) internal view returns (bool isCol) { + (, , , , , , , , isCol) = aaveData.getUserReserveData(token, address(this)); + } + + function convertEthToWeth(bool isEth, TokenInterface token, uint amount) internal { + if(isEth) token.deposit{value:amount}(); + } + + function convertWethToEth(bool isEth, TokenInterface token, uint amount) internal { + if(isEth) { + token.approve(address(token), amount); + token.withdraw(amount); + } + } + + function getMaxBorrow(Protocol target, address token, CTokenInterface ctoken, uint rateMode) internal returns (uint amt) { + AaveV1Interface aaveV1 = AaveV1Interface(getAaveProvider().getLendingPool()); + AaveV2DataProviderInterface aaveData = getAaveV2DataProvider(); + + if (target == Protocol.Aave) { + (uint _amt, uint _fee) = getPaybackBalance(aaveV1, token); + amt = _amt + _fee; + } else if (target == Protocol.AaveV2) { + amt = getPaybackBalanceV2(aaveData, token, rateMode); + } else if (target == Protocol.Compound) { + amt = ctoken.borrowBalanceCurrent(address(this)); + } + } + + function transferFees(address token, uint feeAmt) internal { + if (feeAmt > 0) { + if (token == getEthAddr()) { + feeCollector.transfer(feeAmt); + } else { + IERC20(token).safeTransfer(feeCollector, feeAmt); + } + } + } + + function calculateFee(uint256 amount, uint256 fee, bool toAdd) internal pure returns(uint feeAmount, uint _amount){ + feeAmount = wmul(amount, fee); + _amount = toAdd ? add(amount, feeAmount) : sub(amount, feeAmount); + } + + function getTokenInterfaces(uint length, address[] memory tokens) internal pure returns (TokenInterface[] memory) { + TokenInterface[] memory _tokens = new TokenInterface[](length); + for (uint i = 0; i < length; i++) { + if (tokens[i] == getEthAddr()) { + _tokens[i] = TokenInterface(getWethAddr()); + } else { + _tokens[i] = TokenInterface(tokens[i]); + } + } + return _tokens; + } + + function getCtokenInterfaces(uint length, string[] memory tokenIds) internal view returns (CTokenInterface[] memory) { + CTokenInterface[] memory _ctokens = new CTokenInterface[](length); + for (uint i = 0; i < length; i++) { + (address token, address cToken) = CompoundMappingInterface(getMappingAddr()).getMapping(tokenIds[i]); + require(token != address(0) && cToken != address(0), "invalid token/ctoken address"); + _ctokens[i] = CTokenInterface(cToken); + } + return _ctokens; + } +} + +contract CompoundHelpers is Helpers { + + struct CompoundBorrowData { + uint length; + uint fee; + Protocol target; + CTokenInterface[] ctokens; + TokenInterface[] tokens; + uint[] amts; + uint[] rateModes; + } + + function _compEnterMarkets(uint length, CTokenInterface[] memory ctokens) internal { + ComptrollerInterface troller = ComptrollerInterface(getComptrollerAddress()); + address[] memory _cTokens = new address[](length); + + for (uint i = 0; i < length; i++) { + _cTokens[i] = address(ctokens[i]); + } + troller.enterMarkets(_cTokens); + } + + function _compBorrowOne( + uint fee, + CTokenInterface ctoken, + TokenInterface token, + uint amt, + Protocol target, + uint rateMode + ) internal returns (uint) { + if (amt > 0) { + address _token = address(token) == getWethAddr() ? getEthAddr() : address(token); + + if (amt == uint(-1)) { + amt = getMaxBorrow(target, address(token), ctoken, rateMode); + } + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, true); + + require(ctoken.borrow(_amt) == 0, "borrow-failed-collateral?"); + transferFees(_token, feeAmt); + } + return amt; + } + + function _compBorrow( + CompoundBorrowData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _compBorrowOne( + data.fee, + data.ctokens[i], + data.tokens[i], + data.amts[i], + data.target, + data.rateModes[i] + ); + } + return finalAmts; + } + + function _compDepositOne(uint fee, CTokenInterface ctoken, TokenInterface token, uint amt) internal { + if (amt > 0) { + address _token = address(token) == getWethAddr() ? getEthAddr() : address(token); + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); + + if (_token != getEthAddr()) { + token.approve(address(ctoken), _amt); + require(ctoken.mint(_amt) == 0, "deposit-failed"); + } else { + CETHInterface(address(ctoken)).mint{value:_amt}(); + } + transferFees(_token, feeAmt); + } + } + + function _compDeposit( + uint length, + uint fee, + CTokenInterface[] memory ctokens, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _compDepositOne(fee, ctokens[i], tokens[i], amts[i]); + } + } + + function _compWithdrawOne(CTokenInterface ctoken, TokenInterface token, uint amt) internal returns (uint) { + if (amt > 0) { + if (amt == uint(-1)) { + bool isEth = address(token) == getWethAddr(); + uint initalBal = isEth ? address(this).balance : token.balanceOf(address(this)); + require(ctoken.redeem(ctoken.balanceOf(address(this))) == 0, "withdraw-failed"); + uint finalBal = isEth ? address(this).balance : token.balanceOf(address(this)); + amt = sub(finalBal, initalBal); + } else { + require(ctoken.redeemUnderlying(amt) == 0, "withdraw-failed"); + } + } + return amt; + } + + function _compWithdraw( + uint length, + CTokenInterface[] memory ctokens, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal returns(uint[] memory) { + uint[] memory finalAmts = new uint[](length); + for (uint i = 0; i < length; i++) { + finalAmts[i] = _compWithdrawOne(ctokens[i], tokens[i], amts[i]); + } + return finalAmts; + } + + function _compPaybackOne(CTokenInterface ctoken, TokenInterface token, uint amt) internal returns (uint) { + if (amt > 0) { + if (amt == uint(-1)) { + amt = ctoken.borrowBalanceCurrent(address(this)); + } + if (address(token) != getWethAddr()) { + token.approve(address(ctoken), amt); + require(ctoken.repayBorrow(amt) == 0, "repay-failed."); + } else { + CETHInterface(address(ctoken)).repayBorrow{value:amt}(); + } + } + return amt; + } + + function _compPayback( + uint length, + CTokenInterface[] memory ctokens, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _compPaybackOne(ctokens[i], tokens[i], amts[i]); + } + } +} + +contract AaveV1Helpers is CompoundHelpers { + + struct AaveV1BorrowData { + AaveV1Interface aave; + uint length; + uint fee; + Protocol target; + TokenInterface[] tokens; + CTokenInterface[] ctokens; + uint[] amts; + uint[] borrowRateModes; + uint[] paybackRateModes; + } + + struct AaveV1DepositData { + AaveV1Interface aave; + AaveV1CoreInterface aaveCore; + uint length; + uint fee; + TokenInterface[] tokens; + uint[] amts; + } + + function _aaveV1BorrowOne( + AaveV1Interface aave, + uint fee, + Protocol target, + TokenInterface token, + CTokenInterface ctoken, + uint amt, + uint borrowRateMode, + uint paybackRateMode + ) internal returns (uint) { + if (amt > 0) { + + address _token = address(token) == getWethAddr() ? getEthAddr() : address(token); + + if (amt == uint(-1)) { + amt = getMaxBorrow(target, address(token), ctoken, paybackRateMode); + } + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, true); + + aave.borrow(_token, _amt, borrowRateMode, getReferralCode()); + transferFees(_token, feeAmt); + } + return amt; + } + + function _aaveV1Borrow( + AaveV1BorrowData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _aaveV1BorrowOne( + data.aave, + data.fee, + data.target, + data.tokens[i], + data.ctokens[i], + data.amts[i], + data.borrowRateModes[i], + data.paybackRateModes[i] + ); + } + return finalAmts; + } + + function _aaveV1DepositOne( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + uint fee, + TokenInterface token, + uint amt + ) internal { + if (amt > 0) { + uint ethAmt; + (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); + + bool isEth = address(token) == getWethAddr(); + + address _token = isEth ? getEthAddr() : address(token); + + if (isEth) { + ethAmt = _amt; + } else { + token.approve(address(aaveCore), _amt); + } + + transferFees(_token, feeAmt); + + aave.deposit{value:ethAmt}(_token, _amt, getReferralCode()); + + if (!getIsColl(aave, _token)) + aave.setUserUseReserveAsCollateral(_token, true); + } + } + + function _aaveV1Deposit( + AaveV1DepositData memory data + ) internal { + for (uint i = 0; i < data.length; i++) { + _aaveV1DepositOne( + data.aave, + data.aaveCore, + data.fee, + data.tokens[i], + data.amts[i] + ); + } + } + + function _aaveV1WithdrawOne( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + TokenInterface token, + uint amt + ) internal returns (uint) { + if (amt > 0) { + address _token = address(token) == getWethAddr() ? getEthAddr() : address(token); + ATokenV1Interface atoken = ATokenV1Interface(aaveCore.getReserveATokenAddress(_token)); + if (amt == uint(-1)) { + amt = getWithdrawBalance(aave, _token); + } + atoken.redeem(amt); + } + return amt; + } + + function _aaveV1Withdraw( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + uint length, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](length); + for (uint i = 0; i < length; i++) { + finalAmts[i] = _aaveV1WithdrawOne(aave, aaveCore, tokens[i], amts[i]); + } + return finalAmts; + } + + function _aaveV1PaybackOne( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + TokenInterface token, + uint amt + ) internal returns (uint) { + if (amt > 0) { + uint ethAmt; + + bool isEth = address(token) == getWethAddr(); + + address _token = isEth ? getEthAddr() : address(token); + + if (amt == uint(-1)) { + (uint _amt, uint _fee) = getPaybackBalance(aave, _token); + amt = _amt + _fee; + } + + if (isEth) { + ethAmt = amt; + } else { + token.approve(address(aaveCore), amt); + } + + aave.repay{value:ethAmt}(_token, amt, payable(address(this))); + } + return amt; + } + + function _aaveV1Payback( + AaveV1Interface aave, + AaveV1CoreInterface aaveCore, + uint length, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _aaveV1PaybackOne(aave, aaveCore, tokens[i], amts[i]); + } + } +} + +contract AaveV2Helpers is AaveV1Helpers { + + struct AaveV2BorrowData { + AaveV2Interface aave; + uint length; + uint fee; + Protocol target; + TokenInterface[] tokens; + CTokenInterface[] ctokens; + uint[] amts; + uint[] rateModes; + } + + struct AaveV2PaybackData { + AaveV2Interface aave; + AaveV2DataProviderInterface aaveData; + uint length; + TokenInterface[] tokens; + uint[] amts; + uint[] rateModes; + } + + struct AaveV2WithdrawData { + AaveV2Interface aave; + AaveV2DataProviderInterface aaveData; + uint length; + TokenInterface[] tokens; + uint[] amts; + } + + function _aaveV2BorrowOne( + AaveV2Interface aave, + uint fee, + Protocol target, + TokenInterface token, + CTokenInterface ctoken, + uint amt, + uint rateMode + ) internal returns (uint) { + if (amt > 0) { + bool isEth = address(token) == getWethAddr(); + + address _token = isEth ? getEthAddr() : address(token); + + if (amt == uint(-1)) { + amt = getMaxBorrow(target, _token, ctoken, rateMode); + } + + (uint feeAmt, uint _amt) = calculateFee(amt, fee, true); + + aave.borrow(address(token), _amt, rateMode, getReferralCode(), address(this)); + convertWethToEth(isEth, token, amt); + + transferFees(_token, feeAmt); + } + return amt; + } + + function _aaveV2Borrow( + AaveV2BorrowData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _aaveV2BorrowOne( + data.aave, + data.fee, + data.target, + data.tokens[i], + data.ctokens[i], + data.amts[i], + data.rateModes[i] + ); + } + return finalAmts; + } + + function _aaveV2DepositOne( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + uint fee, + TokenInterface token, + uint amt + ) internal { + if (amt > 0) { + (uint feeAmt, uint _amt) = calculateFee(amt, fee, false); + + bool isEth = address(token) == getWethAddr(); + address _token = isEth ? getEthAddr() : address(token); + + transferFees(_token, feeAmt); + + convertEthToWeth(isEth, token, _amt); + + token.approve(address(aave), _amt); + + aave.deposit(address(token), _amt, address(this), getReferralCode()); + + if (!getIsCollV2(aaveData, address(token))) { + aave.setUserUseReserveAsCollateral(address(token), true); + } + } + } + + function _aaveV2Deposit( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + uint length, + uint fee, + TokenInterface[] memory tokens, + uint[] memory amts + ) internal { + for (uint i = 0; i < length; i++) { + _aaveV2DepositOne(aave, aaveData, fee, tokens[i], amts[i]); + } + } + + function _aaveV2WithdrawOne( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + TokenInterface token, + uint amt + ) internal returns (uint _amt) { + if (amt > 0) { + bool isEth = address(token) == getWethAddr(); + + aave.withdraw(address(token), amt, address(this)); + + _amt = amt == uint(-1) ? getWithdrawBalanceV2(aaveData, address(token)) : amt; + + convertWethToEth(isEth, token, _amt); + } + } + + function _aaveV2Withdraw( + AaveV2WithdrawData memory data + ) internal returns (uint[] memory) { + uint[] memory finalAmts = new uint[](data.length); + for (uint i = 0; i < data.length; i++) { + finalAmts[i] = _aaveV2WithdrawOne( + data.aave, + data.aaveData, + data.tokens[i], + data.amts[i] + ); + } + return finalAmts; + } + + function _aaveV2PaybackOne( + AaveV2Interface aave, + AaveV2DataProviderInterface aaveData, + TokenInterface token, + uint amt, + uint rateMode + ) internal returns (uint _amt) { + if (amt > 0) { + bool isEth = address(token) == getWethAddr(); + + _amt = amt == uint(-1) ? getPaybackBalanceV2(aaveData, address(token), rateMode) : amt; + + convertEthToWeth(isEth, token, _amt); + + token.approve(address(aave), _amt); + + aave.repay(address(token), _amt, rateMode, address(this)); + } + } + + function _aaveV2Payback( + AaveV2PaybackData memory data + ) internal { + for (uint i = 0; i < data.length; i++) { + _aaveV2PaybackOne( + data.aave, + data.aaveData, + data.tokens[i], + data.amts[i], + data.rateModes[i] + ); + } + } +} + +contract RefinanceResolver is AaveV2Helpers { + + struct RefinanceData { + Protocol source; + Protocol target; + uint collateralFee; + uint debtFee; + address[] tokens; + string[] ctokenIds; + uint[] borrowAmts; + uint[] withdrawAmts; + uint[] borrowRateModes; + uint[] paybackRateModes; + } + + function refinance(RefinanceData calldata data) external payable { + + require(data.source != data.target, "source-and-target-unequal"); + + uint length = data.tokens.length; + + require(data.borrowAmts.length == length, "length-mismatch"); + require(data.withdrawAmts.length == length, "length-mismatch"); + require(data.borrowRateModes.length == length, "length-mismatch"); + require(data.paybackRateModes.length == length, "length-mismatch"); + require(data.ctokenIds.length == length, "length-mismatch"); + + AaveV2Interface aaveV2 = AaveV2Interface(getAaveV2Provider().getLendingPool()); + AaveV1Interface aaveV1 = AaveV1Interface(getAaveProvider().getLendingPool()); + AaveV1CoreInterface aaveCore = AaveV1CoreInterface(getAaveProvider().getLendingPoolCore()); + AaveV2DataProviderInterface aaveData = getAaveV2DataProvider(); + + uint[] memory depositAmts; + uint[] memory paybackAmts; + + TokenInterface[] memory tokens = getTokenInterfaces(length, data.tokens); + CTokenInterface[] memory _ctokens = getCtokenInterfaces(length, data.ctokenIds); + + if (data.source == Protocol.Aave && data.target == Protocol.AaveV2) { + AaveV2BorrowData memory _aaveV2BorrowData; + + _aaveV2BorrowData.aave = aaveV2; + _aaveV2BorrowData.length = length; + _aaveV2BorrowData.fee = data.debtFee; + _aaveV2BorrowData.target = data.source; + _aaveV2BorrowData.tokens = tokens; + _aaveV2BorrowData.ctokens = _ctokens; + _aaveV2BorrowData.amts = data.borrowAmts; + _aaveV2BorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); + _aaveV1Payback(aaveV1, aaveCore, length, tokens, paybackAmts); + depositAmts = _aaveV1Withdraw(aaveV1, aaveCore, length, tokens, data.withdrawAmts); + _aaveV2Deposit(aaveV2, aaveData, length, data.collateralFee, tokens, depositAmts); + } else if (data.source == Protocol.Aave && data.target == Protocol.Compound) { + _compEnterMarkets(length, _ctokens); + + CompoundBorrowData memory _compoundBorrowData; + + _compoundBorrowData.length = length; + _compoundBorrowData.fee = data.debtFee; + _compoundBorrowData.target = data.source; + _compoundBorrowData.ctokens = _ctokens; + _compoundBorrowData.tokens = tokens; + _compoundBorrowData.amts = data.borrowAmts; + _compoundBorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _compBorrow(_compoundBorrowData); + + _aaveV1Payback(aaveV1, aaveCore, length, tokens, paybackAmts); + depositAmts = _aaveV1Withdraw(aaveV1, aaveCore, length, tokens, data.withdrawAmts); + _compDeposit(length, data.collateralFee, _ctokens, tokens, depositAmts); + } else if (data.source == Protocol.AaveV2 && data.target == Protocol.Aave) { + + AaveV1BorrowData memory _aaveV1BorrowData; + AaveV2PaybackData memory _aaveV2PaybackData; + AaveV2WithdrawData memory _aaveV2WithdrawData; + + { + _aaveV1BorrowData.aave = aaveV1; + _aaveV1BorrowData.length = length; + _aaveV1BorrowData.fee = data.debtFee; + _aaveV1BorrowData.target = data.source; + _aaveV1BorrowData.tokens = tokens; + _aaveV1BorrowData.ctokens = _ctokens; + _aaveV1BorrowData.amts = data.borrowAmts; + _aaveV1BorrowData.borrowRateModes = data.borrowRateModes; + _aaveV1BorrowData.paybackRateModes = data.paybackRateModes; + + paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); + } + + { + _aaveV2PaybackData.aave = aaveV2; + _aaveV2PaybackData.aaveData = aaveData; + _aaveV2PaybackData.length = length; + _aaveV2PaybackData.tokens = tokens; + _aaveV2PaybackData.amts = paybackAmts; + _aaveV2PaybackData.rateModes = data.paybackRateModes; + _aaveV2Payback(_aaveV2PaybackData); + } + + { + _aaveV2WithdrawData.aave = aaveV2; + _aaveV2WithdrawData.aaveData = aaveData; + _aaveV2WithdrawData.length = length; + _aaveV2WithdrawData.tokens = tokens; + _aaveV2WithdrawData.amts = data.withdrawAmts; + depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); + } + { + AaveV1DepositData memory _aaveV1DepositData; + + _aaveV1DepositData.aave = aaveV1; + _aaveV1DepositData.aaveCore = aaveCore; + _aaveV1DepositData.length = length; + _aaveV1DepositData.fee = data.collateralFee; + _aaveV1DepositData.tokens = tokens; + _aaveV1DepositData.amts = depositAmts; + + _aaveV1Deposit(_aaveV1DepositData); + } + } else if (data.source == Protocol.AaveV2 && data.target == Protocol.Compound) { + _compEnterMarkets(length, _ctokens); + + { + CompoundBorrowData memory _compoundBorrowData; + + _compoundBorrowData.length = length; + _compoundBorrowData.fee = data.debtFee; + _compoundBorrowData.target = data.source; + _compoundBorrowData.ctokens = _ctokens; + _compoundBorrowData.tokens = tokens; + _compoundBorrowData.amts = data.borrowAmts; + _compoundBorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _compBorrow(_compoundBorrowData); + } + + AaveV2PaybackData memory _aaveV2PaybackData; + + _aaveV2PaybackData.aave = aaveV2; + _aaveV2PaybackData.aaveData = aaveData; + _aaveV2PaybackData.length = length; + _aaveV2PaybackData.tokens = tokens; + _aaveV2PaybackData.amts = paybackAmts; + _aaveV2PaybackData.rateModes = data.paybackRateModes; + + _aaveV2Payback(_aaveV2PaybackData); + + { + AaveV2WithdrawData memory _aaveV2WithdrawData; + + _aaveV2WithdrawData.aave = aaveV2; + _aaveV2WithdrawData.aaveData = aaveData; + _aaveV2WithdrawData.length = length; + _aaveV2WithdrawData.tokens = tokens; + _aaveV2WithdrawData.amts = data.withdrawAmts; + depositAmts = _aaveV2Withdraw(_aaveV2WithdrawData); + } + _compDeposit(length, data.collateralFee, _ctokens, tokens, depositAmts); + } else if (data.source == Protocol.Compound && data.target == Protocol.Aave) { + + AaveV1BorrowData memory _aaveV1BorrowData; + + _aaveV1BorrowData.aave = aaveV1; + _aaveV1BorrowData.length = length; + _aaveV1BorrowData.fee = data.debtFee; + _aaveV1BorrowData.target = data.source; + _aaveV1BorrowData.tokens = tokens; + _aaveV1BorrowData.ctokens = _ctokens; + _aaveV1BorrowData.amts = data.borrowAmts; + _aaveV1BorrowData.borrowRateModes = data.borrowRateModes; + _aaveV1BorrowData.paybackRateModes = data.paybackRateModes; + + paybackAmts = _aaveV1Borrow(_aaveV1BorrowData); + { + _compPayback(length, _ctokens, tokens, paybackAmts); + depositAmts = _compWithdraw(length, _ctokens, tokens, data.withdrawAmts); + } + + { + AaveV1DepositData memory _aaveV1DepositData; + + _aaveV1DepositData.aave = aaveV1; + _aaveV1DepositData.aaveCore = aaveCore; + _aaveV1DepositData.length = length; + _aaveV1DepositData.fee = data.collateralFee; + _aaveV1DepositData.tokens = tokens; + _aaveV1DepositData.amts = depositAmts; + + _aaveV1Deposit(_aaveV1DepositData); + } + } else if (data.source == Protocol.Compound && data.target == Protocol.AaveV2) { + AaveV2BorrowData memory _aaveV2BorrowData; + + _aaveV2BorrowData.aave = aaveV2; + _aaveV2BorrowData.length = length; + _aaveV2BorrowData.fee = data.debtFee; + _aaveV2BorrowData.target = data.source; + _aaveV2BorrowData.tokens = tokens; + _aaveV2BorrowData.ctokens = _ctokens; + _aaveV2BorrowData.amts = data.borrowAmts; + _aaveV2BorrowData.rateModes = data.borrowRateModes; + + paybackAmts = _aaveV2Borrow(_aaveV2BorrowData); + _compPayback(length, _ctokens, tokens, paybackAmts); + depositAmts = _compWithdraw(length, _ctokens, tokens, data.withdrawAmts); + _aaveV2Deposit(aaveV2, aaveData, length, data.collateralFee, tokens, depositAmts); + } else { + revert("invalid-options"); + } + } +} + +contract ConnectRefinance is RefinanceResolver { + /** + * @dev Connector Details. + */ + function connectorID() public pure returns(uint _type, uint _id) { + (_type, _id) = (1, 96); + } + + string public name = "Refinance-v1.2"; +} \ No newline at end of file diff --git a/contracts/polygon/connectors/paraswap/main.sol b/contracts/polygon/connectors/paraswap/main.sol index ed8a4b34..72eb0285 100644 --- a/contracts/polygon/connectors/paraswap/main.sol +++ b/contracts/polygon/connectors/paraswap/main.sol @@ -5,6 +5,16 @@ import { Stores } from "../../common/stores.sol"; import { Helpers } from "./helpers.sol"; abstract contract ParaswapResolver is Helpers { + /** + * @dev Sell ETH/ERC20_Token using ParaSwap. + * @notice Swap tokens from exchanges like kyber, 0x etc, with calculation done off-chain. + * @param buyAddr The address of the token to buy.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAddr The address of the token to sell.(For ETH: 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE) + * @param sellAmt The amount of the token to sell. + * @param unitAmt The amount of buyAmt/sellAmt with slippage. + * @param callData Data from 1inch API. + * @param setId ID stores the amount of token brought. + */ function swap( address buyAddr, address sellAddr, diff --git a/package.json b/package.json index 4d64b041..b75eeb9c 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,9 @@ "scripts": { "test": "hardhat test", "coverage": "./node_modules/.bin/solidity-coverage", - "build-contracts": "sol-merger \"./contracts/connectors/mock.sol\" ./contracts/build" + "check-husky": "node status-checks/huskyCheck.js", + "build-contracts": "sol-merger \"./contracts/connectors/mock.sol\" ./contracts/build", + "prepare": "husky install" }, "repository": { "type": "git", @@ -39,6 +41,7 @@ "@tenderly/hardhat-tenderly": "^1.0.6", "ethers": "^5.0.32", "hardhat": "^2.0.8", + "husky": "^6.0.0", "sol-merger": "^2.0.1", "solidity-coverage": "0.5.11", "web3": "^1.3.4" diff --git a/status-checks/check.js b/status-checks/check.js new file mode 100644 index 00000000..3eeb6184 --- /dev/null +++ b/status-checks/check.js @@ -0,0 +1,317 @@ +const fs = require('fs') +const path = require('path') + +const forbiddenStrings = ['selfdestruct'] + +const getConnectorsList = async () => { + try { + const connectors = [] + const connectorsRootsDirs = ['mainnet', 'polygon'] + for (let index = 0; index < connectorsRootsDirs.length; index++) { + const root = `contracts/${connectorsRootsDirs[index]}/connectors` + const dirs = [root] + while (dirs.length) { + const currentDir = dirs.pop() + const subs = fs.readdirSync(currentDir, { withFileTypes: true }) + for (let index = 0; index < subs.length; index++) { + const sub = subs[index] + if (sub.isFile() && sub.name === 'main.sol') { + connectors.push(currentDir) + } else if (sub.isDirectory()) { + dirs.push(`${currentDir}/${sub.name}`) + } + } + } + } + return connectors.map(dir => ({ path: dir })) + } catch (error) { + return Promise.reject(error) + } +} + +const checkCodeForbidden = async (code, codePath) => { + try { + const forbidden = [] + for (let i1 = 0; i1 < forbiddenStrings.length; i1++) { + const forbiddenStr = forbiddenStrings[i1] + const strs = code.split('\n') + for (let i2 = 0; i2 < strs.length; i2++) { + if (strs[i2].includes(forbiddenStr)) { + forbidden.push(`found '${forbiddenStr}' in ${codePath}:${i2 + 1}`) + } + } + } + return forbidden + } catch (error) { + return Promise.reject(error) + } +} + +const checkForbidden = async (parentPath, codePath = './main.sol') => { + try { + if (codePath.startsWith('@')) { + codePath = path.resolve('node_modules', `./${codePath}`) + } else { + codePath = path.resolve(parentPath, codePath) + } + const code = fs.readFileSync(codePath, { encoding: 'utf8' }) + const forbidden = await checkCodeForbidden(code, codePath) + if (code.includes('import')) { + const importsPathes = code + .split('\n') + .filter(str => str.includes('import') && str.includes('from') && str.includes('.sol')) + .map(str => str.split('from')[1].replace(/["; ]/gi, '')) + for (let index = 0; index < importsPathes.length; index++) { + const forbiddenErrors = await checkForbidden( + path.parse(codePath).dir, + importsPathes[index] + ) + forbidden.push(...forbiddenErrors) + } + } + return codePath.endsWith('main.sol') ? { forbiddenErrors: forbidden, code } : forbidden + } catch (error) { + return Promise.reject(error) + } +} + +const checkEvents = async (connector) => { + try { + const errors = [] + const warnings = [] + const eventsPath = `${connector.path}/events.sol` + const mainPath = `${connector.path}/main.sol` + if (connector.events.length) { + const eventNames = [] + for (let i1 = 0; i1 < connector.mainEvents.length; i1++) { + const mainEvent = connector.mainEvents[i1] + const name = mainEvent.split('(')[0] + eventNames.push(name) + const event = connector.events.find(e => e.split('(')[0].split(' ')[1] === name) + if (event) { + const mainEventArgs = mainEvent.split('(')[1].split(')')[0].split(',').map(a => a.trim()) + const eventArgs = event.split('(')[1].split(')')[0].split(',').map(a => a.trim()) + if (mainEventArgs.length !== eventArgs.length) { + errors.push(`arguments amount don't match for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`) + continue + } + for (let i2 = 0; i2 < mainEventArgs.length; i2++) { + if (!mainEventArgs[i2].startsWith(eventArgs[i2].split(' ')[0])) { + errors.push(`invalid argument #${i2 + 1} for ${name} at ${mainPath}:${connector.mainEventsLines[i1]}`) + } + } + } else { + errors.push(`event ${name} missing at ${eventsPath}`) + } + } + if (connector.mainEvents.length < connector.events.length) { + const deprecatedEvents = connector.events.filter(e => { + let used = false + for (let index = 0; index < eventNames.length; index++) { + if (e.split('(')[0].split(' ')[1] === eventNames[index]) used = true + } + return !used + }) + warnings.push(`${deprecatedEvents.map(e => e.split('(')[0].split(' ')[1]).join(', ')} event(s) not used at ${connector.path}/main.sol`) + } + } else { + warnings.push(`missing events file for ${connector.path}/main.sol`) + } + return { eventsErrors: errors, eventsWarnings: warnings } + } catch (error) { + return Promise.reject(error) + } +} + +const getCommments = async (strs) => { + try { + const comments = [] + let type + for (let index = strs.length - 1; index >= 0; index--) { + const str = strs[index] + if (!type) { + if (str.trim().startsWith('//')) { + type = 'single' + } else if (str.trim().startsWith('*/')) { + type = 'multiple' + } + } + if (type === 'single' && str.trim().startsWith('//')) { + comments.push(str.replace(/[/]/gi, '').trim()) + } else if (type === 'multiple' && !str.trim().startsWith('/**') && !str.trim().startsWith('*/')) { + comments.push(str.replace(/[*]/gi, '').trim()) + } else if (type === 'single' && !str.trim().startsWith('//')) { + break + } else if (type === 'multiple' && str.trim().startsWith('/**')) { + break + } + } + return comments + } catch (error) { + return Promise.reject(error) + } +} + +const parseCode = async (connector) => { + try { + const strs = connector.code.split('\n') + const events = [] + const eventsFirstLines = [] + let func = [] + let funcs = [] + let event = [] + let mainEvents = [] + let firstLine + let mainEventsLines = [] + for (let index = 0; index < strs.length; index++) { + const str = strs[index] + if (str.includes('function') && !str.trim().startsWith('//')) { + func = [str] + firstLine = index + 1 + } else if (func.length && !str.trim().startsWith('//')) { + func.push(str) + } + if (func.length && str.startsWith(`${func[0].split('function')[0]}}`)) { + funcs.push({ + raw: func.map(str => str.trim()).join(' '), + comments: await getCommments(strs.slice(0, firstLine)), + firstLine + }) + func = [] + } + } + funcs = funcs + .filter(({ raw }) => { + if ((raw.includes('external') || raw.includes('public')) && + raw.includes('returns')) { + const returns = raw.split('returns')[1].split('(')[1].split(')')[0] + return returns.includes('string') && returns.includes('bytes') + } + return false + }) + .map(f => { + const args = f.raw.split('(')[1].split(')')[0].split(',') + .map(arg => arg.trim()) + .filter(arg => arg !== '') + const name = f.raw.split('(')[0].split('function')[1].trim() + return { + ...f, + args, + name + } + }) + const eventsPath = `${connector.path}/events.sol` + if (fs.existsSync(eventsPath)) { + mainEvents = funcs + .map(({ raw }) => raw.split('_eventName')[2].trim().split('"')[1]) + .filter(raw => !!raw) + mainEventsLines = mainEvents.map(me => strs.findIndex(str => str.includes(me)) + 1) + const eventsCode = fs.readFileSync(eventsPath, { encoding: 'utf8' }) + const eventsStrs = eventsCode.split('\n') + for (let index = 0; index < eventsStrs.length; index++) { + const str = eventsStrs[index] + if (str.includes('event')) { + event = [str] + firstLine = index + 1 + } else if (event.length && !str.trim().startsWith('//')) { + event.push(str) + } + if (event.length && str.includes(')')) { + events.push(event.map(str => str.trim()).join(' ')) + eventsFirstLines.push(firstLine) + event = [] + } + } + } + return { + ...connector, + events, + eventsFirstLines, + mainEvents, + mainEventsLines, + funcs + } + } catch (error) { + return Promise.reject(error) + } +} + +const checkComments = async (connector) => { + try { + const errors = [] + for (let i1 = 0; i1 < connector.funcs.length; i1++) { + const func = connector.funcs[i1] + for (let i2 = 0; i2 < func.args.length; i2++) { + const argName = func.args[i2].split(' ').pop() + if (!func.comments.some( + comment => comment.startsWith('@param') && comment.split(' ')[1] === argName + )) { + errors.push(`argument ${argName} has no @param for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`) + } + } + const reqs = ['@dev', '@notice'] + for (let i3 = 0; i3 < reqs.length; i3++) { + if (!func.comments.some(comment => comment.startsWith(reqs[i3]))) { + errors.push(`no ${reqs[i3]} for function ${func.name} at ${connector.path}/main.sol:${func.firstLine}`) + } + } + } + return errors + } catch (error) { + return Promise.reject(error) + } +} + +const checkName = async (connector) => { + try { + const strs = connector.code.split('\n') + let haveName = false + for (let index = strs.length - 1; index > 0; index--) { + const str = strs[index] + if (str.includes('string') && str.includes('public') && str.includes('name = ')) { + haveName = true + } + } + return haveName ? [] : [`name variable missing in ${connector.path}/main.sol`] + } catch (error) { + return Promise.reject(error) + } +} + +async function checkMain () { + const errors = [] + try { + const warnings = [] + const connectors = await getConnectorsList() + for (let index = 0; index < connectors.length; index++) { + const { forbiddenErrors, code } = await checkForbidden(connectors[index].path) + connectors[index].code = code + connectors[index] = await parseCode(connectors[index]) + const { eventsErrors, eventsWarnings } = await checkEvents(connectors[index]) + const commentsErrors = await checkComments(connectors[index]) + const nameErrors = await checkName(connectors[index]) + + errors.push(...forbiddenErrors) + errors.push(...eventsErrors) + errors.push(...commentsErrors) + errors.push(...nameErrors) + warnings.push(...eventsWarnings) + } + if (errors.length) { + console.log('\x1b[31m%s\x1b[0m', `Total errors: ${errors.length}`) + console.log('\x1b[31m%s\x1b[0m', errors.join('\n')) + } else { + console.log('\x1b[32m%s\x1b[0m', 'No Errors Found') + } + if (warnings.length) { + console.log('\x1b[33m%s\x1b[0m', `Total warnings: ${warnings.length}`) + console.log('\x1b[33m%s\x1b[0m', warnings.join('\n')) + } else { + console.log('\x1b[32m%s\x1b[0m', 'No Warnings Found') + } + } catch (error) { + console.error('check execution error:', error) + } + if (errors.length) throw new Error(errors.join('\n')) +} +module.exports = checkMain; \ No newline at end of file diff --git a/status-checks/checks.js b/status-checks/checks.js new file mode 100644 index 00000000..9a52768b --- /dev/null +++ b/status-checks/checks.js @@ -0,0 +1,6 @@ +const checkMain = require('./check'); + +module.exports = [{ + name: 'Solidity check', + callback: checkMain, +}]; diff --git a/status-checks/huskyCheck.js b/status-checks/huskyCheck.js new file mode 100644 index 00000000..e1e5f4fc --- /dev/null +++ b/status-checks/huskyCheck.js @@ -0,0 +1,9 @@ +const checkMain = require('./check'); + +(async function runHusky () { + try { + await checkMain() + } catch (error) { + process.exit(1) + } +})() \ No newline at end of file diff --git a/status-checks/index.js b/status-checks/index.js new file mode 100644 index 00000000..4dbd9aea --- /dev/null +++ b/status-checks/index.js @@ -0,0 +1,54 @@ +const cp = require('child_process'); +const fetch = require('node-fetch'); + +const checks = require('./checks'); + +const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); + +function getCurrentCommitSha() { + return cp + .execSync(`git rev-parse HEAD`) + .toString() + .trim(); +} +// The SHA provied by GITHUB_SHA is the merge (PR) commit. +// We need to get the current commit sha ourself. +const sha = getCurrentCommitSha(); + +async function setStatus(context, state, description) { + return fetch(`https://api.github.com/repos/${owner}/${repo}/statuses/${sha}`, { + method: 'POST', + body: JSON.stringify({ + state, + description, + context, + }), + headers: { + Authorization: `Bearer ${process.env.GITHUB_TOKEN}`, + 'Content-Type': 'application/json', + }, + }); +} + +(async () => { + console.log(`Starting status checks for commit ${sha}`); + + // Run in parallel + await Promise.all( + checks.map(async check => { + const { name, callback } = check; + + await setStatus(name, 'pending', 'Running check..'); + + try { + const response = await callback(); + await setStatus(name, 'success', response); + } catch (err) { + const message = err ? err.message : 'Something went wrong'; + await setStatus(name, 'failure', message); + } + }), + ); + + console.log('Finished status checks'); +})();