//Code written for SamPriestley.com
pragma solidity ^0.7.0;
pragma experimental ABIEncoderV2;

//The code written by money-legos to make flash loans easier
import "../Contract/DydxFlashLoanBase.sol";
import "../Contract/Interfaces/ICallee.sol";

//Interfaces to call outside contracts
interface IErc20 {
    function approve(address, uint256) external returns (bool);

    function transfer(address, uint256) external returns (bool);
    function balanceOf(address) external view returns (uint);
}

interface ICErc20 is IErc20{
    function mint(uint mintAmount) external returns (uint);
    function borrow(uint borrowAmount) external returns (uint);
    function underlying() external returns (address);
}
interface IComptroller{ 
    function enterMarkets(address[] calldata cTokens) external returns (uint[] memory);
}

contract CompFarmer is DydxFlashloanBase, ICallee {
    address public immutable contractOwner;

    //Addresses of outside currencies
    address private constant SOLO = 0x1E0447b19BB6EcFdAe1e4AE1694b0C3659614e4e;
    address private constant DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
    address private constant CDAI = 0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643;
    address private constant COMPTROLLER = 0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B;

    constructor() { 
        contractOwner = msg.sender; 
    }

    //Withdraw needed or else your Erc20 tokens stuck here forever
    function Withdraw(
        address _currency, uint256 _amount
    ) public  {

        require(msg.sender == contractOwner, "OWNER ONLY");

        IErc20 currency = IErc20(_currency);
        currency.approve(contractOwner, _amount);
        currency.transfer(contractOwner, _amount);       
    }

    //The function we call
    function Invest() public {
        //Only the creator of the contract can interact
        require(msg.sender == contractOwner, "OWNER ONLY");

        //For outside calls to DAI smart contract
        IErc20 dai = IErc20(DAI); 

        //Check we have some dai to invest
        uint256 daiBalance = dai.balanceOf(address(this));
        require(daiBalance >0, "NO DAI TO INVEST");

        //We do not use decimals. So *2900/1000 is same as *2.9
        //In production we would use SafeMath library
        uint256 borrowAmount = daiBalance * 2900/1000;

        ISoloMargin solo = ISoloMargin(SOLO);

        // Get marketId from token address
        uint256 marketId = _getMarketIdFromTokenAddress(SOLO, DAI);

        // Calculate repay amount (borrowAmount + (2 wei))
        // Approve transfer from
        uint256 repayAmount = _getRepaymentAmountInternal(borrowAmount);
        dai.approve(SOLO, repayAmount);

        // 1. Withdraw $
        // 2. Call callFunction(...)
        // 3. Deposit back $
        Actions.ActionArgs[] memory operations = new Actions.ActionArgs[](3);

        //We encode data we want our callFunction to receive
        bytes memory data = abi.encode(repayAmount);

        //Borrow money
        operations[0] = _getWithdrawAction(marketId, borrowAmount);
        //Do our Compound investment
        operations[1] = _getCallAction(data);
        //Repay money
        operations[2] = _getDepositAction(marketId, repayAmount);

        Account.Info[] memory accountInfos = new Account.Info[](1);
        accountInfos[0] = _getAccountInfo();

        solo.operate(accountInfos, operations);

    }


    //This contract is called by the flash loan lender. It is our main logic
    function callFunction(
        address sender,
        Account.Info memory account,
        bytes memory data
    ) public override {
        //We check origin of total transaction is owner. Cannot just check sender
        require(tx.origin == contractOwner, "OWNER ONLY");

         (uint256 repayAmount) = abi.decode(data,(uint256));

        ICErc20 cdai = ICErc20(CDAI);
        IErc20 dai = IErc20(DAI); 
        IComptroller comptroller = IComptroller(COMPTROLLER);

        uint256 lendAmount = dai.balanceOf(address(this));
        
        //Approve using DAI as collateral on Compound
        address[] memory markets = new address[](1);
        markets[0] = CDAI;
        comptroller.enterMarkets(markets);

        //Approve transfer of DAI to CDAI
        dai.approve(CDAI, lendAmount);
        
        //Lend out DAI
        cdai.mint(lendAmount);

        //Borrow enough DAI to repay loan
        cdai.borrow(repayAmount);

        require(dai.balanceOf(address(this)) > repayAmount, "CANT REPAY LOAN");
    }

}