ETH Price: $3,867.14 (-0.55%)

Transaction Decoder

Block:
12054386 at Mar-17-2021 05:56:13 AM +UTC
Transaction Fee:
0.0251667 ETH $97.32
Gas Used:
186,420 Gas / 135 Gwei

Emitted Events:

148 VaultProxy.0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef( 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x0000000000000000000000000000000000000000000000000000000000000000, 0x000000000000000000000000798afe1a6131eba2c6c33e70842e3584fb05dcb0, 000000000000000000000000000000000000000000000000000000003c3b67df )
149 TetherToken.Transfer( from=[Sender] 0x798afe1a6131eba2c6c33e70842e3584fb05dcb0, to=[Receiver] VaultProxy, value=1010526273 )
150 VaultProxy.0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c( 0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c, 0x000000000000000000000000798afe1a6131eba2c6c33e70842e3584fb05dcb0, 000000000000000000000000000000000000000000000000000000003c3b6841 )

Account State Difference:

  Address   Before After State Difference Code
(Nanopool)
3,635.339423571302786564 Eth3,635.364590271302786564 Eth0.0251667
0x798AfE1a...4Fb05DCb0
0.206658955 Eth
Nonce: 26
0.181492255 Eth
Nonce: 27
0.0251667
0xdAC17F95...13D831ec7
0xfd9EeC2B...7934B51A3

Execution Trace

VaultProxy.b6b55f25( )
  • Vault.deposit( amount=1010526273 )
    • IdleStrategyUSDTMainnet.STATICCALL( )
    • IdleStrategyUSDTMainnet.STATICCALL( )
      • IdleTokenHelper.getRedeemPrice( idleYieldToken=0xF34842d05A1c888Ca02769A633DF37177415C2f8 ) => ( redeemPrice=1040760 )
        • AdminUpgradeabilityProxy.0df94ef2( )
          • IdleTokenGovernance.userAvgPrices( 0x7c91e67910F8DBb947F95f41Ec76dAAc1C2Dc08e ) => ( 1040571 )
          • AdminUpgradeabilityProxy.STATICCALL( )
            • IdleTokenGovernance.DELEGATECALL( )
              • TetherToken.balanceOf( who=0xF34842d05A1c888Ca02769A633DF37177415C2f8 ) => ( 92412563378 )
              • CErc20Delegator.balanceOf( owner=0xF34842d05A1c888Ca02769A633DF37177415C2f8 ) => ( 1966 )
              • IdleCompoundV2.STATICCALL( )
              • AToken.balanceOf( _user=0xF34842d05A1c888Ca02769A633DF37177415C2f8 ) => ( 9752940867595 )
              • IdleAave.STATICCALL( )
              • AdminUpgradeabilityProxy.STATICCALL( )
                • IdleTokenGovernance.DELEGATECALL( )
                • AdminUpgradeabilityProxy.70a08231( )
                  • IdleTokenGovernance.balanceOf( account=0x7c91e67910F8DBb947F95f41Ec76dAAc1C2Dc08e ) => ( 2873849150275183502936974 )
                  • TetherToken.balanceOf( who=0x7c91e67910F8DBb947F95f41Ec76dAAc1C2Dc08e ) => ( 0 )
                  • TetherToken.balanceOf( who=0xfd9EeC2Bb8Ed6051Ba557100Ad0Aba87934B51A3 ) => ( 1957266155401 )
                  • TetherToken.transferFrom( _from=0x798AfE1a6131EBa2c6C33E70842E3584Fb05DCb0, _to=0xfd9EeC2Bb8Ed6051Ba557100Ad0Aba87934B51A3, _value=1010526273 )
                    deposit[Vault (ln:377)]
                    File 1 of 11: VaultProxy
                    pragma solidity 0.5.16;
                    import "./interfaces/IUpgradeSource.sol";
                    import "@openzeppelin/upgrades/contracts/upgradeability/BaseUpgradeabilityProxy.sol";
                    contract VaultProxy is BaseUpgradeabilityProxy {
                        constructor(address _implementation) public {
                            _setImplementation(_implementation);
                        }
                        /**
                         * The main logic. If the timer has elapsed and there is a schedule upgrade,
                         * the governance can upgrade the vault
                         */
                        function upgrade() external {
                            (bool should, address newImplementation) =
                                IUpgradeSource(address(this)).shouldUpgrade();
                            require(should, "Upgrade not scheduled");
                            _upgradeTo(newImplementation);
                            // the finalization needs to be executed on itself to update the storage of this proxy
                            // it also needs to be invoked by the governance, not by address(this), so delegatecall is needed
                            (bool success, bytes memory result) =
                                address(this).delegatecall(
                                    abi.encodeWithSignature("finalizeUpgrade()")
                                );
                            require(success, "Issue when finalizing the upgrade");
                        }
                        function implementation() external view returns (address) {
                            return _implementation();
                        }
                    }
                    pragma solidity 0.5.16;
                    interface IUpgradeSource {
                        function shouldUpgrade() external view returns (bool, address);
                        function finalizeUpgrade() external;
                    }
                    pragma solidity ^0.5.0;
                    import './Proxy.sol';
                    import '../utils/Address.sol';
                    /**
                     * @title BaseUpgradeabilityProxy
                     * @dev This contract implements a proxy that allows to change the
                     * implementation address to which it will delegate.
                     * Such a change is called an implementation upgrade.
                     */
                    contract BaseUpgradeabilityProxy is Proxy {
                      /**
                       * @dev Emitted when the implementation is upgraded.
                       * @param implementation Address of the new implementation.
                       */
                      event Upgraded(address indexed implementation);
                      /**
                       * @dev Storage slot with the address of the current implementation.
                       * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                       * validated in the constructor.
                       */
                      bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                      /**
                       * @dev Returns the current implementation.
                       * @return Address of the current implementation
                       */
                      function _implementation() internal view returns (address impl) {
                        bytes32 slot = IMPLEMENTATION_SLOT;
                        assembly {
                          impl := sload(slot)
                        }
                      }
                      /**
                       * @dev Upgrades the proxy to a new implementation.
                       * @param newImplementation Address of the new implementation.
                       */
                      function _upgradeTo(address newImplementation) internal {
                        _setImplementation(newImplementation);
                        emit Upgraded(newImplementation);
                      }
                      /**
                       * @dev Sets the implementation address of the proxy.
                       * @param newImplementation Address of the new implementation.
                       */
                      function _setImplementation(address newImplementation) internal {
                        require(OpenZeppelinUpgradesAddress.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                        bytes32 slot = IMPLEMENTATION_SLOT;
                        assembly {
                          sstore(slot, newImplementation)
                        }
                      }
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @title Proxy
                     * @dev Implements delegation of calls to other contracts, with proper
                     * forwarding of return values and bubbling of failures.
                     * It defines a fallback function that delegates all calls to the address
                     * returned by the abstract _implementation() internal function.
                     */
                    contract Proxy {
                      /**
                       * @dev Fallback function.
                       * Implemented entirely in `_fallback`.
                       */
                      function () payable external {
                        _fallback();
                      }
                      /**
                       * @return The Address of the implementation.
                       */
                      function _implementation() internal view returns (address);
                      /**
                       * @dev Delegates execution to an implementation contract.
                       * This is a low level function that doesn't return to its internal call site.
                       * It will return to the external caller whatever the implementation returns.
                       * @param implementation Address to delegate.
                       */
                      function _delegate(address implementation) internal {
                        assembly {
                          // Copy msg.data. We take full control of memory in this inline assembly
                          // block because it will not return to Solidity code. We overwrite the
                          // Solidity scratch pad at memory position 0.
                          calldatacopy(0, 0, calldatasize)
                          // Call the implementation.
                          // out and outsize are 0 because we don't know the size yet.
                          let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
                          // Copy the returned data.
                          returndatacopy(0, 0, returndatasize)
                          switch result
                          // delegatecall returns 0 on error.
                          case 0 { revert(0, returndatasize) }
                          default { return(0, returndatasize) }
                        }
                      }
                      /**
                       * @dev Function that is run as the first thing in the fallback function.
                       * Can be redefined in derived contracts to add functionality.
                       * Redefinitions must call super._willFallback().
                       */
                      function _willFallback() internal {
                      }
                      /**
                       * @dev fallback implementation.
                       * Extracted to enable manual triggering.
                       */
                      function _fallback() internal {
                        _willFallback();
                        _delegate(_implementation());
                      }
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * Utility library of inline functions on addresses
                     *
                     * Source https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/v2.1.3/contracts/utils/Address.sol
                     * This contract is copied here and renamed from the original to avoid clashes in the compiled artifacts
                     * when the user imports a zos-lib contract (that transitively causes this contract to be compiled and added to the
                     * build/artifacts folder) as well as the vanilla Address implementation from an openzeppelin version.
                     */
                    library OpenZeppelinUpgradesAddress {
                        /**
                         * Returns whether the target address is a contract
                         * @dev This function will return false if invoked during the constructor of a contract,
                         * as the code is not actually created until after the constructor finishes.
                         * @param account address of the account to check
                         * @return whether the target address is a contract
                         */
                        function isContract(address account) internal view returns (bool) {
                            uint256 size;
                            // XXX Currently there is no better way to check if there is a contract in an address
                            // than to check the size of the code at that address.
                            // See https://ethereum.stackexchange.com/a/14016/36603
                            // for more details about how this works.
                            // TODO Check this again before the Serenity release, because all addresses will be
                            // contracts then.
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                    }
                    

                    File 2 of 11: TetherToken
                    pragma solidity ^0.4.17;
                    
                    /**
                     * @title SafeMath
                     * @dev Math operations with safety checks that throw on error
                     */
                    library SafeMath {
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            if (a == 0) {
                                return 0;
                            }
                            uint256 c = a * b;
                            assert(c / a == b);
                            return c;
                        }
                    
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            // assert(b > 0); // Solidity automatically throws when dividing by 0
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                            return c;
                        }
                    
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            assert(b <= a);
                            return a - b;
                        }
                    
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            assert(c >= a);
                            return c;
                        }
                    }
                    
                    /**
                     * @title Ownable
                     * @dev The Ownable contract has an owner address, and provides basic authorization control
                     * functions, this simplifies the implementation of "user permissions".
                     */
                    contract Ownable {
                        address public owner;
                    
                        /**
                          * @dev The Ownable constructor sets the original `owner` of the contract to the sender
                          * account.
                          */
                        function Ownable() public {
                            owner = msg.sender;
                        }
                    
                        /**
                          * @dev Throws if called by any account other than the owner.
                          */
                        modifier onlyOwner() {
                            require(msg.sender == owner);
                            _;
                        }
                    
                        /**
                        * @dev Allows the current owner to transfer control of the contract to a newOwner.
                        * @param newOwner The address to transfer ownership to.
                        */
                        function transferOwnership(address newOwner) public onlyOwner {
                            if (newOwner != address(0)) {
                                owner = newOwner;
                            }
                        }
                    
                    }
                    
                    /**
                     * @title ERC20Basic
                     * @dev Simpler version of ERC20 interface
                     * @dev see https://github.com/ethereum/EIPs/issues/20
                     */
                    contract ERC20Basic {
                        uint public _totalSupply;
                        function totalSupply() public constant returns (uint);
                        function balanceOf(address who) public constant returns (uint);
                        function transfer(address to, uint value) public;
                        event Transfer(address indexed from, address indexed to, uint value);
                    }
                    
                    /**
                     * @title ERC20 interface
                     * @dev see https://github.com/ethereum/EIPs/issues/20
                     */
                    contract ERC20 is ERC20Basic {
                        function allowance(address owner, address spender) public constant returns (uint);
                        function transferFrom(address from, address to, uint value) public;
                        function approve(address spender, uint value) public;
                        event Approval(address indexed owner, address indexed spender, uint value);
                    }
                    
                    /**
                     * @title Basic token
                     * @dev Basic version of StandardToken, with no allowances.
                     */
                    contract BasicToken is Ownable, ERC20Basic {
                        using SafeMath for uint;
                    
                        mapping(address => uint) public balances;
                    
                        // additional variables for use if transaction fees ever became necessary
                        uint public basisPointsRate = 0;
                        uint public maximumFee = 0;
                    
                        /**
                        * @dev Fix for the ERC20 short address attack.
                        */
                        modifier onlyPayloadSize(uint size) {
                            require(!(msg.data.length < size + 4));
                            _;
                        }
                    
                        /**
                        * @dev transfer token for a specified address
                        * @param _to The address to transfer to.
                        * @param _value The amount to be transferred.
                        */
                        function transfer(address _to, uint _value) public onlyPayloadSize(2 * 32) {
                            uint fee = (_value.mul(basisPointsRate)).div(10000);
                            if (fee > maximumFee) {
                                fee = maximumFee;
                            }
                            uint sendAmount = _value.sub(fee);
                            balances[msg.sender] = balances[msg.sender].sub(_value);
                            balances[_to] = balances[_to].add(sendAmount);
                            if (fee > 0) {
                                balances[owner] = balances[owner].add(fee);
                                Transfer(msg.sender, owner, fee);
                            }
                            Transfer(msg.sender, _to, sendAmount);
                        }
                    
                        /**
                        * @dev Gets the balance of the specified address.
                        * @param _owner The address to query the the balance of.
                        * @return An uint representing the amount owned by the passed address.
                        */
                        function balanceOf(address _owner) public constant returns (uint balance) {
                            return balances[_owner];
                        }
                    
                    }
                    
                    /**
                     * @title Standard ERC20 token
                     *
                     * @dev Implementation of the basic standard token.
                     * @dev https://github.com/ethereum/EIPs/issues/20
                     * @dev Based oncode by FirstBlood: https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
                     */
                    contract StandardToken is BasicToken, ERC20 {
                    
                        mapping (address => mapping (address => uint)) public allowed;
                    
                        uint public constant MAX_UINT = 2**256 - 1;
                    
                        /**
                        * @dev Transfer tokens from one address to another
                        * @param _from address The address which you want to send tokens from
                        * @param _to address The address which you want to transfer to
                        * @param _value uint the amount of tokens to be transferred
                        */
                        function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
                            var _allowance = allowed[_from][msg.sender];
                    
                            // Check is not needed because sub(_allowance, _value) will already throw if this condition is not met
                            // if (_value > _allowance) throw;
                    
                            uint fee = (_value.mul(basisPointsRate)).div(10000);
                            if (fee > maximumFee) {
                                fee = maximumFee;
                            }
                            if (_allowance < MAX_UINT) {
                                allowed[_from][msg.sender] = _allowance.sub(_value);
                            }
                            uint sendAmount = _value.sub(fee);
                            balances[_from] = balances[_from].sub(_value);
                            balances[_to] = balances[_to].add(sendAmount);
                            if (fee > 0) {
                                balances[owner] = balances[owner].add(fee);
                                Transfer(_from, owner, fee);
                            }
                            Transfer(_from, _to, sendAmount);
                        }
                    
                        /**
                        * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
                        * @param _spender The address which will spend the funds.
                        * @param _value The amount of tokens to be spent.
                        */
                        function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                    
                            // To change the approve amount you first have to reduce the addresses`
                            //  allowance to zero by calling `approve(_spender, 0)` if it is not
                            //  already 0 to mitigate the race condition described here:
                            //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                            require(!((_value != 0) && (allowed[msg.sender][_spender] != 0)));
                    
                            allowed[msg.sender][_spender] = _value;
                            Approval(msg.sender, _spender, _value);
                        }
                    
                        /**
                        * @dev Function to check the amount of tokens than an owner allowed to a spender.
                        * @param _owner address The address which owns the funds.
                        * @param _spender address The address which will spend the funds.
                        * @return A uint specifying the amount of tokens still available for the spender.
                        */
                        function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                            return allowed[_owner][_spender];
                        }
                    
                    }
                    
                    
                    /**
                     * @title Pausable
                     * @dev Base contract which allows children to implement an emergency stop mechanism.
                     */
                    contract Pausable is Ownable {
                      event Pause();
                      event Unpause();
                    
                      bool public paused = false;
                    
                    
                      /**
                       * @dev Modifier to make a function callable only when the contract is not paused.
                       */
                      modifier whenNotPaused() {
                        require(!paused);
                        _;
                      }
                    
                      /**
                       * @dev Modifier to make a function callable only when the contract is paused.
                       */
                      modifier whenPaused() {
                        require(paused);
                        _;
                      }
                    
                      /**
                       * @dev called by the owner to pause, triggers stopped state
                       */
                      function pause() onlyOwner whenNotPaused public {
                        paused = true;
                        Pause();
                      }
                    
                      /**
                       * @dev called by the owner to unpause, returns to normal state
                       */
                      function unpause() onlyOwner whenPaused public {
                        paused = false;
                        Unpause();
                      }
                    }
                    
                    contract BlackList is Ownable, BasicToken {
                    
                        /////// Getters to allow the same blacklist to be used also by other contracts (including upgraded Tether) ///////
                        function getBlackListStatus(address _maker) external constant returns (bool) {
                            return isBlackListed[_maker];
                        }
                    
                        function getOwner() external constant returns (address) {
                            return owner;
                        }
                    
                        mapping (address => bool) public isBlackListed;
                        
                        function addBlackList (address _evilUser) public onlyOwner {
                            isBlackListed[_evilUser] = true;
                            AddedBlackList(_evilUser);
                        }
                    
                        function removeBlackList (address _clearedUser) public onlyOwner {
                            isBlackListed[_clearedUser] = false;
                            RemovedBlackList(_clearedUser);
                        }
                    
                        function destroyBlackFunds (address _blackListedUser) public onlyOwner {
                            require(isBlackListed[_blackListedUser]);
                            uint dirtyFunds = balanceOf(_blackListedUser);
                            balances[_blackListedUser] = 0;
                            _totalSupply -= dirtyFunds;
                            DestroyedBlackFunds(_blackListedUser, dirtyFunds);
                        }
                    
                        event DestroyedBlackFunds(address _blackListedUser, uint _balance);
                    
                        event AddedBlackList(address _user);
                    
                        event RemovedBlackList(address _user);
                    
                    }
                    
                    contract UpgradedStandardToken is StandardToken{
                        // those methods are called by the legacy contract
                        // and they must ensure msg.sender to be the contract address
                        function transferByLegacy(address from, address to, uint value) public;
                        function transferFromByLegacy(address sender, address from, address spender, uint value) public;
                        function approveByLegacy(address from, address spender, uint value) public;
                    }
                    
                    contract TetherToken is Pausable, StandardToken, BlackList {
                    
                        string public name;
                        string public symbol;
                        uint public decimals;
                        address public upgradedAddress;
                        bool public deprecated;
                    
                        //  The contract can be initialized with a number of tokens
                        //  All the tokens are deposited to the owner address
                        //
                        // @param _balance Initial supply of the contract
                        // @param _name Token Name
                        // @param _symbol Token symbol
                        // @param _decimals Token decimals
                        function TetherToken(uint _initialSupply, string _name, string _symbol, uint _decimals) public {
                            _totalSupply = _initialSupply;
                            name = _name;
                            symbol = _symbol;
                            decimals = _decimals;
                            balances[owner] = _initialSupply;
                            deprecated = false;
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function transfer(address _to, uint _value) public whenNotPaused {
                            require(!isBlackListed[msg.sender]);
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).transferByLegacy(msg.sender, _to, _value);
                            } else {
                                return super.transfer(_to, _value);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function transferFrom(address _from, address _to, uint _value) public whenNotPaused {
                            require(!isBlackListed[_from]);
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).transferFromByLegacy(msg.sender, _from, _to, _value);
                            } else {
                                return super.transferFrom(_from, _to, _value);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function balanceOf(address who) public constant returns (uint) {
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).balanceOf(who);
                            } else {
                                return super.balanceOf(who);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function approve(address _spender, uint _value) public onlyPayloadSize(2 * 32) {
                            if (deprecated) {
                                return UpgradedStandardToken(upgradedAddress).approveByLegacy(msg.sender, _spender, _value);
                            } else {
                                return super.approve(_spender, _value);
                            }
                        }
                    
                        // Forward ERC20 methods to upgraded contract if this one is deprecated
                        function allowance(address _owner, address _spender) public constant returns (uint remaining) {
                            if (deprecated) {
                                return StandardToken(upgradedAddress).allowance(_owner, _spender);
                            } else {
                                return super.allowance(_owner, _spender);
                            }
                        }
                    
                        // deprecate current contract in favour of a new one
                        function deprecate(address _upgradedAddress) public onlyOwner {
                            deprecated = true;
                            upgradedAddress = _upgradedAddress;
                            Deprecate(_upgradedAddress);
                        }
                    
                        // deprecate current contract if favour of a new one
                        function totalSupply() public constant returns (uint) {
                            if (deprecated) {
                                return StandardToken(upgradedAddress).totalSupply();
                            } else {
                                return _totalSupply;
                            }
                        }
                    
                        // Issue a new amount of tokens
                        // these tokens are deposited into the owner address
                        //
                        // @param _amount Number of tokens to be issued
                        function issue(uint amount) public onlyOwner {
                            require(_totalSupply + amount > _totalSupply);
                            require(balances[owner] + amount > balances[owner]);
                    
                            balances[owner] += amount;
                            _totalSupply += amount;
                            Issue(amount);
                        }
                    
                        // Redeem tokens.
                        // These tokens are withdrawn from the owner address
                        // if the balance must be enough to cover the redeem
                        // or the call will fail.
                        // @param _amount Number of tokens to be issued
                        function redeem(uint amount) public onlyOwner {
                            require(_totalSupply >= amount);
                            require(balances[owner] >= amount);
                    
                            _totalSupply -= amount;
                            balances[owner] -= amount;
                            Redeem(amount);
                        }
                    
                        function setParams(uint newBasisPoints, uint newMaxFee) public onlyOwner {
                            // Ensure transparency by hardcoding limit beyond which fees can never be added
                            require(newBasisPoints < 20);
                            require(newMaxFee < 50);
                    
                            basisPointsRate = newBasisPoints;
                            maximumFee = newMaxFee.mul(10**decimals);
                    
                            Params(basisPointsRate, maximumFee);
                        }
                    
                        // Called when new token are issued
                        event Issue(uint amount);
                    
                        // Called when tokens are redeemed
                        event Redeem(uint amount);
                    
                        // Called when contract is deprecated
                        event Deprecate(address newAddress);
                    
                        // Called if contract ever adds fees
                        event Params(uint feeBasisPoints, uint maxFee);
                    }

                    File 3 of 11: Vault
                    pragma solidity 0.5.16;
                    import "./GovernableInit.sol";
                    // A clone of Governable supporting the Initializable interface and pattern
                    contract ControllableInit is GovernableInit {
                        constructor() public {}
                        function initialize(address _storage) public initializer {
                            GovernableInit.initialize(_storage);
                        }
                        modifier onlyController() {
                            require(
                                Storage(_storage()).isController(msg.sender),
                                "Not a controller"
                            );
                            _;
                        }
                        modifier onlyControllerOrGovernance() {
                            require(
                                (Storage(_storage()).isController(msg.sender) ||
                                    Storage(_storage()).isGovernance(msg.sender)),
                                "The caller must be controller or governance"
                            );
                            _;
                        }
                        function controller() public view returns (address) {
                            return Storage(_storage()).controller();
                        }
                    }
                    pragma solidity 0.5.16;
                    import "@openzeppelin/upgrades/contracts/Initializable.sol";
                    import "./Storage.sol";
                    // A clone of Governable supporting the Initializable interface and pattern
                    contract GovernableInit is Initializable {
                        bytes32 internal constant _STORAGE_SLOT =
                            0xa7ec62784904ff31cbcc32d09932a58e7f1e4476e1d041995b37c917990b16dc;
                        modifier onlyGovernance() {
                            require(Storage(_storage()).isGovernance(msg.sender), "Not governance");
                            _;
                        }
                        constructor() public {
                            assert(
                                _STORAGE_SLOT ==
                                    bytes32(
                                        uint256(keccak256("eip1967.governableInit.storage")) - 1
                                    )
                            );
                        }
                        function initialize(address _store) public initializer {
                            _setStorage(_store);
                        }
                        function _setStorage(address newStorage) private {
                            bytes32 slot = _STORAGE_SLOT;
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                sstore(slot, newStorage)
                            }
                        }
                        function setStorage(address _store) public onlyGovernance {
                            require(_store != address(0), "new storage shouldn't be empty");
                            _setStorage(_store);
                        }
                        function _storage() internal view returns (address str) {
                            bytes32 slot = _STORAGE_SLOT;
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                str := sload(slot)
                            }
                        }
                        function governance() public view returns (address) {
                            return Storage(_storage()).governance();
                        }
                    }
                    pragma solidity 0.5.16;
                    contract Storage {
                        address public governance;
                        address public controller;
                        constructor() public {
                            governance = msg.sender;
                        }
                        modifier onlyGovernance() {
                            require(isGovernance(msg.sender), "Not governance");
                            _;
                        }
                        function setGovernance(address _governance) public onlyGovernance {
                            require(_governance != address(0), "new governance shouldn't be empty");
                            governance = _governance;
                        }
                        function setController(address _controller) public onlyGovernance {
                            require(_controller != address(0), "new controller shouldn't be empty");
                            controller = _controller;
                        }
                        function isGovernance(address account) public view returns (bool) {
                            return account == governance;
                        }
                        function isController(address account) public view returns (bool) {
                            return account == controller;
                        }
                    }
                    pragma solidity 0.5.16;
                    import "@openzeppelin/contracts-ethereum-package/contracts/math/Math.sol";
                    import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol";
                    import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol";
                    import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol";
                    import "@openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol";
                    import "@openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol";
                    import "./interfaces/IStrategy.sol";
                    import "./interfaces/IController.sol";
                    import "./interfaces/IVault.sol";
                    import "./interfaces/IUpgradeSource.sol";
                    import "./ControllableInit.sol";
                    import "./VaultStorage.sol";
                    contract Vault is
                        ERC20,
                        ERC20Detailed,
                        IVault,
                        IUpgradeSource,
                        ControllableInit,
                        VaultStorage,
                        ReentrancyGuard
                    {
                        using SafeERC20 for IERC20;
                        using Address for address;
                        using SafeMath for uint256;
                        event Withdraw(address indexed beneficiary, uint256 amount);
                        event Deposit(address indexed beneficiary, uint256 amount);
                        event Invest(uint256 amount);
                        event StrategyAnnounced(address newStrategy, uint256 time);
                        event StrategyChanged(address newStrategy, address oldStrategy);
                        modifier whenStrategyDefined() {
                            require(address(strategy()) != address(0), "Strategy must be defined");
                            _;
                        }
                        // Only smart contracts will be affected by this modifier
                        modifier defense() {
                            require(
                                (msg.sender == tx.origin) || // If it is a normal user and not smart contract,
                                    // then the requirement will pass
                                    IController(controller()).whiteList(msg.sender),
                                "Access denied for caller"
                            );
                            _;
                        }
                        // the function is name differently to not cause inheritance clash in truffle and allows tests
                        function initializeVault(
                            address _storage,
                            address _underlying,
                            uint256 _toInvestNumerator,
                            uint256 _toInvestDenominator,
                            uint256 _maxDepositCap
                        ) public initializer {
                            require(
                                _toInvestNumerator <= _toInvestDenominator,
                                "cannot invest more than 100%"
                            );
                            require(_toInvestDenominator != 0, "cannot divide by 0");
                            ERC20Detailed.initialize(
                                string(
                                    abi.encodePacked("FORCE_", ERC20Detailed(_underlying).symbol())
                                ),
                                string(abi.encodePacked("x", ERC20Detailed(_underlying).symbol())),
                                ERC20Detailed(_underlying).decimals()
                            );
                            ControllableInit.initialize(_storage);
                            ReentrancyGuard.initialize();
                            uint256 underlyingUnit =
                                10**uint256(ERC20Detailed(address(_underlying)).decimals());
                            uint256 implementationDelay = 12 hours;
                            uint256 strategyChangeDelay = 12 hours;
                            VaultStorage.initialize(
                                _underlying,
                                _toInvestNumerator,
                                _toInvestDenominator,
                                underlyingUnit,
                                _maxDepositCap,
                                implementationDelay,
                                strategyChangeDelay
                            );
                        }
                        function strategy() public view returns (address) {
                            return _strategy();
                        }
                        function underlying() public view returns (address) {
                            return _underlying();
                        }
                        function underlyingUnit() public view returns (uint256) {
                            return _underlyingUnit();
                        }
                        function vaultFractionToInvestNumerator() public view returns (uint256) {
                            return _vaultFractionToInvestNumerator();
                        }
                        function vaultFractionToInvestDenominator() public view returns (uint256) {
                            return _vaultFractionToInvestDenominator();
                        }
                        function totalDeposits() public view returns (uint256) {
                            return _totalDeposits();
                        }
                        function maxDepositCap() public view returns (uint256) {
                            return _maxDepositCap();
                        }
                        function nextImplementation() public view returns (address) {
                            return _nextImplementation();
                        }
                        function nextImplementationTimestamp() public view returns (uint256) {
                            return _nextImplementationTimestamp();
                        }
                        function nextImplementationDelay() public view returns (uint256) {
                            return _nextImplementationDelay();
                        }
                        /**
                         * Chooses the best strategy and re-invests. If the strategy did not change, it just calls
                         * forceUnleashed on the current strategy. Call this through controller to claim galactic rewards.
                         */
                        function forceUnleashed()
                            external
                            whenStrategyDefined
                            onlyControllerOrGovernance
                        {
                            // ensure that new funds are invested too
                            invest();
                            IStrategy(strategy()).forceUnleashed();
                        }
                        /*
                         * Returns the cash balance across all users in this contract.
                         */
                        function underlyingBalanceInVault() public view returns (uint256) {
                            return IERC20(underlying()).balanceOf(address(this));
                        }
                        /* Returns the current underlying (e.g., DAI's) balance together with
                         * the invested amount (if DAI is invested elsewhere by the strategy).
                         */
                        function underlyingBalanceWithInvestment() public view returns (uint256) {
                            if (address(strategy()) == address(0)) {
                                // initial state, when not set
                                return underlyingBalanceInVault();
                            }
                            return
                                underlyingBalanceInVault().add(
                                    IStrategy(strategy()).investedUnderlyingBalance()
                                );
                        }
                        function getPricePerFullShare() public view returns (uint256) {
                            return
                                totalSupply() == 0
                                    ? underlyingUnit()
                                    : underlyingUnit().mul(underlyingBalanceWithInvestment()).div(
                                        totalSupply()
                                    );
                        }
                        /* get the user's share (in underlying)
                         */
                        function underlyingBalanceWithInvestmentForHolder(address holder)
                            external
                            view
                            returns (uint256)
                        {
                            if (totalSupply() == 0) {
                                return 0;
                            }
                            return
                                underlyingBalanceWithInvestment().mul(balanceOf(holder)).div(
                                    totalSupply()
                                );
                        }
                        function futureStrategy() public view returns (address) {
                            return _futureStrategy();
                        }
                        function strategyUpdateTime() public view returns (uint256) {
                            return _strategyUpdateTime();
                        }
                        function strategyTimeLock() public view returns (uint256) {
                            return _strategyTimeLock();
                        }
                        function canUpdateStrategy(address _strategy) public view returns (bool) {
                            return
                                strategy() == address(0) || // no strategy was set yet
                                (_strategy == futureStrategy() &&
                                    block.timestamp > strategyUpdateTime() &&
                                    strategyUpdateTime() > 0); // or the timelock has passed
                        }
                        /**
                         * Indicates that the strategy update will happen in the future
                         */
                        function announceStrategyUpdate(address _strategy)
                            public
                            onlyControllerOrGovernance
                        {
                            // records a new timestamp
                            uint256 when = block.timestamp.add(strategyTimeLock());
                            _setStrategyUpdateTime(when);
                            _setFutureStrategy(_strategy);
                            emit StrategyAnnounced(_strategy, when);
                        }
                        /**
                         * Finalizes (or cancels) the strategy update by resetting the data
                         */
                        function finalizeStrategyUpdate() public onlyControllerOrGovernance {
                            _setStrategyUpdateTime(0);
                            _setFutureStrategy(address(0));
                        }
                        function setStrategy(address _strategy) public onlyControllerOrGovernance {
                            require(
                                canUpdateStrategy(_strategy),
                                "The strategy exists and switch timelock did not elapse yet"
                            );
                            require(_strategy != address(0), "new _strategy cannot be empty");
                            require(
                                IStrategy(_strategy).underlying() == address(underlying()),
                                "Vault underlying must match Strategy underlying"
                            );
                            require(
                                IStrategy(_strategy).vault() == address(this),
                                "the strategy does not belong to this vault"
                            );
                            emit StrategyChanged(_strategy, strategy());
                            if (address(_strategy) != address(strategy())) {
                                if (address(strategy()) != address(0)) {
                                    // if the original strategy (no underscore) is defined
                                    IERC20(underlying()).safeApprove(address(strategy()), 0);
                                    IStrategy(strategy()).withdrawAllToVault();
                                }
                                _setStrategy(_strategy);
                                IERC20(underlying()).safeApprove(address(strategy()), 0);
                                IERC20(underlying()).safeApprove(address(strategy()), uint256(~0));
                            }
                            finalizeStrategyUpdate();
                        }
                        function setVaultFractionToInvest(uint256 numerator, uint256 denominator)
                            external
                            onlyGovernance
                        {
                            require(denominator > 0, "denominator must be greater than 0");
                            require(
                                numerator <= denominator,
                                "denominator must be greater than or equal to the numerator"
                            );
                            _setVaultFractionToInvestNumerator(numerator);
                            _setVaultFractionToInvestDenominator(denominator);
                        }
                        function setMaxDepositCap(uint256 value) external onlyGovernance {
                            return _setMaxDepositCap(value);
                        }
                        function rebalance() external onlyControllerOrGovernance {
                            withdrawAll();
                            invest();
                        }
                        function availableToInvestOut() public view returns (uint256) {
                            uint256 wantInvestInTotal =
                                underlyingBalanceWithInvestment()
                                    .mul(vaultFractionToInvestNumerator())
                                    .div(vaultFractionToInvestDenominator());
                            uint256 alreadyInvested =
                                IStrategy(strategy()).investedUnderlyingBalance();
                            if (alreadyInvested >= wantInvestInTotal) {
                                return 0;
                            } else {
                                uint256 remainingToInvest = wantInvestInTotal.sub(alreadyInvested);
                                return
                                    remainingToInvest <= underlyingBalanceInVault() // TODO: we think that the "else" branch of the ternary operation is not // going to get hit
                                        ? remainingToInvest
                                        : underlyingBalanceInVault();
                            }
                        }
                        function invest() internal whenStrategyDefined {
                            uint256 availableAmount = availableToInvestOut();
                            if (availableAmount > 0) {
                                IERC20(underlying()).safeTransfer(
                                    address(strategy()),
                                    availableAmount
                                );
                                emit Invest(availableAmount);
                            }
                        }
                        /*
                         * Allows for depositing the underlying asset in exchange for shares.
                         * Approval is assumed.
                         */
                        function deposit(uint256 amount) external defense {
                            _deposit(amount, msg.sender, msg.sender);
                        }
                        /*
                         * Allows for depositing the underlying asset in exchange for shares
                         * assigned to the holder.
                         * This facilitates depositing for someone else (using DepositHelper)
                         */
                        function depositFor(uint256 amount, address holder) public defense {
                            _deposit(amount, msg.sender, holder);
                        }
                        function withdrawAll()
                            public
                            onlyControllerOrGovernance
                            whenStrategyDefined
                        {
                            IStrategy(strategy()).withdrawAllToVault();
                        }
                        function withdraw(uint256 numberOfShares) external defense nonReentrant {
                            require(totalSupply() > 0, "Vault has no shares");
                            require(numberOfShares > 0, "numberOfShares must be greater than 0");
                            uint256 totalSupply = totalSupply();
                            _burn(msg.sender, numberOfShares);
                            uint256 underlyingAmountToWithdraw =
                                underlyingBalanceWithInvestment().mul(numberOfShares).div(
                                    totalSupply
                                );
                            uint256 originalDepositsToWithdraw =
                                _totalDeposits().mul(numberOfShares).div(totalSupply);
                            if (underlyingAmountToWithdraw > underlyingBalanceInVault()) {
                                // withdraw everything from the strategy to accurately check the share value
                                if (numberOfShares == totalSupply) {
                                    IStrategy(strategy()).withdrawAllToVault();
                                } else {
                                    uint256 missing =
                                        underlyingAmountToWithdraw.sub(underlyingBalanceInVault());
                                    IStrategy(strategy()).withdrawToVault(missing);
                                }
                                // recalculate to improve accuracy
                                underlyingAmountToWithdraw = Math.min(
                                    underlyingBalanceWithInvestment().mul(numberOfShares).div(
                                        totalSupply
                                    ),
                                    underlyingBalanceInVault()
                                );
                            }
                            IERC20(underlying()).safeTransfer(
                                msg.sender,
                                underlyingAmountToWithdraw
                            );
                            _setTotalDeposits(_totalDeposits().sub(originalDepositsToWithdraw));
                            // update the withdrawal amount for the holder
                            emit Withdraw(msg.sender, underlyingAmountToWithdraw);
                        }
                        function _deposit(
                            uint256 amount,
                            address sender,
                            address beneficiary
                        ) internal nonReentrant {
                            require(amount > 0, "Cannot deposit 0");
                            require(beneficiary != address(0), "holder must be defined");
                            require(
                                maxDepositCap() == 0 ||
                                    totalDeposits().add(amount) <= maxDepositCap(),
                                "Cannot deposit more than cap"
                            );
                            if (address(strategy()) != address(0)) {
                                require(IStrategy(strategy()).depositArbCheck(), "Too much arb");
                            }
                            uint256 toMint =
                                totalSupply() == 0
                                    ? amount
                                    : amount.mul(totalSupply()).div(
                                        underlyingBalanceWithInvestment()
                                    );
                            _mint(beneficiary, toMint);
                            IERC20(underlying()).safeTransferFrom(sender, address(this), amount);
                            _setTotalDeposits(_totalDeposits().add(amount));
                            // update the contribution amount for the beneficiary
                            emit Deposit(beneficiary, amount);
                        }
                        /**
                         * Schedules an upgrade for this vault's proxy.
                         */
                        function scheduleUpgrade(address impl) public onlyGovernance {
                            _setNextImplementation(impl);
                            _setNextImplementationTimestamp(
                                block.timestamp.add(nextImplementationDelay())
                            );
                        }
                        function shouldUpgrade() external view returns (bool, address) {
                            return (
                                nextImplementationTimestamp() != 0 &&
                                    block.timestamp > nextImplementationTimestamp() &&
                                    nextImplementation() != address(0),
                                nextImplementation()
                            );
                        }
                        function finalizeUpgrade() external onlyGovernance {
                            _setNextImplementation(address(0));
                            _setNextImplementationTimestamp(0);
                        }
                    }
                    pragma solidity 0.5.16;
                    import "@openzeppelin/upgrades/contracts/Initializable.sol";
                    contract VaultStorage is Initializable {
                        bytes32 internal constant _STRATEGY_SLOT =
                            0xf1a169aa0f736c2813818fdfbdc5755c31e0839c8f49831a16543496b28574ea;
                        bytes32 internal constant _UNDERLYING_SLOT =
                            0x1994607607e11d53306ef62e45e3bd85762c58d9bf38b5578bc4a258a26a7371;
                        bytes32 internal constant _UNDERLYING_UNIT_SLOT =
                            0xa66bc57d4b4eed7c7687876ca77997588987307cb13ecc23f5e52725192e5fff;
                        bytes32 internal constant _VAULT_FRACTION_TO_INVEST_NUMERATOR_SLOT =
                            0x39122c9adfb653455d0c05043bd52fcfbc2be864e832efd3abc72ce5a3d7ed5a;
                        bytes32 internal constant _VAULT_FRACTION_TO_INVEST_DENOMINATOR_SLOT =
                            0x469a3bad2fab7b936c45eecd1f5da52af89cead3e2ed7f732b6f3fc92ed32308;
                        bytes32 internal constant _NEXT_IMPLEMENTATION_SLOT =
                            0xb1acf527cd7cd1668b30e5a9a1c0d845714604de29ce560150922c9d8c0937df;
                        bytes32 internal constant _NEXT_IMPLEMENTATION_TIMESTAMP_SLOT =
                            0x3bc747f4b148b37be485de3223c90b4468252967d2ea7f9fcbd8b6e653f434c9;
                        bytes32 internal constant _NEXT_IMPLEMENTATION_DELAY_SLOT =
                            0x82ddc3be3f0c1a6870327f78f4979a0b37b21b16736ef5be6a7a7a35e530bcf0;
                        bytes32 internal constant _STRATEGY_TIME_LOCK_SLOT =
                            0x6d02338b2e4c913c0f7d380e2798409838a48a2c4d57d52742a808c82d713d8b;
                        bytes32 internal constant _FUTURE_STRATEGY_SLOT =
                            0xb441b53a4e42c2ca9182bc7ede99bedba7a5d9360d9dfbd31fa8ee2dc8590610;
                        bytes32 internal constant _STRATEGY_UPDATE_TIME_SLOT =
                            0x56e7c0e75875c6497f0de657009613a32558904b5c10771a825cc330feff7e72;
                        bytes32 internal constant _TOTAL_DEPOSITS =
                            0xaf765835ed5af0d235b6c686724ad31fa90e06b3daf1c074d6cc398b8fcef213;
                        bytes32 internal constant _MAX_DEPOSIT_CAP =
                            0x0df75d4bdb87be8e3e04e1dc08ec1c98ed6c4147138e5789f0bd448c5c8e1e28;
                        constructor() public {
                            assert(
                                _STRATEGY_SLOT ==
                                    bytes32(uint256(keccak256("eip1967.vaultStorage.strategy")) - 1)
                            );
                            assert(
                                _UNDERLYING_SLOT ==
                                    bytes32(
                                        uint256(keccak256("eip1967.vaultStorage.underlying")) - 1
                                    )
                            );
                            assert(
                                _UNDERLYING_UNIT_SLOT ==
                                    bytes32(
                                        uint256(keccak256("eip1967.vaultStorage.underlyingUnit")) -
                                            1
                                    )
                            );
                            assert(
                                _VAULT_FRACTION_TO_INVEST_NUMERATOR_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256(
                                                "eip1967.vaultStorage.vaultFractionToInvestNumerator"
                                            )
                                        ) - 1
                                    )
                            );
                            assert(
                                _VAULT_FRACTION_TO_INVEST_DENOMINATOR_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256(
                                                "eip1967.vaultStorage.vaultFractionToInvestDenominator"
                                            )
                                        ) - 1
                                    )
                            );
                            assert(
                                _NEXT_IMPLEMENTATION_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256("eip1967.vaultStorage.nextImplementation")
                                        ) - 1
                                    )
                            );
                            assert(
                                _NEXT_IMPLEMENTATION_TIMESTAMP_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256(
                                                "eip1967.vaultStorage.nextImplementationTimestamp"
                                            )
                                        ) - 1
                                    )
                            );
                            assert(
                                _NEXT_IMPLEMENTATION_DELAY_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256(
                                                "eip1967.vaultStorage.nextImplementationDelay"
                                            )
                                        ) - 1
                                    )
                            );
                            assert(
                                _STRATEGY_TIME_LOCK_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256("eip1967.vaultStorage.strategyTimeLock")
                                        ) - 1
                                    )
                            );
                            assert(
                                _FUTURE_STRATEGY_SLOT ==
                                    bytes32(
                                        uint256(keccak256("eip1967.vaultStorage.futureStrategy")) -
                                            1
                                    )
                            );
                            assert(
                                _STRATEGY_UPDATE_TIME_SLOT ==
                                    bytes32(
                                        uint256(
                                            keccak256("eip1967.vaultStorage.strategyUpdateTime")
                                        ) - 1
                                    )
                            );
                            assert(
                                _TOTAL_DEPOSITS ==
                                    bytes32(
                                        uint256(keccak256("eip1967.vaultStorage.totalDeposits")) - 1
                                    )
                            );
                            assert(
                                _MAX_DEPOSIT_CAP ==
                                    bytes32(
                                        uint256(keccak256("eip1967.vaultStorage.maxDepositCap")) - 1
                                    )
                            );
                        }
                        function initialize(
                            address _underlying,
                            uint256 _toInvestNumerator,
                            uint256 _toInvestDenominator,
                            uint256 _underlyingUnit,
                            uint256 _maxDepositCap_,
                            uint256 _implementationChangeDelay,
                            uint256 _strategyChangeDelay
                        ) public initializer {
                            _setUnderlying(_underlying);
                            _setVaultFractionToInvestNumerator(_toInvestNumerator);
                            _setVaultFractionToInvestDenominator(_toInvestDenominator);
                            _setMaxDepositCap(_maxDepositCap_);
                            _setUnderlyingUnit(_underlyingUnit);
                            _setNextImplementationDelay(_implementationChangeDelay);
                            _setStrategyTimeLock(_strategyChangeDelay);
                            _setStrategyUpdateTime(0);
                            _setFutureStrategy(address(0));
                        }
                        function _setStrategy(address _address) internal {
                            setAddress(_STRATEGY_SLOT, _address);
                        }
                        function _strategy() internal view returns (address) {
                            return getAddress(_STRATEGY_SLOT);
                        }
                        function _setUnderlying(address _address) internal {
                            setAddress(_UNDERLYING_SLOT, _address);
                        }
                        function _underlying() internal view returns (address) {
                            return getAddress(_UNDERLYING_SLOT);
                        }
                        function _setUnderlyingUnit(uint256 _value) internal {
                            setUint256(_UNDERLYING_UNIT_SLOT, _value);
                        }
                        function _underlyingUnit() internal view returns (uint256) {
                            return getUint256(_UNDERLYING_UNIT_SLOT);
                        }
                        function _setVaultFractionToInvestNumerator(uint256 _value) internal {
                            setUint256(_VAULT_FRACTION_TO_INVEST_NUMERATOR_SLOT, _value);
                        }
                        function _vaultFractionToInvestNumerator() internal view returns (uint256) {
                            return getUint256(_VAULT_FRACTION_TO_INVEST_NUMERATOR_SLOT);
                        }
                        function _setVaultFractionToInvestDenominator(uint256 _value) internal {
                            setUint256(_VAULT_FRACTION_TO_INVEST_DENOMINATOR_SLOT, _value);
                        }
                        function _vaultFractionToInvestDenominator()
                            internal
                            view
                            returns (uint256)
                        {
                            return getUint256(_VAULT_FRACTION_TO_INVEST_DENOMINATOR_SLOT);
                        }
                        function _setTotalDeposits(uint256 _value) internal {
                            setUint256(_TOTAL_DEPOSITS, _value);
                        }
                        function _totalDeposits() internal view returns (uint256) {
                            return getUint256(_TOTAL_DEPOSITS);
                        }
                        function _setMaxDepositCap(uint256 _value) internal {
                            setUint256(_MAX_DEPOSIT_CAP, _value);
                        }
                        function _maxDepositCap() internal view returns (uint256) {
                            return getUint256(_MAX_DEPOSIT_CAP);
                        }
                        function _setNextImplementation(address _address) internal {
                            setAddress(_NEXT_IMPLEMENTATION_SLOT, _address);
                        }
                        function _nextImplementation() internal view returns (address) {
                            return getAddress(_NEXT_IMPLEMENTATION_SLOT);
                        }
                        function _setNextImplementationTimestamp(uint256 _value) internal {
                            setUint256(_NEXT_IMPLEMENTATION_TIMESTAMP_SLOT, _value);
                        }
                        function _nextImplementationTimestamp() internal view returns (uint256) {
                            return getUint256(_NEXT_IMPLEMENTATION_TIMESTAMP_SLOT);
                        }
                        function _setNextImplementationDelay(uint256 _value) internal {
                            setUint256(_NEXT_IMPLEMENTATION_DELAY_SLOT, _value);
                        }
                        function _nextImplementationDelay() internal view returns (uint256) {
                            return getUint256(_NEXT_IMPLEMENTATION_DELAY_SLOT);
                        }
                        function _setStrategyTimeLock(uint256 _value) internal {
                            setUint256(_STRATEGY_TIME_LOCK_SLOT, _value);
                        }
                        function _strategyTimeLock() internal view returns (uint256) {
                            return getUint256(_STRATEGY_TIME_LOCK_SLOT);
                        }
                        function _setFutureStrategy(address _value) internal {
                            setAddress(_FUTURE_STRATEGY_SLOT, _value);
                        }
                        function _futureStrategy() internal view returns (address) {
                            return getAddress(_FUTURE_STRATEGY_SLOT);
                        }
                        function _setStrategyUpdateTime(uint256 _value) internal {
                            setUint256(_STRATEGY_UPDATE_TIME_SLOT, _value);
                        }
                        function _strategyUpdateTime() internal view returns (uint256) {
                            return getUint256(_STRATEGY_UPDATE_TIME_SLOT);
                        }
                        function setAddress(bytes32 slot, address _address) private {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                sstore(slot, _address)
                            }
                        }
                        function setUint256(bytes32 slot, uint256 _value) private {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                sstore(slot, _value)
                            }
                        }
                        function getAddress(bytes32 slot) private view returns (address str) {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                str := sload(slot)
                            }
                        }
                        function getUint256(bytes32 slot) private view returns (uint256 str) {
                            // solhint-disable-next-line no-inline-assembly
                            assembly {
                                str := sload(slot)
                            }
                        }
                        uint256[50] private ______gap;
                    }
                    pragma solidity 0.5.16;
                    interface IController {
                        function whiteList(address _target) external view returns (bool);
                        function addVaultAndStrategy(address _vault, address _strategy) external;
                        function forceUnleashed(address _vault) external;
                        function hasVault(address _vault) external returns (bool);
                        function salvage(address _token, uint256 amount) external;
                        function salvageStrategy(
                            address _strategy,
                            address _token,
                            uint256 amount
                        ) external;
                        function notifyFee(address _underlying, uint256 fee) external;
                        function profitSharingNumerator() external view returns (uint256);
                        function profitSharingDenominator() external view returns (uint256);
                        function treasury() external view returns (address);
                    }
                    pragma solidity 0.5.16;
                    interface IStrategy {
                        function unsalvagableTokens(address tokens) external view returns (bool);
                        function governance() external view returns (address);
                        function controller() external view returns (address);
                        function underlying() external view returns (address);
                        function vault() external view returns (address);
                        function withdrawAllToVault() external;
                        function withdrawToVault(uint256 amount) external;
                        function investedUnderlyingBalance() external view returns (uint256); // itsNotMuch()
                        // should only be called by controller
                        function salvage(
                            address recipient,
                            address token,
                            uint256 amount
                        ) external;
                        function forceUnleashed() external;
                        function depositArbCheck() external view returns (bool);
                    }
                    pragma solidity 0.5.16;
                    interface IUpgradeSource {
                        function shouldUpgrade() external view returns (bool, address);
                        function finalizeUpgrade() external;
                    }
                    pragma solidity 0.5.16;
                    interface IVault {
                        function underlyingBalanceInVault() external view returns (uint256);
                        function underlyingBalanceWithInvestment() external view returns (uint256);
                        function governance() external view returns (address);
                        function controller() external view returns (address);
                        function underlying() external view returns (address);
                        function strategy() external view returns (address);
                        function setStrategy(address _strategy) external;
                        function setVaultFractionToInvest(uint256 numerator, uint256 denominator)
                            external;
                        function deposit(uint256 amountWei) external;
                        function depositFor(uint256 amountWei, address holder) external;
                        function withdrawAll() external;
                        function withdraw(uint256 numberOfShares) external;
                        function getPricePerFullShare() external view returns (uint256);
                        function underlyingBalanceWithInvestmentForHolder(address holder)
                            external
                            view
                            returns (uint256);
                        // force unleash should be callable only by the controller (by the force unleasher) or by governance
                        function forceUnleashed() external;
                        function rebalance() external;
                    }
                    pragma solidity ^0.5.0;
                    import "@openzeppelin/upgrades/contracts/Initializable.sol";
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with GSN meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    contract Context is Initializable {
                        // Empty internal constructor, to prevent people from mistakenly deploying
                        // an instance of this contract, which should be used via inheritance.
                        constructor () internal { }
                        // solhint-disable-previous-line no-empty-blocks
                        function _msgSender() internal view returns (address payable) {
                            return msg.sender;
                        }
                        function _msgData() internal view returns (bytes memory) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @dev Standard math utilities missing in the Solidity language.
                     */
                    library Math {
                        /**
                         * @dev Returns the largest of two numbers.
                         */
                        function max(uint256 a, uint256 b) internal pure returns (uint256) {
                            return a >= b ? a : b;
                        }
                        /**
                         * @dev Returns the smallest of two numbers.
                         */
                        function min(uint256 a, uint256 b) internal pure returns (uint256) {
                            return a < b ? a : b;
                        }
                        /**
                         * @dev Returns the average of two numbers. The result is rounded towards
                         * zero.
                         */
                        function average(uint256 a, uint256 b) internal pure returns (uint256) {
                            // (a + b) / 2 can overflow, so we distribute
                            return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
                        }
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                            return c;
                        }
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            return sub(a, b, "SafeMath: subtraction overflow");
                        }
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         *
                         * _Available since v2.4.0._
                         */
                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b <= a, errorMessage);
                            uint256 c = a - b;
                            return c;
                        }
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                            if (a == 0) {
                                return 0;
                            }
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                            return c;
                        }
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            return div(a, b, "SafeMath: division by zero");
                        }
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            // Solidity only automatically asserts when dividing by 0
                            require(b > 0, errorMessage);
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                            return c;
                        }
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            return mod(a, b, "SafeMath: modulo by zero");
                        }
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts with custom message when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b != 0, errorMessage);
                            return a % b;
                        }
                    }
                    pragma solidity ^0.5.0;
                    import "@openzeppelin/upgrades/contracts/Initializable.sol";
                    import "../../GSN/Context.sol";
                    import "./IERC20.sol";
                    import "../../math/SafeMath.sol";
                    /**
                     * @dev Implementation of the {IERC20} interface.
                     *
                     * This implementation is agnostic to the way tokens are created. This means
                     * that a supply mechanism has to be added in a derived contract using {_mint}.
                     * For a generic mechanism see {ERC20Mintable}.
                     *
                     * TIP: For a detailed writeup see our guide
                     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
                     * to implement supply mechanisms].
                     *
                     * We have followed general OpenZeppelin guidelines: functions revert instead
                     * of returning `false` on failure. This behavior is nonetheless conventional
                     * and does not conflict with the expectations of ERC20 applications.
                     *
                     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
                     * This allows applications to reconstruct the allowance for all accounts just
                     * by listening to said events. Other implementations of the EIP may not emit
                     * these events, as it isn't required by the specification.
                     *
                     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
                     * functions have been added to mitigate the well-known issues around setting
                     * allowances. See {IERC20-approve}.
                     */
                    contract ERC20 is Initializable, Context, IERC20 {
                        using SafeMath for uint256;
                        mapping (address => uint256) private _balances;
                        mapping (address => mapping (address => uint256)) private _allowances;
                        uint256 private _totalSupply;
                        /**
                         * @dev See {IERC20-totalSupply}.
                         */
                        function totalSupply() public view returns (uint256) {
                            return _totalSupply;
                        }
                        /**
                         * @dev See {IERC20-balanceOf}.
                         */
                        function balanceOf(address account) public view returns (uint256) {
                            return _balances[account];
                        }
                        /**
                         * @dev See {IERC20-transfer}.
                         *
                         * Requirements:
                         *
                         * - `recipient` cannot be the zero address.
                         * - the caller must have a balance of at least `amount`.
                         */
                        function transfer(address recipient, uint256 amount) public returns (bool) {
                            _transfer(_msgSender(), recipient, amount);
                            return true;
                        }
                        /**
                         * @dev See {IERC20-allowance}.
                         */
                        function allowance(address owner, address spender) public view returns (uint256) {
                            return _allowances[owner][spender];
                        }
                        /**
                         * @dev See {IERC20-approve}.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function approve(address spender, uint256 amount) public returns (bool) {
                            _approve(_msgSender(), spender, amount);
                            return true;
                        }
                        /**
                         * @dev See {IERC20-transferFrom}.
                         *
                         * Emits an {Approval} event indicating the updated allowance. This is not
                         * required by the EIP. See the note at the beginning of {ERC20};
                         *
                         * Requirements:
                         * - `sender` and `recipient` cannot be the zero address.
                         * - `sender` must have a balance of at least `amount`.
                         * - the caller must have allowance for `sender`'s tokens of at least
                         * `amount`.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
                            _transfer(sender, recipient, amount);
                            _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                            return true;
                        }
                        /**
                         * @dev Atomically increases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to {approve} that can be used as a mitigation for
                         * problems described in {IERC20-approve}.
                         *
                         * Emits an {Approval} event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
                            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                            return true;
                        }
                        /**
                         * @dev Atomically decreases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to {approve} that can be used as a mitigation for
                         * problems described in {IERC20-approve}.
                         *
                         * Emits an {Approval} event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         * - `spender` must have allowance for the caller of at least
                         * `subtractedValue`.
                         */
                        function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
                            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                            return true;
                        }
                        /**
                         * @dev Moves tokens `amount` from `sender` to `recipient`.
                         *
                         * This is internal function is equivalent to {transfer}, and can be used to
                         * e.g. implement automatic token fees, slashing mechanisms, etc.
                         *
                         * Emits a {Transfer} event.
                         *
                         * Requirements:
                         *
                         * - `sender` cannot be the zero address.
                         * - `recipient` cannot be the zero address.
                         * - `sender` must have a balance of at least `amount`.
                         */
                        function _transfer(address sender, address recipient, uint256 amount) internal {
                            require(sender != address(0), "ERC20: transfer from the zero address");
                            require(recipient != address(0), "ERC20: transfer to the zero address");
                            _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                            _balances[recipient] = _balances[recipient].add(amount);
                            emit Transfer(sender, recipient, amount);
                        }
                        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                         * the total supply.
                         *
                         * Emits a {Transfer} event with `from` set to the zero address.
                         *
                         * Requirements
                         *
                         * - `to` cannot be the zero address.
                         */
                        function _mint(address account, uint256 amount) internal {
                            require(account != address(0), "ERC20: mint to the zero address");
                            _totalSupply = _totalSupply.add(amount);
                            _balances[account] = _balances[account].add(amount);
                            emit Transfer(address(0), account, amount);
                        }
                        /**
                         * @dev Destroys `amount` tokens from `account`, reducing the
                         * total supply.
                         *
                         * Emits a {Transfer} event with `to` set to the zero address.
                         *
                         * Requirements
                         *
                         * - `account` cannot be the zero address.
                         * - `account` must have at least `amount` tokens.
                         */
                        function _burn(address account, uint256 amount) internal {
                            require(account != address(0), "ERC20: burn from the zero address");
                            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                            _totalSupply = _totalSupply.sub(amount);
                            emit Transfer(account, address(0), amount);
                        }
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
                         *
                         * This is internal function is equivalent to `approve`, and can be used to
                         * e.g. set automatic allowances for certain subsystems, etc.
                         *
                         * Emits an {Approval} event.
                         *
                         * Requirements:
                         *
                         * - `owner` cannot be the zero address.
                         * - `spender` cannot be the zero address.
                         */
                        function _approve(address owner, address spender, uint256 amount) internal {
                            require(owner != address(0), "ERC20: approve from the zero address");
                            require(spender != address(0), "ERC20: approve to the zero address");
                            _allowances[owner][spender] = amount;
                            emit Approval(owner, spender, amount);
                        }
                        /**
                         * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
                         * from the caller's allowance.
                         *
                         * See {_burn} and {_approve}.
                         */
                        function _burnFrom(address account, uint256 amount) internal {
                            _burn(account, amount);
                            _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
                        }
                        uint256[50] private ______gap;
                    }
                    pragma solidity ^0.5.0;
                    import "@openzeppelin/upgrades/contracts/Initializable.sol";
                    import "./IERC20.sol";
                    /**
                     * @dev Optional functions from the ERC20 standard.
                     */
                    contract ERC20Detailed is Initializable, IERC20 {
                        string private _name;
                        string private _symbol;
                        uint8 private _decimals;
                        /**
                         * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
                         * these values are immutable: they can only be set once during
                         * construction.
                         */
                        function initialize(string memory name, string memory symbol, uint8 decimals) public initializer {
                            _name = name;
                            _symbol = symbol;
                            _decimals = decimals;
                        }
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() public view returns (string memory) {
                            return _name;
                        }
                        /**
                         * @dev Returns the symbol of the token, usually a shorter version of the
                         * name.
                         */
                        function symbol() public view returns (string memory) {
                            return _symbol;
                        }
                        /**
                         * @dev Returns the number of decimals used to get its user representation.
                         * For example, if `decimals` equals `2`, a balance of `505` tokens should
                         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                         *
                         * Tokens usually opt for a value of 18, imitating the relationship between
                         * Ether and Wei.
                         *
                         * NOTE: This information is only used for _display_ purposes: it in
                         * no way affects any of the arithmetic of the contract, including
                         * {IERC20-balanceOf} and {IERC20-transfer}.
                         */
                        function decimals() public view returns (uint8) {
                            return _decimals;
                        }
                        uint256[50] private ______gap;
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
                     * the optional functions; to access them see {ERC20Detailed}.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to {approve}. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                    }
                    pragma solidity ^0.5.0;
                    import "./IERC20.sol";
                    import "../../math/SafeMath.sol";
                    import "../../utils/Address.sol";
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                     * contract returns false). Tokens that return no value (and instead revert or
                     * throw on failure) are also supported, non-reverting calls are assumed to be
                     * successful.
                     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                     */
                    library SafeERC20 {
                        using SafeMath for uint256;
                        using Address for address;
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        /**
                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                         * @param token The token targeted by the call.
                         * @param data The call data (encoded using abi.encode or one of its variants).
                         */
                        function callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves.
                            // A Solidity high level call has three parts:
                            //  1. The target address is checked to verify it contains contract code
                            //  2. The call itself is made, and success asserted
                            //  3. The return value is decoded, which in turn checks the size of the returned data.
                            // solhint-disable-next-line max-line-length
                            require(address(token).isContract(), "SafeERC20: call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = address(token).call(data);
                            require(success, "SafeERC20: low-level call failed");
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    pragma solidity ^0.5.5;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following 
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                            // for accounts without code, i.e. `keccak256('')`
                            bytes32 codehash;
                            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { codehash := extcodehash(account) }
                            return (codehash != accountHash && codehash != 0x0);
                        }
                        /**
                         * @dev Converts an `address` into `address payable`. Note that this is
                         * simply a type cast: the actual underlying value is not changed.
                         *
                         * _Available since v2.4.0._
                         */
                        function toPayable(address account) internal pure returns (address payable) {
                            return address(uint160(account));
                        }
                        /**
                         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                         * `recipient`, forwarding all available gas and reverting on errors.
                         *
                         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                         * of certain opcodes, possibly making contracts go over the 2300 gas limit
                         * imposed by `transfer`, making them unable to receive funds via
                         * `transfer`. {sendValue} removes this limitation.
                         *
                         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                         *
                         * IMPORTANT: because control is transferred to `recipient`, care must be
                         * taken to not create reentrancy vulnerabilities. Consider using
                         * {ReentrancyGuard} or the
                         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         *
                         * _Available since v2.4.0._
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            // solhint-disable-next-line avoid-call-value
                            (bool success, ) = recipient.call.value(amount)("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                    }
                    pragma solidity ^0.5.0;
                    import "@openzeppelin/upgrades/contracts/Initializable.sol";
                    /**
                     * @dev Contract module that helps prevent reentrant calls to a function.
                     *
                     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                     * available, which can be applied to functions to make sure there are no nested
                     * (reentrant) calls to them.
                     *
                     * Note that because there is a single `nonReentrant` guard, functions marked as
                     * `nonReentrant` may not call one another. This can be worked around by making
                     * those functions `private`, and then adding `external` `nonReentrant` entry
                     * points to them.
                     */
                    contract ReentrancyGuard is Initializable {
                        // counter to allow mutex lock with only one SSTORE operation
                        uint256 private _guardCounter;
                        function initialize() public initializer {
                            // The counter starts at one to prevent changing it from zero to a non-zero
                            // value, which is a more expensive operation.
                            _guardCounter = 1;
                        }
                        /**
                         * @dev Prevents a contract from calling itself, directly or indirectly.
                         * Calling a `nonReentrant` function from another `nonReentrant`
                         * function is not supported. It is possible to prevent this from happening
                         * by making the `nonReentrant` function external, and make it call a
                         * `private` function that does the actual work.
                         */
                        modifier nonReentrant() {
                            _guardCounter += 1;
                            uint256 localCounter = _guardCounter;
                            _;
                            require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
                        }
                        uint256[50] private ______gap;
                    }
                    pragma solidity >=0.4.24 <0.7.0;
                    /**
                     * @title Initializable
                     *
                     * @dev Helper contract to support initializer functions. To use it, replace
                     * the constructor with a function that has the `initializer` modifier.
                     * WARNING: Unlike constructors, initializer functions must be manually
                     * invoked. This applies both to deploying an Initializable contract, as well
                     * as extending an Initializable contract via inheritance.
                     * WARNING: When used with inheritance, manual care must be taken to not invoke
                     * a parent initializer twice, or ensure that all initializers are idempotent,
                     * because this is not dealt with automatically as with constructors.
                     */
                    contract Initializable {
                      /**
                       * @dev Indicates that the contract has been initialized.
                       */
                      bool private initialized;
                      /**
                       * @dev Indicates that the contract is in the process of being initialized.
                       */
                      bool private initializing;
                      /**
                       * @dev Modifier to use in the initializer function of a contract.
                       */
                      modifier initializer() {
                        require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
                        bool isTopLevelCall = !initializing;
                        if (isTopLevelCall) {
                          initializing = true;
                          initialized = true;
                        }
                        _;
                        if (isTopLevelCall) {
                          initializing = false;
                        }
                      }
                      /// @dev Returns true if and only if the function is running in the constructor
                      function isConstructor() private view returns (bool) {
                        // extcodesize checks the size of the code stored in an address, and
                        // address returns the current address. Since the code is still not
                        // deployed when running a constructor, any checks on its code size will
                        // yield zero, making it an effective way to detect if a contract is
                        // under construction or not.
                        address self = address(this);
                        uint256 cs;
                        assembly { cs := extcodesize(self) }
                        return cs == 0;
                      }
                      // Reserved storage space to allow for layout changes in the future.
                      uint256[50] private ______gap;
                    }
                    

                    File 4 of 11: IdleStrategyUSDTMainnet
                    pragma solidity 0.5.16;
                    import "./Governable.sol";
                    contract Controllable is Governable {
                        constructor(address _storage) public Governable(_storage) {}
                        modifier onlyController() {
                            require(store.isController(msg.sender), "Not a controller");
                            _;
                        }
                        modifier onlyControllerOrGovernance() {
                            require(
                                (store.isController(msg.sender) || store.isGovernance(msg.sender)),
                                "The caller must be controller or governance"
                            );
                            _;
                        }
                        function controller() public view returns (address) {
                            return store.controller();
                        }
                    }
                    pragma solidity 0.5.16;
                    import "./Storage.sol";
                    contract Governable {
                        Storage public store;
                        constructor(address _store) public {
                            require(_store != address(0), "new storage shouldn't be empty");
                            store = Storage(_store);
                        }
                        modifier onlyGovernance() {
                            require(store.isGovernance(msg.sender), "Not governance");
                            _;
                        }
                        function setStorage(address _store) public onlyGovernance {
                            require(_store != address(0), "new storage shouldn't be empty");
                            store = Storage(_store);
                        }
                        function governance() public view returns (address) {
                            return store.governance();
                        }
                    }
                    pragma solidity 0.5.16;
                    contract Storage {
                        address public governance;
                        address public controller;
                        constructor() public {
                            governance = msg.sender;
                        }
                        modifier onlyGovernance() {
                            require(isGovernance(msg.sender), "Not governance");
                            _;
                        }
                        function setGovernance(address _governance) public onlyGovernance {
                            require(_governance != address(0), "new governance shouldn't be empty");
                            governance = _governance;
                        }
                        function setController(address _controller) public onlyGovernance {
                            require(_controller != address(0), "new controller shouldn't be empty");
                            controller = _controller;
                        }
                        function isGovernance(address account) public view returns (bool) {
                            return account == governance;
                        }
                        function isController(address account) public view returns (bool) {
                            return account == controller;
                        }
                    }
                    pragma solidity 0.5.16;
                    interface IController {
                        function whiteList(address _target) external view returns (bool);
                        function addVaultAndStrategy(address _vault, address _strategy) external;
                        function forceUnleashed(address _vault) external;
                        function hasVault(address _vault) external returns (bool);
                        function salvage(address _token, uint256 amount) external;
                        function salvageStrategy(
                            address _strategy,
                            address _token,
                            uint256 amount
                        ) external;
                        function notifyFee(address _underlying, uint256 fee) external;
                        function profitSharingNumerator() external view returns (uint256);
                        function profitSharingDenominator() external view returns (uint256);
                        function treasury() external view returns (address);
                    }
                    pragma solidity 0.5.16;
                    interface IStrategy {
                        function unsalvagableTokens(address tokens) external view returns (bool);
                        function governance() external view returns (address);
                        function controller() external view returns (address);
                        function underlying() external view returns (address);
                        function vault() external view returns (address);
                        function withdrawAllToVault() external;
                        function withdrawToVault(uint256 amount) external;
                        function investedUnderlyingBalance() external view returns (uint256); // itsNotMuch()
                        // should only be called by controller
                        function salvage(
                            address recipient,
                            address token,
                            uint256 amount
                        ) external;
                        function forceUnleashed() external;
                        function depositArbCheck() external view returns (bool);
                    }
                    pragma solidity 0.5.16;
                    interface IVault {
                        function underlyingBalanceInVault() external view returns (uint256);
                        function underlyingBalanceWithInvestment() external view returns (uint256);
                        function governance() external view returns (address);
                        function controller() external view returns (address);
                        function underlying() external view returns (address);
                        function strategy() external view returns (address);
                        function setStrategy(address _strategy) external;
                        function setVaultFractionToInvest(uint256 numerator, uint256 denominator)
                            external;
                        function deposit(uint256 amountWei) external;
                        function depositFor(uint256 amountWei, address holder) external;
                        function withdrawAll() external;
                        function withdraw(uint256 numberOfShares) external;
                        function getPricePerFullShare() external view returns (uint256);
                        function underlyingBalanceWithInvestmentForHolder(address holder)
                            external
                            view
                            returns (uint256);
                        // force unleash should be callable only by the controller (by the force unleasher) or by governance
                        function forceUnleashed() external;
                        function rebalance() external;
                    }
                    pragma solidity 0.5.16;
                    import "@openzeppelin/contracts/math/SafeMath.sol";
                    import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
                    import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
                    import "../interfaces/IController.sol";
                    import "../Controllable.sol";
                    contract RewardTokenProfitNotifier is Controllable {
                        using SafeMath for uint256;
                        using SafeERC20 for IERC20;
                        uint256 public profitSharingNumerator;
                        uint256 public profitSharingDenominator;
                        address public rewardToken;
                        constructor(address _storage, address _rewardToken)
                            public
                            Controllable(_storage)
                        {
                            rewardToken = _rewardToken;
                            profitSharingNumerator = 0;
                            profitSharingDenominator = 100;
                            require(
                                profitSharingNumerator < profitSharingDenominator,
                                "invalid profit share"
                            );
                        }
                        event ProfitLogInReward(
                            uint256 profitAmount,
                            uint256 feeAmount,
                            uint256 timestamp
                        );
                        function notifyProfitInRewardToken(uint256 _rewardBalance) internal {
                            if (_rewardBalance > 0 && profitSharingNumerator > 0) {
                                uint256 feeAmount =
                                    _rewardBalance.mul(profitSharingNumerator).div(
                                        profitSharingDenominator
                                    );
                                emit ProfitLogInReward(_rewardBalance, feeAmount, block.timestamp);
                                IERC20(rewardToken).safeApprove(controller(), 0);
                                IERC20(rewardToken).safeApprove(controller(), feeAmount);
                                IController(controller()).notifyFee(rewardToken, feeAmount);
                            } else {
                                emit ProfitLogInReward(0, 0, block.timestamp);
                            }
                        }
                        function setProfitSharingNumerator(uint256 _profitSharingNumerator)
                            external
                            onlyGovernance
                        {
                            profitSharingNumerator = _profitSharingNumerator;
                        }
                    }
                    pragma solidity 0.5.16;
                    contract IIdleTokenHelper {
                        function getMintingPrice(address idleYieldToken)
                            external
                            view
                            returns (uint256 mintingPrice);
                        function getRedeemPrice(address idleYieldToken)
                            external
                            view
                            returns (uint256 redeemPrice);
                        function getRedeemPrice(address idleYieldToken, address user)
                            external
                            view
                            returns (uint256 redeemPrice);
                    }
                    pragma solidity 0.5.16;
                    import "@openzeppelin/contracts/math/Math.sol";
                    import "@openzeppelin/contracts/math/SafeMath.sol";
                    import "@openzeppelin/contracts/token/ERC20/ERC20Detailed.sol";
                    import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
                    import "../../Controllable.sol";
                    import "../../interfaces/IStrategy.sol";
                    import "../../uniswap/interfaces/IUniswapV2Router02.sol";
                    import "./IdleToken.sol";
                    import "./IIdleTokenHelper.sol";
                    import "../RewardTokenProfitNotifier.sol";
                    import "../../interfaces/IVault.sol";
                    contract IdleFinanceStrategy is IStrategy, RewardTokenProfitNotifier {
                        using SafeMath for uint256;
                        using SafeERC20 for IERC20;
                        event ProfitsNotCollected(address);
                        event Liquidating(address, uint256);
                        IERC20 public underlying;
                        address public idleUnderlying;
                        uint256 public virtualPrice;
                        IIdleTokenHelper public idleTokenHelper;
                        address public vault;
                        address public comp;
                        address public idle;
                        address[] public uniswapComp;
                        address[] public uniswapIdle;
                        address public uniswapRouterV2;
                        bool public sellComp;
                        bool public sellIdle;
                        bool public claimAllowed;
                        bool public protected;
                        // These tokens cannot be claimed by the controller
                        mapping(address => bool) public unsalvagableTokens;
                        modifier restricted() {
                            require(
                                msg.sender == vault ||
                                    msg.sender == address(controller()) ||
                                    msg.sender == address(governance()),
                                "The sender has to be the controller or vault or governance"
                            );
                            _;
                        }
                        modifier updateVirtualPrice() {
                            if (protected) {
                                require(
                                    virtualPrice <= idleTokenHelper.getRedeemPrice(idleUnderlying),
                                    "virtual price is higher than needed"
                                );
                            }
                            _;
                            virtualPrice = idleTokenHelper.getRedeemPrice(idleUnderlying);
                        }
                        constructor(
                            address _storage,
                            address _underlying,
                            address _idleUnderlying,
                            address _vault,
                            address _comp,
                            address _idle,
                            address _weth,
                            address _uniswap
                        ) public RewardTokenProfitNotifier(_storage, _idle) {
                            comp = _comp;
                            idle = _idle;
                            underlying = IERC20(_underlying);
                            idleUnderlying = _idleUnderlying;
                            vault = _vault;
                            uniswapRouterV2 = _uniswap;
                            protected = true;
                            // set these tokens to be not salvagable
                            unsalvagableTokens[_underlying] = true;
                            unsalvagableTokens[_idleUnderlying] = true;
                            unsalvagableTokens[_comp] = true;
                            unsalvagableTokens[_idle] = true;
                            uniswapComp = [_comp, _weth, _idle];
                            uniswapIdle = [_idle, _weth, _underlying];
                            idleTokenHelper = IIdleTokenHelper(
                                0x04Ce60ed10F6D2CfF3AA015fc7b950D13c113be5
                            );
                            virtualPrice = idleTokenHelper.getRedeemPrice(idleUnderlying);
                        }
                        function depositArbCheck() public view returns (bool) {
                            return true;
                        }
                        /**
                         * The strategy invests by supplying the underlying token into IDLE.
                         */
                        function investAllUnderlying() public restricted updateVirtualPrice {
                            uint256 balance = underlying.balanceOf(address(this));
                            underlying.safeApprove(address(idleUnderlying), 0);
                            underlying.safeApprove(address(idleUnderlying), balance);
                            IIdleTokenV3_1(idleUnderlying).mintIdleToken(balance, true, address(0));
                        }
                        /**
                         * Exits IDLE and transfers everything to the vault.
                         */
                        function withdrawAllToVault() external restricted updateVirtualPrice {
                            withdrawAll();
                            IERC20(address(underlying)).safeTransfer(
                                vault,
                                underlying.balanceOf(address(this))
                            );
                        }
                        /**
                         * Withdraws all from IDLE
                         */
                        function withdrawAll() internal {
                            uint256 balance = IERC20(idleUnderlying).balanceOf(address(this));
                            // this automatically claims the crops
                            IIdleTokenV3_1(idleUnderlying).redeemIdleToken(balance);
                            liquidateComp();
                            liquidateIdle();
                        }
                        function withdrawToVault(uint256 amountUnderlying) public restricted {
                            // this method is called when the vault is missing funds
                            // we will calculate the proportion of idle LP tokens that matches
                            // the underlying amount requested
                            uint256 balanceBefore = underlying.balanceOf(address(this));
                            uint256 totalIdleLpTokens =
                                IERC20(idleUnderlying).balanceOf(address(this));
                            uint256 totalUnderlyingBalance =
                                totalIdleLpTokens.mul(virtualPrice).div(1e18);
                            uint256 ratio = amountUnderlying.mul(1e18).div(totalUnderlyingBalance);
                            uint256 toRedeem = totalIdleLpTokens.mul(ratio).div(1e18);
                            IIdleTokenV3_1(idleUnderlying).redeemIdleToken(toRedeem);
                            uint256 balanceAfter = underlying.balanceOf(address(this));
                            underlying.safeTransfer(vault, balanceAfter.sub(balanceBefore));
                        }
                        /**
                         * Withdraws all assets, liquidates COMP, and invests again in the required ratio.
                         */
                        function forceUnleashed() public restricted updateVirtualPrice {
                            if (claimAllowed) {
                                claim();
                            }
                            liquidateComp();
                            liquidateIdle();
                            // this updates the virtual price
                            investAllUnderlying();
                            // state of supply/loan will be updated by the modifier
                        }
                        /**
                         * Salvages a token.
                         */
                        function salvage(
                            address recipient,
                            address token,
                            uint256 amount
                        ) public onlyGovernance {
                            // To make sure that governance cannot come in and take away the coins
                            require(
                                !unsalvagableTokens[token],
                                "token is defined as not salvagable"
                            );
                            IERC20(token).safeTransfer(recipient, amount);
                        }
                        function claim() internal {
                            IIdleTokenV3_1(idleUnderlying).redeemIdleToken(0);
                        }
                        function liquidateComp() internal {
                            if (!sellComp) {
                                // Profits can be disabled for possible simplified and rapid exit
                                emit ProfitsNotCollected(comp);
                                return;
                            }
                            // no profit notification, comp is liquidated to IDLE and will be notified there
                            uint256 compBalance = IERC20(comp).balanceOf(address(this));
                            if (compBalance > 0) {
                                emit Liquidating(address(comp), compBalance);
                                IERC20(comp).safeApprove(uniswapRouterV2, 0);
                                IERC20(comp).safeApprove(uniswapRouterV2, compBalance);
                                // we can accept 1 as the minimum because this will be called only by a trusted worker
                                IUniswapV2Router02(uniswapRouterV2).swapExactTokensForTokens(
                                    compBalance,
                                    1,
                                    uniswapComp,
                                    address(this),
                                    block.timestamp
                                );
                            }
                        }
                        function liquidateIdle() internal {
                            if (!sellIdle) {
                                // Profits can be disabled for possible simplified and rapid exit
                                emit ProfitsNotCollected(idle);
                                return;
                            }
                            uint256 rewardBalance = IERC20(idle).balanceOf(address(this));
                            notifyProfitInRewardToken(rewardBalance);
                            uint256 idleBalance = IERC20(idle).balanceOf(address(this));
                            if (idleBalance > 0) {
                                emit Liquidating(address(idle), idleBalance);
                                IERC20(idle).safeApprove(uniswapRouterV2, 0);
                                IERC20(idle).safeApprove(uniswapRouterV2, idleBalance);
                                // we can accept 1 as the minimum because this will be called only by a trusted worker
                                IUniswapV2Router02(uniswapRouterV2).swapExactTokensForTokens(
                                    idleBalance,
                                    1,
                                    uniswapIdle,
                                    address(this),
                                    block.timestamp
                                );
                            }
                        }
                        /**
                         * Returns the current balance. Ignores COMP that was not liquidated and invested.
                         */
                        function investedUnderlyingBalance() public view returns (uint256) {
                            // NOTE: The use of virtual price is okay for appreciating assets inside IDLE,
                            // but would be wrong and exploitable if funds were lost by IDLE, indicated by
                            // the virtualPrice being greater than the token price.
                            if (protected) {
                                require(
                                    virtualPrice <= idleTokenHelper.getRedeemPrice(idleUnderlying),
                                    "virtual price is higher than needed"
                                );
                            }
                            uint256 invested =
                                IERC20(idleUnderlying)
                                    .balanceOf(address(this))
                                    .mul(virtualPrice)
                                    .div(1e18);
                            return invested.add(IERC20(underlying).balanceOf(address(this)));
                        }
                        function setLiquidation(
                            bool _sellComp,
                            bool _sellIdle,
                            bool _claimAllowed
                        ) public onlyGovernance {
                            sellComp = _sellComp;
                            sellIdle = _sellIdle;
                            claimAllowed = _claimAllowed;
                        }
                        function setProtected(bool _protected) public onlyGovernance {
                            protected = _protected;
                        }
                    }
                    pragma solidity 0.5.16;
                    import "./IdleFinanceStrategy.sol";
                    /**
                     * Adds the mainnet addresses to the PickleStrategy3Pool
                     */
                    contract IdleStrategyUSDTMainnet is IdleFinanceStrategy {
                        // token addresses
                        address public constant __weth =
                            address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
                        address public constant __usdt =
                            address(0xdAC17F958D2ee523a2206206994597C13D831ec7);
                        address public constant __uniswap =
                            address(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
                        address public constant __idleUnderlying =
                            address(0xF34842d05A1c888Ca02769A633DF37177415C2f8);
                        address public constant __comp =
                            address(0xc00e94Cb662C3520282E6f5717214004A7f26888);
                        address public constant __idle =
                            address(0x875773784Af8135eA0ef43b5a374AaD105c5D39e);
                        constructor(address _storage, address _vault)
                            public
                            IdleFinanceStrategy(
                                _storage,
                                __usdt,
                                __idleUnderlying,
                                _vault,
                                __comp,
                                __idle,
                                __weth,
                                __uniswap
                            )
                        {}
                    }
                    /**
                     * @title: Idle Token interface
                     * @author: Idle Labs Inc., idle.finance
                     */
                    pragma solidity 0.5.16;
                    interface IIdleTokenV3_1 {
                        // view
                        /**
                         * IdleToken price calculation, in underlying
                         *
                         * @return : price in underlying token
                         */
                        function tokenPrice() external view returns (uint256 price);
                        /**
                         * @return : underlying token address
                         */
                        function token() external view returns (address);
                        /**
                         * Get APR of every ILendingProtocol
                         *
                         * @return addresses: array of token addresses
                         * @return aprs: array of aprs (ordered in respect to the `addresses` array)
                         */
                        function getAPRs()
                            external
                            view
                            returns (address[] memory addresses, uint256[] memory aprs);
                        // external
                        // We should save the amount one has deposited to calc interests
                        /**
                         * Used to mint IdleTokens, given an underlying amount (eg. DAI).
                         * This method triggers a rebalance of the pools if needed
                         * NOTE: User should 'approve' _amount of tokens before calling mintIdleToken
                         * NOTE 2: this method can be paused
                         *
                         * @param _amount : amount of underlying token to be lended
                         * @param _skipRebalance : flag for skipping rebalance for lower gas price
                         * @param _referral : referral address
                         * @return mintedTokens : amount of IdleTokens minted
                         */
                        function mintIdleToken(
                            uint256 _amount,
                            bool _skipRebalance,
                            address _referral
                        ) external returns (uint256 mintedTokens);
                        /**
                         * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                         * This method triggers a rebalance of the pools if needed
                         * NOTE: If the contract is paused or iToken price has decreased one can still redeem but no rebalance happens.
                         * NOTE 2: If iToken price has decresed one should not redeem (but can do it) otherwise he would capitalize the loss.
                         *         Ideally one should wait until the black swan event is terminated
                         *
                         * @param _amount : amount of IdleTokens to be burned
                         * @return redeemedTokens : amount of underlying tokens redeemed
                         */
                        function redeemIdleToken(uint256 _amount)
                            external
                            returns (uint256 redeemedTokens);
                        /**
                         * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                         * and send interest-bearing tokens (eg. cDAI/iDAI) directly to the user.
                         * Underlying (eg. DAI) is not redeemed here.
                         *
                         * @param _amount : amount of IdleTokens to be burned
                         */
                        function redeemInterestBearingTokens(uint256 _amount) external;
                        /**
                         * @return : whether has rebalanced or not
                         */
                        function rebalance() external returns (bool);
                    }
                    pragma solidity >=0.5.0;
                    interface IUniswapV2Router01 {
                        function factory() external pure returns (address);
                        function WETH() external pure returns (address);
                        function addLiquidity(
                            address tokenA,
                            address tokenB,
                            uint256 amountADesired,
                            uint256 amountBDesired,
                            uint256 amountAMin,
                            uint256 amountBMin,
                            address to,
                            uint256 deadline
                        )
                            external
                            returns (
                                uint256 amountA,
                                uint256 amountB,
                                uint256 liquidity
                            );
                        function addLiquidityETH(
                            address token,
                            uint256 amountTokenDesired,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline
                        )
                            external
                            payable
                            returns (
                                uint256 amountToken,
                                uint256 amountETH,
                                uint256 liquidity
                            );
                        function removeLiquidity(
                            address tokenA,
                            address tokenB,
                            uint256 liquidity,
                            uint256 amountAMin,
                            uint256 amountBMin,
                            address to,
                            uint256 deadline
                        ) external returns (uint256 amountA, uint256 amountB);
                        function removeLiquidityETH(
                            address token,
                            uint256 liquidity,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline
                        ) external returns (uint256 amountToken, uint256 amountETH);
                        function removeLiquidityWithPermit(
                            address tokenA,
                            address tokenB,
                            uint256 liquidity,
                            uint256 amountAMin,
                            uint256 amountBMin,
                            address to,
                            uint256 deadline,
                            bool approveMax,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) external returns (uint256 amountA, uint256 amountB);
                        function removeLiquidityETHWithPermit(
                            address token,
                            uint256 liquidity,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline,
                            bool approveMax,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) external returns (uint256 amountToken, uint256 amountETH);
                        function swapExactTokensForTokens(
                            uint256 amountIn,
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapTokensForExactTokens(
                            uint256 amountOut,
                            uint256 amountInMax,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapExactETHForTokens(
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external payable returns (uint256[] memory amounts);
                        function swapTokensForExactETH(
                            uint256 amountOut,
                            uint256 amountInMax,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapExactTokensForETH(
                            uint256 amountIn,
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapETHForExactTokens(
                            uint256 amountOut,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external payable returns (uint256[] memory amounts);
                        function quote(
                            uint256 amountA,
                            uint256 reserveA,
                            uint256 reserveB
                        ) external pure returns (uint256 amountB);
                        function getAmountOut(
                            uint256 amountIn,
                            uint256 reserveIn,
                            uint256 reserveOut
                        ) external pure returns (uint256 amountOut);
                        function getAmountIn(
                            uint256 amountOut,
                            uint256 reserveIn,
                            uint256 reserveOut
                        ) external pure returns (uint256 amountIn);
                        function getAmountsOut(uint256 amountIn, address[] calldata path)
                            external
                            view
                            returns (uint256[] memory amounts);
                        function getAmountsIn(uint256 amountOut, address[] calldata path)
                            external
                            view
                            returns (uint256[] memory amounts);
                    }
                    pragma solidity >=0.5.0;
                    import "./IUniswapV2Router01.sol";
                    interface IUniswapV2Router02 {
                        function factory() external pure returns (address);
                        function WETH() external pure returns (address);
                        function addLiquidity(
                            address tokenA,
                            address tokenB,
                            uint256 amountADesired,
                            uint256 amountBDesired,
                            uint256 amountAMin,
                            uint256 amountBMin,
                            address to,
                            uint256 deadline
                        )
                            external
                            returns (
                                uint256 amountA,
                                uint256 amountB,
                                uint256 liquidity
                            );
                        function addLiquidityETH(
                            address token,
                            uint256 amountTokenDesired,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline
                        )
                            external
                            payable
                            returns (
                                uint256 amountToken,
                                uint256 amountETH,
                                uint256 liquidity
                            );
                        function removeLiquidity(
                            address tokenA,
                            address tokenB,
                            uint256 liquidity,
                            uint256 amountAMin,
                            uint256 amountBMin,
                            address to,
                            uint256 deadline
                        ) external returns (uint256 amountA, uint256 amountB);
                        function removeLiquidityETH(
                            address token,
                            uint256 liquidity,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline
                        ) external returns (uint256 amountToken, uint256 amountETH);
                        function removeLiquidityWithPermit(
                            address tokenA,
                            address tokenB,
                            uint256 liquidity,
                            uint256 amountAMin,
                            uint256 amountBMin,
                            address to,
                            uint256 deadline,
                            bool approveMax,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) external returns (uint256 amountA, uint256 amountB);
                        function removeLiquidityETHWithPermit(
                            address token,
                            uint256 liquidity,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline,
                            bool approveMax,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) external returns (uint256 amountToken, uint256 amountETH);
                        function swapExactTokensForTokens(
                            uint256 amountIn,
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapTokensForExactTokens(
                            uint256 amountOut,
                            uint256 amountInMax,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapExactETHForTokens(
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external payable returns (uint256[] memory amounts);
                        function swapTokensForExactETH(
                            uint256 amountOut,
                            uint256 amountInMax,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapExactTokensForETH(
                            uint256 amountIn,
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external returns (uint256[] memory amounts);
                        function swapETHForExactTokens(
                            uint256 amountOut,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external payable returns (uint256[] memory amounts);
                        function quote(
                            uint256 amountA,
                            uint256 reserveA,
                            uint256 reserveB
                        ) external pure returns (uint256 amountB);
                        function getAmountOut(
                            uint256 amountIn,
                            uint256 reserveIn,
                            uint256 reserveOut
                        ) external pure returns (uint256 amountOut);
                        function getAmountIn(
                            uint256 amountOut,
                            uint256 reserveIn,
                            uint256 reserveOut
                        ) external pure returns (uint256 amountIn);
                        function getAmountsOut(uint256 amountIn, address[] calldata path)
                            external
                            view
                            returns (uint256[] memory amounts);
                        function getAmountsIn(uint256 amountOut, address[] calldata path)
                            external
                            view
                            returns (uint256[] memory amounts);
                        function removeLiquidityETHSupportingFeeOnTransferTokens(
                            address token,
                            uint256 liquidity,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline
                        ) external returns (uint256 amountETH);
                        function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
                            address token,
                            uint256 liquidity,
                            uint256 amountTokenMin,
                            uint256 amountETHMin,
                            address to,
                            uint256 deadline,
                            bool approveMax,
                            uint8 v,
                            bytes32 r,
                            bytes32 s
                        ) external returns (uint256 amountETH);
                        function swapExactTokensForTokensSupportingFeeOnTransferTokens(
                            uint256 amountIn,
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external;
                        function swapExactETHForTokensSupportingFeeOnTransferTokens(
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external payable;
                        function swapExactTokensForETHSupportingFeeOnTransferTokens(
                            uint256 amountIn,
                            uint256 amountOutMin,
                            address[] calldata path,
                            address to,
                            uint256 deadline
                        ) external;
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @dev Standard math utilities missing in the Solidity language.
                     */
                    library Math {
                        /**
                         * @dev Returns the largest of two numbers.
                         */
                        function max(uint256 a, uint256 b) internal pure returns (uint256) {
                            return a >= b ? a : b;
                        }
                        /**
                         * @dev Returns the smallest of two numbers.
                         */
                        function min(uint256 a, uint256 b) internal pure returns (uint256) {
                            return a < b ? a : b;
                        }
                        /**
                         * @dev Returns the average of two numbers. The result is rounded towards
                         * zero.
                         */
                        function average(uint256 a, uint256 b) internal pure returns (uint256) {
                            // (a + b) / 2 can overflow, so we distribute
                            return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
                        }
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                            return c;
                        }
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            return sub(a, b, "SafeMath: subtraction overflow");
                        }
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         *
                         * _Available since v2.4.0._
                         */
                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b <= a, errorMessage);
                            uint256 c = a - b;
                            return c;
                        }
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                            if (a == 0) {
                                return 0;
                            }
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                            return c;
                        }
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            return div(a, b, "SafeMath: division by zero");
                        }
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            // Solidity only automatically asserts when dividing by 0
                            require(b > 0, errorMessage);
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                            return c;
                        }
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            return mod(a, b, "SafeMath: modulo by zero");
                        }
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts with custom message when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b != 0, errorMessage);
                            return a % b;
                        }
                    }
                    pragma solidity ^0.5.0;
                    import "./IERC20.sol";
                    /**
                     * @dev Optional functions from the ERC20 standard.
                     */
                    contract ERC20Detailed is IERC20 {
                        string private _name;
                        string private _symbol;
                        uint8 private _decimals;
                        /**
                         * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
                         * these values are immutable: they can only be set once during
                         * construction.
                         */
                        constructor (string memory name, string memory symbol, uint8 decimals) public {
                            _name = name;
                            _symbol = symbol;
                            _decimals = decimals;
                        }
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() public view returns (string memory) {
                            return _name;
                        }
                        /**
                         * @dev Returns the symbol of the token, usually a shorter version of the
                         * name.
                         */
                        function symbol() public view returns (string memory) {
                            return _symbol;
                        }
                        /**
                         * @dev Returns the number of decimals used to get its user representation.
                         * For example, if `decimals` equals `2`, a balance of `505` tokens should
                         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                         *
                         * Tokens usually opt for a value of 18, imitating the relationship between
                         * Ether and Wei.
                         *
                         * NOTE: This information is only used for _display_ purposes: it in
                         * no way affects any of the arithmetic of the contract, including
                         * {IERC20-balanceOf} and {IERC20-transfer}.
                         */
                        function decimals() public view returns (uint8) {
                            return _decimals;
                        }
                    }
                    pragma solidity ^0.5.0;
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
                     * the optional functions; to access them see {ERC20Detailed}.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to {approve}. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                    }
                    pragma solidity ^0.5.0;
                    import "./IERC20.sol";
                    import "../../math/SafeMath.sol";
                    import "../../utils/Address.sol";
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                     * contract returns false). Tokens that return no value (and instead revert or
                     * throw on failure) are also supported, non-reverting calls are assumed to be
                     * successful.
                     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                     */
                    library SafeERC20 {
                        using SafeMath for uint256;
                        using Address for address;
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                        /**
                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                         * @param token The token targeted by the call.
                         * @param data The call data (encoded using abi.encode or one of its variants).
                         */
                        function callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves.
                            // A Solidity high level call has three parts:
                            //  1. The target address is checked to verify it contains contract code
                            //  2. The call itself is made, and success asserted
                            //  3. The return value is decoded, which in turn checks the size of the returned data.
                            // solhint-disable-next-line max-line-length
                            require(address(token).isContract(), "SafeERC20: call to non-contract");
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = address(token).call(data);
                            require(success, "SafeERC20: low-level call failed");
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    pragma solidity ^0.5.5;
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following 
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                            // for accounts without code, i.e. `keccak256('')`
                            bytes32 codehash;
                            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { codehash := extcodehash(account) }
                            return (codehash != accountHash && codehash != 0x0);
                        }
                        /**
                         * @dev Converts an `address` into `address payable`. Note that this is
                         * simply a type cast: the actual underlying value is not changed.
                         *
                         * _Available since v2.4.0._
                         */
                        function toPayable(address account) internal pure returns (address payable) {
                            return address(uint160(account));
                        }
                        /**
                         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                         * `recipient`, forwarding all available gas and reverting on errors.
                         *
                         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                         * of certain opcodes, possibly making contracts go over the 2300 gas limit
                         * imposed by `transfer`, making them unable to receive funds via
                         * `transfer`. {sendValue} removes this limitation.
                         *
                         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                         *
                         * IMPORTANT: because control is transferred to `recipient`, care must be
                         * taken to not create reentrancy vulnerabilities. Consider using
                         * {ReentrancyGuard} or the
                         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         *
                         * _Available since v2.4.0._
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                            // solhint-disable-next-line avoid-call-value
                            (bool success, ) = recipient.call.value(amount)("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                    }
                    

                    File 5 of 11: IdleTokenHelper
                    // File: @openzeppelin/contracts/math/SafeMath.sol
                    
                    pragma solidity ^0.6.0;
                    
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         *
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         *
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            return sub(a, b, "SafeMath: subtraction overflow");
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         *
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b <= a, errorMessage);
                            uint256 c = a - b;
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         *
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                            if (a == 0) {
                                return 0;
                            }
                    
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         *
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            return div(a, b, "SafeMath: division by zero");
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         *
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b > 0, errorMessage);
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         *
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            return mod(a, b, "SafeMath: modulo by zero");
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts with custom message when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         *
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b != 0, errorMessage);
                            return a % b;
                        }
                    }
                    
                    // File: interfaces/Idle/IIdleTokenV3_1.sol
                    
                    pragma solidity 0.6.12;
                    
                    interface IIdleTokenV3_1 {
                        // view
                        /**
                         * IdleToken price calculation not considering fees, in underlying
                         *
                         * @return price : price in underlying token
                         */
                        function tokenPrice() external view returns (uint256 price);
                    
                        // view
                        /**
                         * Map which saves avg idleToken minting price per user
                         * Used in calculating redeem price
                         *
                         * @return price : price in underlying token
                         */
                        function userAvgPrices(address user) external view returns (uint256 price);
                    
                    
                        // view
                        /**
                         * Current fee on interest gained
                         *
                         * @return fee : fee on interest gained
                         */
                        function fee() external view returns (uint256 fee);
                    
                        /**
                         * @return underlying : underlying token address
                         */
                        function token() external view returns (address underlying);
                    
                        /**
                         * Get APR of every ILendingProtocol
                         *
                         * @return addresses : array of token addresses
                         * @return aprs : array of aprs (ordered in respect to the `addresses` array)
                         */
                        function getAPRs() external view returns (address[] memory addresses, uint256[] memory aprs);
                    
                        // external
                        // We should save the amount one has deposited to calc interests
                    
                        /**
                         * Used to mint IdleTokens, given an underlying amount (eg. DAI).
                         * This method triggers a rebalance of the pools if needed
                         * NOTE: User should 'approve' _amount of tokens before calling mintIdleToken
                         * NOTE 2: this method can be paused
                         *
                         * @param _amount : amount of underlying token to be lended
                         * @param _skipRebalance : flag for skipping rebalance for lower gas price
                         * @param _referral : referral address
                         * @return mintedTokens : amount of IdleTokens minted
                         */
                        function mintIdleToken(uint256 _amount, bool _skipRebalance, address _referral) external returns (uint256 mintedTokens);
                    
                        /**
                         * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                         * This method triggers a rebalance of the pools if needed
                         * NOTE: If the contract is paused or iToken price has decreased one can still redeem but no rebalance happens.
                         * NOTE 2: If iToken price has decresed one should not redeem (but can do it) otherwise he would capitalize the loss.
                         *         Ideally one should wait until the black swan event is terminated
                         *
                         * @param _amount : amount of IdleTokens to be burned
                         * @return redeemedTokens : amount of underlying tokens redeemed
                         */
                        function redeemIdleToken(uint256 _amount) external returns (uint256 redeemedTokens);
                        /**
                         * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                         * and send interest-bearing tokens (eg. cDAI/iDAI) directly to the user.
                         * Underlying (eg. DAI) is not redeemed here.
                         *
                         * @param _amount : amount of IdleTokens to be burned
                         */
                        function redeemInterestBearingTokens(uint256 _amount) external;
                    
                        /**
                         * @return : whether has rebalanced or not
                         */
                        function rebalance() external returns (bool);
                    }
                    
                    // File: contracts/IdleTokenHelper.sol
                    
                    // SPDX-License-Identifier: AGPL-3.0
                    pragma solidity ^0.6.12;
                    
                    
                    
                    contract IdleTokenHelper {
                        using SafeMath for uint256;
                    
                        uint256 constant public FULL_ALLOC = 100000;
                    
                        function getMintingPrice(address idleYieldToken) view external returns (uint256 mintingPrice) {
                            return _getMintingPrice(idleYieldToken);
                        }
                    
                        function _getMintingPrice(address idleYieldToken) view internal returns (uint256 mintingPrice) {
                            return IIdleTokenV3_1(idleYieldToken).tokenPrice();
                        }
                    
                        function getRedeemPrice(address idleYieldToken) view external returns (uint256 redeemPrice) {
                            return _getRedeemPrice(idleYieldToken, msg.sender);
                        }
                    
                        function getRedeemPrice(address idleYieldToken, address user) view external returns (uint256 redeemPrice) {
                            return _getRedeemPrice(idleYieldToken, user);
                        }
                    
                        function _getRedeemPrice(address idleYieldToken, address user) view internal returns (uint256 redeemPrice) {
                            /*
                             *  As per https://github.com/Idle-Labs/idle-contracts/blob/ad0f18fef670ea6a4030fe600f64ece3d3ac2202/contracts/IdleTokenGovernance.sol#L878-L900
                             *
                             *  Price on minting is currentPrice
                             *  Price on redeem must consider the fee
                             *
                             *  Below the implementation of the following redeemPrice formula
                             *
                             *  redeemPrice := underlyingAmount/idleTokenAmount
                             *
                             *  redeemPrice = currentPrice * (1 - scaledFee * ΔP%)
                             *
                             *  where:
                             *  - scaledFee   := fee/FULL_ALLOC
                             *  - ΔP% := 0 when currentPrice < userAvgPrice (no gain) and (currentPrice-userAvgPrice)/currentPrice
                             *
                             *  n.b: gain := idleTokenAmount * ΔP% * currentPrice
                             */
                    
                            IIdleTokenV3_1 iyt = IIdleTokenV3_1(idleYieldToken);
                    
                            uint256 userAvgPrice = iyt.userAvgPrices(user);
                            uint256 currentPrice = iyt.tokenPrice();
                    
                            // When no deposits userAvgPrice is 0 equiv currentPrice
                            // and in the case of issues
                            if (userAvgPrice == 0 || currentPrice < userAvgPrice) {
                                redeemPrice = currentPrice;
                            } else {
                                uint256 fee = iyt.fee();
                    
                                redeemPrice = ((currentPrice.mul(FULL_ALLOC))
                                    .sub(
                                        fee.mul(
                                             currentPrice.sub(userAvgPrice)
                                        )
                                    )).div(FULL_ALLOC);
                            }
                    
                            return redeemPrice;
                        }
                    }

                    File 6 of 11: AdminUpgradeabilityProxy
                    // File: @openzeppelin/upgrades/contracts/upgradeability/Proxy.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @title Proxy
                     * @dev Implements delegation of calls to other contracts, with proper
                     * forwarding of return values and bubbling of failures.
                     * It defines a fallback function that delegates all calls to the address
                     * returned by the abstract _implementation() internal function.
                     */
                    contract Proxy {
                      /**
                       * @dev Fallback function.
                       * Implemented entirely in `_fallback`.
                       */
                      function () payable external {
                        _fallback();
                      }
                    
                      /**
                       * @return The Address of the implementation.
                       */
                      function _implementation() internal view returns (address);
                    
                      /**
                       * @dev Delegates execution to an implementation contract.
                       * This is a low level function that doesn't return to its internal call site.
                       * It will return to the external caller whatever the implementation returns.
                       * @param implementation Address to delegate.
                       */
                      function _delegate(address implementation) internal {
                        assembly {
                          // Copy msg.data. We take full control of memory in this inline assembly
                          // block because it will not return to Solidity code. We overwrite the
                          // Solidity scratch pad at memory position 0.
                          calldatacopy(0, 0, calldatasize)
                    
                          // Call the implementation.
                          // out and outsize are 0 because we don't know the size yet.
                          let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
                    
                          // Copy the returned data.
                          returndatacopy(0, 0, returndatasize)
                    
                          switch result
                          // delegatecall returns 0 on error.
                          case 0 { revert(0, returndatasize) }
                          default { return(0, returndatasize) }
                        }
                      }
                    
                      /**
                       * @dev Function that is run as the first thing in the fallback function.
                       * Can be redefined in derived contracts to add functionality.
                       * Redefinitions must call super._willFallback().
                       */
                      function _willFallback() internal {
                      }
                    
                      /**
                       * @dev fallback implementation.
                       * Extracted to enable manual triggering.
                       */
                      function _fallback() internal {
                        _willFallback();
                        _delegate(_implementation());
                      }
                    }
                    
                    // File: @openzeppelin/upgrades/contracts/utils/Address.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * Utility library of inline functions on addresses
                     *
                     * Source https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-solidity/v2.1.3/contracts/utils/Address.sol
                     * This contract is copied here and renamed from the original to avoid clashes in the compiled artifacts
                     * when the user imports a zos-lib contract (that transitively causes this contract to be compiled and added to the
                     * build/artifacts folder) as well as the vanilla Address implementation from an openzeppelin version.
                     */
                    library OpenZeppelinUpgradesAddress {
                        /**
                         * Returns whether the target address is a contract
                         * @dev This function will return false if invoked during the constructor of a contract,
                         * as the code is not actually created until after the constructor finishes.
                         * @param account address of the account to check
                         * @return whether the target address is a contract
                         */
                        function isContract(address account) internal view returns (bool) {
                            uint256 size;
                            // XXX Currently there is no better way to check if there is a contract in an address
                            // than to check the size of the code at that address.
                            // See https://ethereum.stackexchange.com/a/14016/36603
                            // for more details about how this works.
                            // TODO Check this again before the Serenity release, because all addresses will be
                            // contracts then.
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                    }
                    
                    // File: @openzeppelin/upgrades/contracts/upgradeability/BaseUpgradeabilityProxy.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    /**
                     * @title BaseUpgradeabilityProxy
                     * @dev This contract implements a proxy that allows to change the
                     * implementation address to which it will delegate.
                     * Such a change is called an implementation upgrade.
                     */
                    contract BaseUpgradeabilityProxy is Proxy {
                      /**
                       * @dev Emitted when the implementation is upgraded.
                       * @param implementation Address of the new implementation.
                       */
                      event Upgraded(address indexed implementation);
                    
                      /**
                       * @dev Storage slot with the address of the current implementation.
                       * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                       * validated in the constructor.
                       */
                      bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                    
                      /**
                       * @dev Returns the current implementation.
                       * @return Address of the current implementation
                       */
                      function _implementation() internal view returns (address impl) {
                        bytes32 slot = IMPLEMENTATION_SLOT;
                        assembly {
                          impl := sload(slot)
                        }
                      }
                    
                      /**
                       * @dev Upgrades the proxy to a new implementation.
                       * @param newImplementation Address of the new implementation.
                       */
                      function _upgradeTo(address newImplementation) internal {
                        _setImplementation(newImplementation);
                        emit Upgraded(newImplementation);
                      }
                    
                      /**
                       * @dev Sets the implementation address of the proxy.
                       * @param newImplementation Address of the new implementation.
                       */
                      function _setImplementation(address newImplementation) internal {
                        require(OpenZeppelinUpgradesAddress.isContract(newImplementation), "Cannot set a proxy implementation to a non-contract address");
                    
                        bytes32 slot = IMPLEMENTATION_SLOT;
                    
                        assembly {
                          sstore(slot, newImplementation)
                        }
                      }
                    }
                    
                    // File: @openzeppelin/upgrades/contracts/upgradeability/UpgradeabilityProxy.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    /**
                     * @title UpgradeabilityProxy
                     * @dev Extends BaseUpgradeabilityProxy with a constructor for initializing
                     * implementation and init data.
                     */
                    contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
                      /**
                       * @dev Contract constructor.
                       * @param _logic Address of the initial implementation.
                       * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                       */
                      constructor(address _logic, bytes memory _data) public payable {
                        assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1));
                        _setImplementation(_logic);
                        if(_data.length > 0) {
                          (bool success,) = _logic.delegatecall(_data);
                          require(success);
                        }
                      }  
                    }
                    
                    // File: @openzeppelin/upgrades/contracts/upgradeability/BaseAdminUpgradeabilityProxy.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    /**
                     * @title BaseAdminUpgradeabilityProxy
                     * @dev This contract combines an upgradeability proxy with an authorization
                     * mechanism for administrative tasks.
                     * All external functions in this contract must be guarded by the
                     * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                     * feature proposal that would enable this to be done automatically.
                     */
                    contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
                      /**
                       * @dev Emitted when the administration has been transferred.
                       * @param previousAdmin Address of the previous admin.
                       * @param newAdmin Address of the new admin.
                       */
                      event AdminChanged(address previousAdmin, address newAdmin);
                    
                      /**
                       * @dev Storage slot with the admin of the contract.
                       * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                       * validated in the constructor.
                       */
                    
                      bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                    
                      /**
                       * @dev Modifier to check whether the `msg.sender` is the admin.
                       * If it is, it will run the function. Otherwise, it will delegate the call
                       * to the implementation.
                       */
                      modifier ifAdmin() {
                        if (msg.sender == _admin()) {
                          _;
                        } else {
                          _fallback();
                        }
                      }
                    
                      /**
                       * @return The address of the proxy admin.
                       */
                      function admin() external ifAdmin returns (address) {
                        return _admin();
                      }
                    
                      /**
                       * @return The address of the implementation.
                       */
                      function implementation() external ifAdmin returns (address) {
                        return _implementation();
                      }
                    
                      /**
                       * @dev Changes the admin of the proxy.
                       * Only the current admin can call this function.
                       * @param newAdmin Address to transfer proxy administration to.
                       */
                      function changeAdmin(address newAdmin) external ifAdmin {
                        require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                        emit AdminChanged(_admin(), newAdmin);
                        _setAdmin(newAdmin);
                      }
                    
                      /**
                       * @dev Upgrade the backing implementation of the proxy.
                       * Only the admin can call this function.
                       * @param newImplementation Address of the new implementation.
                       */
                      function upgradeTo(address newImplementation) external ifAdmin {
                        _upgradeTo(newImplementation);
                      }
                    
                      /**
                       * @dev Upgrade the backing implementation of the proxy and call a function
                       * on the new implementation.
                       * This is useful to initialize the proxied contract.
                       * @param newImplementation Address of the new implementation.
                       * @param data Data to send as msg.data in the low level call.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       */
                      function upgradeToAndCall(address newImplementation, bytes calldata data) payable external ifAdmin {
                        _upgradeTo(newImplementation);
                        (bool success,) = newImplementation.delegatecall(data);
                        require(success);
                      }
                    
                      /**
                       * @return The admin slot.
                       */
                      function _admin() internal view returns (address adm) {
                        bytes32 slot = ADMIN_SLOT;
                        assembly {
                          adm := sload(slot)
                        }
                      }
                    
                      /**
                       * @dev Sets the address of the proxy admin.
                       * @param newAdmin Address of the new proxy admin.
                       */
                      function _setAdmin(address newAdmin) internal {
                        bytes32 slot = ADMIN_SLOT;
                    
                        assembly {
                          sstore(slot, newAdmin)
                        }
                      }
                    
                      /**
                       * @dev Only fall back when the sender is not the admin.
                       */
                      function _willFallback() internal {
                        require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                        super._willFallback();
                      }
                    }
                    
                    // File: @openzeppelin/upgrades/contracts/upgradeability/AdminUpgradeabilityProxy.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    /**
                     * @title AdminUpgradeabilityProxy
                     * @dev Extends from BaseAdminUpgradeabilityProxy with a constructor for 
                     * initializing the implementation, admin, and init data.
                     */
                    contract AdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, UpgradeabilityProxy {
                      /**
                       * Contract constructor.
                       * @param _logic address of the initial implementation.
                       * @param _admin Address of the proxy administrator.
                       * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                       */
                      constructor(address _logic, address _admin, bytes memory _data) UpgradeabilityProxy(_logic, _data) public payable {
                        assert(ADMIN_SLOT == bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1));
                        _setAdmin(_admin);
                      }
                    }

                    File 7 of 11: IdleTokenGovernance
                    // File: @openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            return sub(a, b, "SafeMath: subtraction overflow");
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         *
                         * _Available since v2.4.0._
                         */
                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b <= a, errorMessage);
                            uint256 c = a - b;
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                            if (a == 0) {
                                return 0;
                            }
                    
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            return div(a, b, "SafeMath: division by zero");
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            // Solidity only automatically asserts when dividing by 0
                            require(b > 0, errorMessage);
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            return mod(a, b, "SafeMath: modulo by zero");
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts with custom message when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b != 0, errorMessage);
                            return a % b;
                        }
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/IERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
                     * the optional functions; to access them see {ERC20Detailed}.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                    
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                    
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                    
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                    
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to {approve}. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/utils/Address.sol
                    
                    pragma solidity ^0.5.5;
                    
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * [IMPORTANT]
                         * ====
                         * It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         *
                         * Among others, `isContract` will return false for the following 
                         * types of addresses:
                         *
                         *  - an externally-owned account
                         *  - a contract in construction
                         *  - an address where a contract will be created
                         *  - an address where a contract lived, but was destroyed
                         * ====
                         */
                        function isContract(address account) internal view returns (bool) {
                            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                            // for accounts without code, i.e. `keccak256('')`
                            bytes32 codehash;
                            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { codehash := extcodehash(account) }
                            return (codehash != accountHash && codehash != 0x0);
                        }
                    
                        /**
                         * @dev Converts an `address` into `address payable`. Note that this is
                         * simply a type cast: the actual underlying value is not changed.
                         *
                         * _Available since v2.4.0._
                         */
                        function toPayable(address account) internal pure returns (address payable) {
                            return address(uint160(account));
                        }
                    
                        /**
                         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                         * `recipient`, forwarding all available gas and reverting on errors.
                         *
                         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                         * of certain opcodes, possibly making contracts go over the 2300 gas limit
                         * imposed by `transfer`, making them unable to receive funds via
                         * `transfer`. {sendValue} removes this limitation.
                         *
                         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                         *
                         * IMPORTANT: because control is transferred to `recipient`, care must be
                         * taken to not create reentrancy vulnerabilities. Consider using
                         * {ReentrancyGuard} or the
                         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         *
                         * _Available since v2.4.0._
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                    
                            // solhint-disable-next-line avoid-call-value
                            (bool success, ) = recipient.call.value(amount)("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/SafeERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                     * contract returns false). Tokens that return no value (and instead revert or
                     * throw on failure) are also supported, non-reverting calls are assumed to be
                     * successful.
                     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                     */
                    library SafeERC20 {
                        using SafeMath for uint256;
                        using Address for address;
                    
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                    
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                    
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                    
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        /**
                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                         * @param token The token targeted by the call.
                         * @param data The call data (encoded using abi.encode or one of its variants).
                         */
                        function callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves.
                    
                            // A Solidity high level call has three parts:
                            //  1. The target address is checked to verify it contains contract code
                            //  2. The call itself is made, and success asserted
                            //  3. The return value is decoded, which in turn checks the size of the returned data.
                            // solhint-disable-next-line max-line-length
                            require(address(token).isContract(), "SafeERC20: call to non-contract");
                    
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = address(token).call(data);
                            require(success, "SafeERC20: low-level call failed");
                    
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    
                    // File: @openzeppelin/upgrades/contracts/Initializable.sol
                    
                    pragma solidity >=0.4.24 <0.7.0;
                    
                    
                    /**
                     * @title Initializable
                     *
                     * @dev Helper contract to support initializer functions. To use it, replace
                     * the constructor with a function that has the `initializer` modifier.
                     * WARNING: Unlike constructors, initializer functions must be manually
                     * invoked. This applies both to deploying an Initializable contract, as well
                     * as extending an Initializable contract via inheritance.
                     * WARNING: When used with inheritance, manual care must be taken to not invoke
                     * a parent initializer twice, or ensure that all initializers are idempotent,
                     * because this is not dealt with automatically as with constructors.
                     */
                    contract Initializable {
                    
                      /**
                       * @dev Indicates that the contract has been initialized.
                       */
                      bool private initialized;
                    
                      /**
                       * @dev Indicates that the contract is in the process of being initialized.
                       */
                      bool private initializing;
                    
                      /**
                       * @dev Modifier to use in the initializer function of a contract.
                       */
                      modifier initializer() {
                        require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
                    
                        bool isTopLevelCall = !initializing;
                        if (isTopLevelCall) {
                          initializing = true;
                          initialized = true;
                        }
                    
                        _;
                    
                        if (isTopLevelCall) {
                          initializing = false;
                        }
                      }
                    
                      /// @dev Returns true if and only if the function is running in the constructor
                      function isConstructor() private view returns (bool) {
                        // extcodesize checks the size of the code stored in an address, and
                        // address returns the current address. Since the code is still not
                        // deployed when running a constructor, any checks on its code size will
                        // yield zero, making it an effective way to detect if a contract is
                        // under construction or not.
                        address self = address(this);
                        uint256 cs;
                        assembly { cs := extcodesize(self) }
                        return cs == 0;
                      }
                    
                      // Reserved storage space to allow for layout changes in the future.
                      uint256[50] private ______gap;
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/GSN/Context.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with GSN meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    contract Context is Initializable {
                        // Empty internal constructor, to prevent people from mistakenly deploying
                        // an instance of this contract, which should be used via inheritance.
                        constructor () internal { }
                        // solhint-disable-previous-line no-empty-blocks
                    
                        function _msgSender() internal view returns (address payable) {
                            return msg.sender;
                        }
                    
                        function _msgData() internal view returns (bytes memory) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    
                    
                    /**
                     * @dev Implementation of the {IERC20} interface.
                     *
                     * This implementation is agnostic to the way tokens are created. This means
                     * that a supply mechanism has to be added in a derived contract using {_mint}.
                     * For a generic mechanism see {ERC20Mintable}.
                     *
                     * TIP: For a detailed writeup see our guide
                     * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
                     * to implement supply mechanisms].
                     *
                     * We have followed general OpenZeppelin guidelines: functions revert instead
                     * of returning `false` on failure. This behavior is nonetheless conventional
                     * and does not conflict with the expectations of ERC20 applications.
                     *
                     * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
                     * This allows applications to reconstruct the allowance for all accounts just
                     * by listening to said events. Other implementations of the EIP may not emit
                     * these events, as it isn't required by the specification.
                     *
                     * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
                     * functions have been added to mitigate the well-known issues around setting
                     * allowances. See {IERC20-approve}.
                     */
                    contract ERC20 is Initializable, Context, IERC20 {
                        using SafeMath for uint256;
                    
                        mapping (address => uint256) private _balances;
                    
                        mapping (address => mapping (address => uint256)) private _allowances;
                    
                        uint256 private _totalSupply;
                    
                        /**
                         * @dev See {IERC20-totalSupply}.
                         */
                        function totalSupply() public view returns (uint256) {
                            return _totalSupply;
                        }
                    
                        /**
                         * @dev See {IERC20-balanceOf}.
                         */
                        function balanceOf(address account) public view returns (uint256) {
                            return _balances[account];
                        }
                    
                        /**
                         * @dev See {IERC20-transfer}.
                         *
                         * Requirements:
                         *
                         * - `recipient` cannot be the zero address.
                         * - the caller must have a balance of at least `amount`.
                         */
                        function transfer(address recipient, uint256 amount) public returns (bool) {
                            _transfer(_msgSender(), recipient, amount);
                            return true;
                        }
                    
                        /**
                         * @dev See {IERC20-allowance}.
                         */
                        function allowance(address owner, address spender) public view returns (uint256) {
                            return _allowances[owner][spender];
                        }
                    
                        /**
                         * @dev See {IERC20-approve}.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function approve(address spender, uint256 amount) public returns (bool) {
                            _approve(_msgSender(), spender, amount);
                            return true;
                        }
                    
                        /**
                         * @dev See {IERC20-transferFrom}.
                         *
                         * Emits an {Approval} event indicating the updated allowance. This is not
                         * required by the EIP. See the note at the beginning of {ERC20};
                         *
                         * Requirements:
                         * - `sender` and `recipient` cannot be the zero address.
                         * - `sender` must have a balance of at least `amount`.
                         * - the caller must have allowance for `sender`'s tokens of at least
                         * `amount`.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
                            _transfer(sender, recipient, amount);
                            _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
                            return true;
                        }
                    
                        /**
                         * @dev Atomically increases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to {approve} that can be used as a mitigation for
                         * problems described in {IERC20-approve}.
                         *
                         * Emits an {Approval} event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
                            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(addedValue));
                            return true;
                        }
                    
                        /**
                         * @dev Atomically decreases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to {approve} that can be used as a mitigation for
                         * problems described in {IERC20-approve}.
                         *
                         * Emits an {Approval} event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         * - `spender` must have allowance for the caller of at least
                         * `subtractedValue`.
                         */
                        function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
                            _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
                            return true;
                        }
                    
                        /**
                         * @dev Moves tokens `amount` from `sender` to `recipient`.
                         *
                         * This is internal function is equivalent to {transfer}, and can be used to
                         * e.g. implement automatic token fees, slashing mechanisms, etc.
                         *
                         * Emits a {Transfer} event.
                         *
                         * Requirements:
                         *
                         * - `sender` cannot be the zero address.
                         * - `recipient` cannot be the zero address.
                         * - `sender` must have a balance of at least `amount`.
                         */
                        function _transfer(address sender, address recipient, uint256 amount) internal {
                            require(sender != address(0), "ERC20: transfer from the zero address");
                            require(recipient != address(0), "ERC20: transfer to the zero address");
                    
                            _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
                            _balances[recipient] = _balances[recipient].add(amount);
                            emit Transfer(sender, recipient, amount);
                        }
                    
                        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                         * the total supply.
                         *
                         * Emits a {Transfer} event with `from` set to the zero address.
                         *
                         * Requirements
                         *
                         * - `to` cannot be the zero address.
                         */
                        function _mint(address account, uint256 amount) internal {
                            require(account != address(0), "ERC20: mint to the zero address");
                    
                            _totalSupply = _totalSupply.add(amount);
                            _balances[account] = _balances[account].add(amount);
                            emit Transfer(address(0), account, amount);
                        }
                    
                        /**
                         * @dev Destroys `amount` tokens from `account`, reducing the
                         * total supply.
                         *
                         * Emits a {Transfer} event with `to` set to the zero address.
                         *
                         * Requirements
                         *
                         * - `account` cannot be the zero address.
                         * - `account` must have at least `amount` tokens.
                         */
                        function _burn(address account, uint256 amount) internal {
                            require(account != address(0), "ERC20: burn from the zero address");
                    
                            _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
                            _totalSupply = _totalSupply.sub(amount);
                            emit Transfer(account, address(0), amount);
                        }
                    
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
                         *
                         * This is internal function is equivalent to `approve`, and can be used to
                         * e.g. set automatic allowances for certain subsystems, etc.
                         *
                         * Emits an {Approval} event.
                         *
                         * Requirements:
                         *
                         * - `owner` cannot be the zero address.
                         * - `spender` cannot be the zero address.
                         */
                        function _approve(address owner, address spender, uint256 amount) internal {
                            require(owner != address(0), "ERC20: approve from the zero address");
                            require(spender != address(0), "ERC20: approve to the zero address");
                    
                            _allowances[owner][spender] = amount;
                            emit Approval(owner, spender, amount);
                        }
                    
                        /**
                         * @dev Destroys `amount` tokens from `account`.`amount` is then deducted
                         * from the caller's allowance.
                         *
                         * See {_burn} and {_approve}.
                         */
                        function _burnFrom(address account, uint256 amount) internal {
                            _burn(account, amount);
                            _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
                        }
                    
                        uint256[50] private ______gap;
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/token/ERC20/ERC20Detailed.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    /**
                     * @dev Optional functions from the ERC20 standard.
                     */
                    contract ERC20Detailed is Initializable, IERC20 {
                        string private _name;
                        string private _symbol;
                        uint8 private _decimals;
                    
                        /**
                         * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
                         * these values are immutable: they can only be set once during
                         * construction.
                         */
                        function initialize(string memory name, string memory symbol, uint8 decimals) public initializer {
                            _name = name;
                            _symbol = symbol;
                            _decimals = decimals;
                        }
                    
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() public view returns (string memory) {
                            return _name;
                        }
                    
                        /**
                         * @dev Returns the symbol of the token, usually a shorter version of the
                         * name.
                         */
                        function symbol() public view returns (string memory) {
                            return _symbol;
                        }
                    
                        /**
                         * @dev Returns the number of decimals used to get its user representation.
                         * For example, if `decimals` equals `2`, a balance of `505` tokens should
                         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                         *
                         * Tokens usually opt for a value of 18, imitating the relationship between
                         * Ether and Wei.
                         *
                         * NOTE: This information is only used for _display_ purposes: it in
                         * no way affects any of the arithmetic of the contract, including
                         * {IERC20-balanceOf} and {IERC20-transfer}.
                         */
                        function decimals() public view returns (uint8) {
                            return _decimals;
                        }
                    
                        uint256[50] private ______gap;
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/utils/ReentrancyGuard.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    /**
                     * @dev Contract module that helps prevent reentrant calls to a function.
                     *
                     * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
                     * available, which can be applied to functions to make sure there are no nested
                     * (reentrant) calls to them.
                     *
                     * Note that because there is a single `nonReentrant` guard, functions marked as
                     * `nonReentrant` may not call one another. This can be worked around by making
                     * those functions `private`, and then adding `external` `nonReentrant` entry
                     * points to them.
                     */
                    contract ReentrancyGuard is Initializable {
                        // counter to allow mutex lock with only one SSTORE operation
                        uint256 private _guardCounter;
                    
                        function initialize() public initializer {
                            // The counter starts at one to prevent changing it from zero to a non-zero
                            // value, which is a more expensive operation.
                            _guardCounter = 1;
                        }
                    
                        /**
                         * @dev Prevents a contract from calling itself, directly or indirectly.
                         * Calling a `nonReentrant` function from another `nonReentrant`
                         * function is not supported. It is possible to prevent this from happening
                         * by making the `nonReentrant` function external, and make it call a
                         * `private` function that does the actual work.
                         */
                        modifier nonReentrant() {
                            _guardCounter += 1;
                            uint256 localCounter = _guardCounter;
                            _;
                            require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
                        }
                    
                        uint256[50] private ______gap;
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/ownership/Ownable.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be aplied to your functions to restrict their use to
                     * the owner.
                     */
                    contract Ownable is Initializable, Context {
                        address private _owner;
                    
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                    
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        function initialize(address sender) public initializer {
                            _owner = sender;
                            emit OwnershipTransferred(address(0), _owner);
                        }
                    
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view returns (address) {
                            return _owner;
                        }
                    
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(isOwner(), "Ownable: caller is not the owner");
                            _;
                        }
                    
                        /**
                         * @dev Returns true if the caller is the current owner.
                         */
                        function isOwner() public view returns (bool) {
                            return _msgSender() == _owner;
                        }
                    
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * > Note: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public onlyOwner {
                            _transferOwnership(newOwner);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         */
                        function _transferOwnership(address newOwner) internal {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    
                        uint256[50] private ______gap;
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/access/Roles.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @title Roles
                     * @dev Library for managing addresses assigned to a Role.
                     */
                    library Roles {
                        struct Role {
                            mapping (address => bool) bearer;
                        }
                    
                        /**
                         * @dev Give an account access to this role.
                         */
                        function add(Role storage role, address account) internal {
                            require(!has(role, account), "Roles: account already has role");
                            role.bearer[account] = true;
                        }
                    
                        /**
                         * @dev Remove an account's access to this role.
                         */
                        function remove(Role storage role, address account) internal {
                            require(has(role, account), "Roles: account does not have role");
                            role.bearer[account] = false;
                        }
                    
                        /**
                         * @dev Check if an account has this role.
                         * @return bool
                         */
                        function has(Role storage role, address account) internal view returns (bool) {
                            require(account != address(0), "Roles: account is the zero address");
                            return role.bearer[account];
                        }
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/access/roles/PauserRole.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    
                    contract PauserRole is Initializable, Context {
                        using Roles for Roles.Role;
                    
                        event PauserAdded(address indexed account);
                        event PauserRemoved(address indexed account);
                    
                        Roles.Role private _pausers;
                    
                        function initialize(address sender) public initializer {
                            if (!isPauser(sender)) {
                                _addPauser(sender);
                            }
                        }
                    
                        modifier onlyPauser() {
                            require(isPauser(_msgSender()), "PauserRole: caller does not have the Pauser role");
                            _;
                        }
                    
                        function isPauser(address account) public view returns (bool) {
                            return _pausers.has(account);
                        }
                    
                        function addPauser(address account) public onlyPauser {
                            _addPauser(account);
                        }
                    
                        function renouncePauser() public {
                            _removePauser(_msgSender());
                        }
                    
                        function _addPauser(address account) internal {
                            _pausers.add(account);
                            emit PauserAdded(account);
                        }
                    
                        function _removePauser(address account) internal {
                            _pausers.remove(account);
                            emit PauserRemoved(account);
                        }
                    
                        uint256[50] private ______gap;
                    }
                    
                    // File: @openzeppelin/contracts-ethereum-package/contracts/lifecycle/Pausable.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    
                    /**
                     * @dev Contract module which allows children to implement an emergency stop
                     * mechanism that can be triggered by an authorized account.
                     *
                     * This module is used through inheritance. It will make available the
                     * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
                     * the functions of your contract. Note that they will not be pausable by
                     * simply including this module, only once the modifiers are put in place.
                     */
                    contract Pausable is Initializable, Context, PauserRole {
                        /**
                         * @dev Emitted when the pause is triggered by a pauser (`account`).
                         */
                        event Paused(address account);
                    
                        /**
                         * @dev Emitted when the pause is lifted by a pauser (`account`).
                         */
                        event Unpaused(address account);
                    
                        bool private _paused;
                    
                        /**
                         * @dev Initializes the contract in unpaused state. Assigns the Pauser role
                         * to the deployer.
                         */
                        function initialize(address sender) public initializer {
                            PauserRole.initialize(sender);
                    
                            _paused = false;
                        }
                    
                        /**
                         * @dev Returns true if the contract is paused, and false otherwise.
                         */
                        function paused() public view returns (bool) {
                            return _paused;
                        }
                    
                        /**
                         * @dev Modifier to make a function callable only when the contract is not paused.
                         */
                        modifier whenNotPaused() {
                            require(!_paused, "Pausable: paused");
                            _;
                        }
                    
                        /**
                         * @dev Modifier to make a function callable only when the contract is paused.
                         */
                        modifier whenPaused() {
                            require(_paused, "Pausable: not paused");
                            _;
                        }
                    
                        /**
                         * @dev Called by a pauser to pause, triggers stopped state.
                         */
                        function pause() public onlyPauser whenNotPaused {
                            _paused = true;
                            emit Paused(_msgSender());
                        }
                    
                        /**
                         * @dev Called by a pauser to unpause, returns to normal state.
                         */
                        function unpause() public onlyPauser whenPaused {
                            _paused = false;
                            emit Unpaused(_msgSender());
                        }
                    
                        uint256[50] private ______gap;
                    }
                    
                    // File: contracts/interfaces/iERC20Fulcrum.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface iERC20Fulcrum {
                      function mint(
                        address receiver,
                        uint256 depositAmount)
                        external
                        returns (uint256 mintAmount);
                    
                      function burn(
                        address receiver,
                        uint256 burnAmount)
                        external
                        returns (uint256 loanAmountPaid);
                    
                      function tokenPrice()
                        external
                        view
                        returns (uint256 price);
                    
                      function supplyInterestRate()
                        external
                        view
                        returns (uint256);
                    
                      function rateMultiplier()
                        external
                        view
                        returns (uint256);
                      function baseRate()
                        external
                        view
                        returns (uint256);
                    
                      function borrowInterestRate()
                        external
                        view
                        returns (uint256);
                    
                      function avgBorrowInterestRate()
                        external
                        view
                        returns (uint256);
                    
                      function protocolInterestRate()
                        external
                        view
                        returns (uint256);
                    
                      function spreadMultiplier()
                        external
                        view
                        returns (uint256);
                    
                      function totalAssetBorrow()
                        external
                        view
                        returns (uint256);
                    
                      function totalAssetSupply()
                        external
                        view
                        returns (uint256);
                    
                      function nextSupplyInterestRate(uint256)
                        external
                        view
                        returns (uint256);
                    
                      function nextBorrowInterestRate(uint256)
                        external
                        view
                        returns (uint256);
                      function nextLoanInterestRate(uint256)
                        external
                        view
                        returns (uint256);
                      function totalSupplyInterestRate(uint256)
                        external
                        view
                        returns (uint256);
                    
                      function claimLoanToken()
                        external
                        returns (uint256 claimedAmount);
                    
                      function dsr()
                        external
                        view
                        returns (uint256);
                    
                      function chaiPrice()
                        external
                        view
                        returns (uint256);
                    }
                    
                    // File: contracts/interfaces/ILendingProtocol.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface ILendingProtocol {
                      function mint() external returns (uint256);
                      function redeem(address account) external returns (uint256);
                      function nextSupplyRate(uint256 amount) external view returns (uint256);
                      function nextSupplyRateWithParams(uint256[] calldata params) external view returns (uint256);
                      function getAPR() external view returns (uint256);
                      function getPriceInToken() external view returns (uint256);
                      function token() external view returns (address);
                      function underlying() external view returns (address);
                      function availableLiquidity() external view returns (uint256);
                    }
                    
                    // File: contracts/interfaces/IGovToken.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface IGovToken {
                      function redeemGovTokens() external;
                    }
                    
                    // File: contracts/interfaces/IIdleTokenV3_1.sol
                    
                    /**
                     * @title: Idle Token interface
                     * @author: Idle Labs Inc., idle.finance
                     */
                    pragma solidity 0.5.16;
                    
                    interface IIdleTokenV3_1 {
                      // view
                      /**
                       * IdleToken price calculation, in underlying
                       *
                       * @return : price in underlying token
                       */
                      function tokenPrice() external view returns (uint256 price);
                    
                      /**
                       * @return : underlying token address
                       */
                      function token() external view returns (address);
                      /**
                       * Get APR of every ILendingProtocol
                       *
                       * @return addresses: array of token addresses
                       * @return aprs: array of aprs (ordered in respect to the `addresses` array)
                       */
                      function getAPRs() external view returns (address[] memory addresses, uint256[] memory aprs);
                    
                      // external
                      // We should save the amount one has deposited to calc interests
                    
                      /**
                       * Used to mint IdleTokens, given an underlying amount (eg. DAI).
                       * This method triggers a rebalance of the pools if needed
                       * NOTE: User should 'approve' _amount of tokens before calling mintIdleToken
                       * NOTE 2: this method can be paused
                       *
                       * @param _amount : amount of underlying token to be lended
                       * @param _skipRebalance : flag for skipping rebalance for lower gas price
                       * @param _referral : referral address
                       * @return mintedTokens : amount of IdleTokens minted
                       */
                      function mintIdleToken(uint256 _amount, bool _skipRebalance, address _referral) external returns (uint256 mintedTokens);
                    
                      /**
                       * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                       * This method triggers a rebalance of the pools if needed
                       * NOTE: If the contract is paused or iToken price has decreased one can still redeem but no rebalance happens.
                       * NOTE 2: If iToken price has decresed one should not redeem (but can do it) otherwise he would capitalize the loss.
                       *         Ideally one should wait until the black swan event is terminated
                       *
                       * @param _amount : amount of IdleTokens to be burned
                       * @return redeemedTokens : amount of underlying tokens redeemed
                       */
                      function redeemIdleToken(uint256 _amount) external returns (uint256 redeemedTokens);
                      /**
                       * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                       * and send interest-bearing tokens (eg. cDAI/iDAI) directly to the user.
                       * Underlying (eg. DAI) is not redeemed here.
                       *
                       * @param _amount : amount of IdleTokens to be burned
                       */
                      function redeemInterestBearingTokens(uint256 _amount) external;
                    
                      /**
                       * @return : whether has rebalanced or not
                       */
                      function rebalance() external returns (bool);
                    }
                    
                    // File: contracts/interfaces/Comptroller.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface Comptroller {
                      function claimComp(address) external;
                      function compSpeeds(address _cToken) external view returns (uint256);
                      function claimComp(address[] calldata holders, address[] calldata cTokens, bool borrowers, bool suppliers) external;
                    }
                    
                    // File: contracts/interfaces/CERC20.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface CERC20 {
                      function mint(uint256 mintAmount) external returns (uint256);
                      function comptroller() external view returns (address);
                      function redeem(uint256 redeemTokens) external returns (uint256);
                      function exchangeRateStored() external view returns (uint256);
                      function supplyRatePerBlock() external view returns (uint256);
                    
                      function borrowRatePerBlock() external view returns (uint256);
                      function totalReserves() external view returns (uint256);
                      function getCash() external view returns (uint256);
                      function totalBorrows() external view returns (uint256);
                      function reserveFactorMantissa() external view returns (uint256);
                      function interestRateModel() external view returns (address);
                    }
                    
                    // File: contracts/interfaces/IdleController.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface IdleController {
                      function idleSpeeds(address _idleToken) external view returns (uint256);
                      function claimIdle(address[] calldata holders, address[] calldata idleTokens) external;
                    }
                    
                    // File: contracts/interfaces/PriceOracle.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface PriceOracle {
                      function getUnderlyingPrice(address _idleToken) external view returns (uint256);
                      function getPriceUSD(address _asset) external view returns (uint256 price);
                      function getPriceETH(address _asset) external view returns (uint256 price);
                      function getPriceToken(address _asset, address _token) external view returns (uint256 price);
                      function WETH() external view returns (address);
                    
                      function getCompApr(address cToken, address token) external view returns (uint256);
                    }
                    
                    // File: contracts/interfaces/GasToken.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface GasToken {
                      function freeUpTo(uint256 value) external returns (uint256 freed);
                      function freeFromUpTo(address from, uint256 value) external returns (uint256 freed);
                      function balanceOf(address from) external returns (uint256 balance);
                    }
                    
                    // File: contracts/GST2ConsumerV2.sol
                    
                    pragma solidity 0.5.16;
                    
                    
                    
                    contract GST2ConsumerV2 is Initializable {
                      GasToken public gst2;
                    
                      function initialize() initializer public {
                        gst2 = GasToken(0x0000000000b3F879cb30FE243b4Dfee438691c04);
                      }
                    
                      modifier gasDiscountFrom(address from) {
                        uint256 initialGasLeft = gasleft();
                        _;
                        _makeGasDiscount(initialGasLeft - gasleft(), from);
                      }
                    
                      function _makeGasDiscount(uint256 gasSpent, address from) internal {
                        // For more info https://gastoken.io/
                        // 14154 -> FREE_BASE -> base cost of freeing
                        // 41130 -> 2 * REIMBURSE - FREE_TOKEN -> 2 * 24000 - 6870
                        uint256 tokens = (gasSpent + 14154) / 41130;
                        uint256 safeNumTokens;
                        uint256 gas = gasleft();
                    
                        // For more info https://github.com/projectchicago/gastoken/blob/master/contract/gst2_free_example.sol
                        if (gas >= 27710) {
                          safeNumTokens = (gas - 27710) / 7020;
                        }
                    
                        if (tokens > safeNumTokens) {
                          tokens = safeNumTokens;
                        }
                    
                        if (tokens > 0) {
                          gst2.freeFromUpTo(from, tokens);
                        }
                      }
                    }
                    
                    // File: contracts/IdleTokenGovernance.sol
                    
                    /**
                     * @title: Idle Token (V3) main contract
                     * @summary: ERC20 that holds pooled user funds together
                     *           Each token rapresent a share of the underlying pools
                     *           and with each token user have the right to redeem a portion of these pools
                     * @author: Idle Labs Inc., idle.finance
                     */
                    pragma solidity 0.5.16;
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    contract IdleTokenGovernance is Initializable, ERC20, ERC20Detailed, ReentrancyGuard, Ownable, Pausable, IIdleTokenV3_1, GST2ConsumerV2 {
                      using SafeERC20 for IERC20;
                      using SafeMath for uint256;
                    
                      uint256 private constant ONE_18 = 10**18;
                      // State variables
                      // eg. DAI address
                      address public token;
                      // eg. iDAI address
                      address private iToken;
                      // eg. cDAI address
                      address private cToken;
                      // Idle rebalancer current implementation address
                      address public rebalancer;
                      // Address collecting underlying fees
                      address public feeAddress;
                      // Last iToken price, used to pause contract in case of a black swan event
                      uint256 public lastITokenPrice;
                      // eg. 18 for DAI
                      uint256 private tokenDecimals;
                      // Max unlent assets percentage for gas friendly swaps
                      uint256 public maxUnlentPerc; // 100000 == 100% -> 1000 == 1%
                      // Current fee on interest gained
                      uint256 public fee;
                      // eg. [cTokenAddress, iTokenAddress, ...]
                      address[] public allAvailableTokens;
                      // eg. [COMPAddress, CRVAddress, ...]
                      address[] public govTokens;
                      // last fully applied allocations (ie when all liquidity has been correctly placed)
                      // eg. [5000, 0, 5000, 0] for 50% in compound, 0% fulcrum, 50% aave, 0 dydx. same order of allAvailableTokens
                      uint256[] public lastAllocations;
                      // Map that saves avg idleToken price paid for each user, used to calculate earnings
                      mapping(address => uint256) public userAvgPrices;
                      // eg. cTokenAddress => IdleCompoundAddress
                      mapping(address => address) public protocolWrappers;
                      // array with last balance recorded for each gov tokens
                      mapping (address => uint256) public govTokensLastBalances;
                      // govToken -> user_address -> user_index eg. usersGovTokensIndexes[govTokens[0]][msg.sender] = 1111123;
                      mapping (address => mapping (address => uint256)) public usersGovTokensIndexes;
                      // global indices for each gov tokens used as a reference to calculate a fair share for each user
                      mapping (address => uint256) public govTokensIndexes;
                      // Map that saves amount with no fee for each user
                      mapping(address => uint256) private userNoFeeQty;
                      // variable used for avoid the call of mint and redeem in the same tx
                      bytes32 private _minterBlock;
                    
                      // Events
                      event Rebalance(address _rebalancer, uint256 _amount);
                      event Referral(uint256 _amount, address _ref);
                    
                      // ########## IdleToken V4_1 updates
                      // Idle governance token
                      address public constant IDLE = address(0x875773784Af8135eA0ef43b5a374AaD105c5D39e);
                      // Compound governance token
                      address public constant COMP = address(0xc00e94Cb662C3520282E6f5717214004A7f26888);
                      uint256 private constant FULL_ALLOC = 100000;
                    
                      // Idle distribution controller
                      address public constant idleController = address(0x275DA8e61ea8E02d51EDd8d0DC5c0E62b4CDB0BE);
                      // oracle used for calculating the avgAPR with gov tokens
                      address public oracle;
                      // eg cDAI -> COMP
                      mapping(address => address) private protocolTokenToGov;
                      // Whether openRebalance is enabled or not
                      bool public isRiskAdjusted;
                      // last allocations submitted by rebalancer
                      uint256[] private lastRebalancerAllocations;
                    
                      // onlyOwner
                      /**
                       * It allows owner to modify allAvailableTokens array in case of emergency
                       * ie if a bug on a interest bearing token is discovered and reset protocolWrappers
                       * associated with those tokens.
                       *
                       * @param protocolTokens : array of protocolTokens addresses (eg [cDAI, iDAI, ...])
                       * @param wrappers : array of wrapper addresses (eg [IdleCompound, IdleFulcrum, ...])
                       * @param allocations : array of allocations
                       * @param keepAllocations : whether to update lastRebalancerAllocations or not
                       */
                      function setAllAvailableTokensAndWrappers(
                        address[] calldata protocolTokens,
                        address[] calldata wrappers,
                        uint256[] calldata allocations,
                        bool keepAllocations
                      ) external onlyOwner {
                        require(protocolTokens.length == wrappers.length && (allocations.length == wrappers.length || keepAllocations), "IDLE:LEN_DIFF");
                    
                        for (uint256 i = 0; i < protocolTokens.length; i++) {
                          require(protocolTokens[i] != address(0) && wrappers[i] != address(0), "IDLE:IS_0");
                          protocolWrappers[protocolTokens[i]] = wrappers[i];
                        }
                        allAvailableTokens = protocolTokens;
                    
                        if (keepAllocations) {
                          require(protocolTokens.length == allAvailableTokens.length, "IDLE:LEN_DIFF2");
                          return;
                        }
                        _setAllocations(allocations);
                      }
                    
                      /**
                       * It allows owner to set gov tokens array
                       * In case of any errors gov distribution can be paused by passing an empty array
                       *
                       * @param _newGovTokens : array of governance token addresses
                       * @param _protocolTokens : array of interest bearing token addresses
                       */
                      function setGovTokens(
                        address[] calldata _newGovTokens,
                        address[] calldata _protocolTokens
                      ) external onlyOwner {
                        govTokens = _newGovTokens;
                        // Reset protocolTokenToGov mapping
                        for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                          protocolTokenToGov[allAvailableTokens[i]] = address(0);
                        }
                        // set protocol token to gov token mapping
                        for (uint256 i = 0; i < _protocolTokens.length; i++) {
                          address newGov = _newGovTokens[i];
                          if (newGov == IDLE) { continue; }
                          protocolTokenToGov[_protocolTokens[i]] = _newGovTokens[i];
                        }
                      }
                    
                      /**
                       * It allows owner to set the IdleRebalancerV3_1 address
                       *
                       * @param _rebalancer : new IdleRebalancerV3_1 address
                       */
                      function setRebalancer(address _rebalancer)
                        external onlyOwner {
                          require(_rebalancer != address(0), "IDLE:IS_0");
                          rebalancer = _rebalancer;
                      }
                    
                      /**
                       * It allows owner to set the fee (1000 == 10% of gained interest)
                       *
                       * @param _fee : fee amount where 100000 is 100%, max settable is 10%
                       */
                      function setFee(uint256 _fee)
                        external onlyOwner {
                          // 100000 == 100% -> 10000 == 10%
                          require(_fee <= 10000, "IDLE:TOO_HIGH");
                          fee = _fee;
                      }
                    
                      /**
                       * It allows owner to set the fee address
                       *
                       * @param _feeAddress : fee address
                       */
                      function setFeeAddress(address _feeAddress)
                        external onlyOwner {
                          require(_feeAddress != address(0), "IDLE:IS_0");
                          feeAddress = _feeAddress;
                      }
                    
                      /**
                       * It allows owner to set the oracle address for getting avgAPR
                       *
                       * @param _oracle : new oracle address
                       */
                      function setOracleAddress(address _oracle)
                        external onlyOwner {
                          require(_oracle != address(0), "IDLE:IS_0");
                          oracle = _oracle;
                      }
                    
                      /**
                       * It allows owner to set the max unlent asset percentage (1000 == 1% of unlent asset max)
                       *
                       * @param _perc : max unlent perc where 100000 is 100%
                       */
                      function setMaxUnlentPerc(uint256 _perc)
                        external onlyOwner {
                          require(_perc <= 100000, "IDLE:TOO_HIGH");
                          maxUnlentPerc = _perc;
                      }
                    
                      /**
                       * It allows owner to set the isRiskAdjusted flag
                       *
                       * @param _isRiskAdjusted : flag for openRebalance
                       */
                      function setIsRiskAdjusted(bool _isRiskAdjusted)
                        external onlyOwner {
                          isRiskAdjusted = _isRiskAdjusted;
                      }
                    
                      /**
                       * Used by Rebalancer to set the new allocations
                       *
                       * @param _allocations : array with allocations in percentages (100% => 100000)
                       */
                      function setAllocations(uint256[] calldata _allocations) external {
                        require(msg.sender == rebalancer || msg.sender == owner(), "IDLE:!AUTH");
                        _setAllocations(_allocations);
                      }
                    
                      /**
                       * Used by Rebalancer or in openRebalance to set the new allocations
                       *
                       * @param _allocations : array with allocations in percentages (100% => 100000)
                       */
                      function _setAllocations(uint256[] memory _allocations) internal {
                        require(_allocations.length == allAvailableTokens.length, "IDLE:!EQ_LEN");
                        uint256 total;
                        for (uint256 i = 0; i < _allocations.length; i++) {
                          total = total.add(_allocations[i]);
                        }
                        lastRebalancerAllocations = _allocations;
                        require(total == FULL_ALLOC, "IDLE:!EQ_TOT");
                      }
                    
                      // view
                      /**
                       * Get latest allocations submitted by rebalancer
                       *
                       * @return : array of allocations ordered as allAvailableTokens
                       */
                      function getAllocations() external view returns (uint256[] memory) {
                        return lastRebalancerAllocations;
                      }
                    
                      /**
                       * IdleToken price calculation, in underlying
                       *
                       * @return : price in underlying token
                       */
                      function tokenPrice()
                        external view
                        returns (uint256) {
                        return _tokenPrice();
                      }
                    
                      /**
                       * Get APR of every ILendingProtocol
                       *
                       * @return addresses: array of token addresses
                       * @return aprs: array of aprs (ordered in respect to the `addresses` array)
                       */
                      function getAPRs()
                        external view
                        returns (address[] memory addresses, uint256[] memory aprs) {
                          address currToken;
                          addresses = new address[](allAvailableTokens.length);
                          aprs = new uint256[](allAvailableTokens.length);
                          for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                            currToken = allAvailableTokens[i];
                            addresses[i] = currToken;
                            aprs[i] = ILendingProtocol(protocolWrappers[currToken]).getAPR();
                          }
                      }
                    
                      /**
                       * Get current avg APR of this IdleToken
                       *
                       * @return avgApr: current weighted avg apr
                       */
                      function getAvgAPR()
                        external view
                        returns (uint256) {
                        return _getAvgAPR();
                      }
                    
                      /**
                       * Get current avg APR of this IdleToken
                       *
                       * @return avgApr: current weighted avg apr
                       */
                      function _getAvgAPR()
                        internal view
                        returns (uint256 avgApr) {
                          (, uint256[] memory amounts, uint256 total) = _getCurrentAllocations();
                          // IDLE gov token won't be counted here because is not in allAvailableTokens
                          for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                            if (amounts[i] == 0) {
                              continue;
                            }
                            address protocolToken = allAvailableTokens[i];
                            // avgApr = avgApr.add(currApr.mul(weight).div(ONE_18))
                            avgApr = avgApr.add(
                              ILendingProtocol(protocolWrappers[protocolToken]).getAPR().mul(
                                amounts[i]
                              )
                            );
                            // Add weighted gov tokens apr
                            address currGov = protocolTokenToGov[protocolToken];
                            if (govTokens.length > 0 && currGov != address(0)) {
                              avgApr = avgApr.add(amounts[i].mul(getGovApr(currGov)));
                            }
                          }
                    
                          avgApr = avgApr.div(total);
                      }
                    
                      /**
                       * Get gov token APR
                       *
                       * @return : apr scaled to 1e18
                       */
                      function getGovApr(address _govToken) internal view returns (uint256) {
                        // In case new Gov tokens will be supported this should be updated, no need to add IDLE apr
                        if (_govToken == COMP && cToken != address(0)) {
                          return PriceOracle(oracle).getCompApr(cToken, token);
                        }
                      }
                    
                      /**
                       * ERC20 modified transferFrom that also update the avgPrice paid for the recipient and
                       * updates user gov idx
                       *
                       * @param sender : sender account
                       * @param recipient : recipient account
                       * @param amount : value to transfer
                       * @return : flag whether transfer was successful or not
                       */
                      function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
                        _updateUserGovIdxTransfer(sender, recipient, amount);
                        _transfer(sender, recipient, amount);
                        _approve(sender, msg.sender, allowance(sender, msg.sender).sub(amount, "ERC20: transfer amount exceeds allowance"));
                        _updateUserFeeInfoTransfer(sender, recipient, amount, userAvgPrices[sender]);
                        return true;
                      }
                    
                      /**
                       * ERC20 modified transfer that also update the avgPrice paid for the recipient and
                       * updates user gov idx
                       *
                       * @param recipient : recipient account
                       * @param amount : value to transfer
                       * @return : flag whether transfer was successful or not
                       */
                      function transfer(address recipient, uint256 amount) public returns (bool) {
                        _updateUserGovIdxTransfer(msg.sender, recipient, amount);
                        _transfer(msg.sender, recipient, amount);
                        _updateUserFeeInfoTransfer(msg.sender, recipient, amount, userAvgPrices[msg.sender]);
                        return true;
                      }
                    
                      /**
                       * Helper method for transfer and transferFrom, updates recipient gov indexes
                       *
                       * @param _from : sender account
                       * @param _to : recipient account
                       * @param amount : value to transfer
                       */
                      function _updateUserGovIdxTransfer(address _from, address _to, uint256 amount) internal {
                        address govToken;
                        uint256 govTokenIdx;
                        uint256 sharePerTokenFrom;
                        uint256 shareTo;
                        uint256 balanceTo = balanceOf(_to);
                        for (uint256 i = 0; i < govTokens.length; i++) {
                          govToken = govTokens[i];
                          if (balanceTo == 0) {
                            usersGovTokensIndexes[govToken][_to] = usersGovTokensIndexes[govToken][_from];
                            continue;
                          }
                    
                          govTokenIdx = govTokensIndexes[govToken];
                          // calc 1 idleToken value in gov shares for user `_from`
                          sharePerTokenFrom = govTokenIdx.sub(usersGovTokensIndexes[govToken][_from]);
                          // calc current gov shares (before transfer) for user `_to`
                          shareTo = balanceTo.mul(govTokenIdx.sub(usersGovTokensIndexes[govToken][_to])).div(ONE_18);
                          // user `_to` should have -> shareTo + (sharePerTokenFrom * amount / 1e18) = (balanceTo + amount) * (govTokenIdx - userIdx) / 1e18
                          // so userIdx = govTokenIdx - ((shareTo * 1e18 + (sharePerTokenFrom * amount)) / (balanceTo + amount))
                          usersGovTokensIndexes[govToken][_to] = govTokenIdx.sub(
                            shareTo.mul(ONE_18).add(sharePerTokenFrom.mul(amount)).div(
                              balanceTo.add(amount)
                            )
                          );
                        }
                      }
                    
                      /**
                       * Get how many gov tokens a user is entitled to (this may not include eventual undistributed tokens)
                       *
                       * @param _usr : user address
                       * @return : array of amounts for each gov token
                       */
                      function getGovTokensAmounts(address _usr) external view returns (uint256[] memory _amounts) {
                        address govToken;
                        uint256 usrBal = balanceOf(_usr);
                        _amounts = new uint256[](govTokens.length);
                        for (uint256 i = 0; i < _amounts.length; i++) {
                          govToken = govTokens[i];
                          _amounts[i] = usrBal.mul(govTokensIndexes[govToken].sub(usersGovTokensIndexes[govToken][_usr])).div(ONE_18);
                        }
                      }
                    
                      // external
                      /**
                       * Used to mint IdleTokens, given an underlying amount (eg. DAI).
                       * This method triggers a rebalance of the pools if _skipRebalance is set to false
                       * NOTE: User should 'approve' _amount of tokens before calling mintIdleToken
                       * NOTE 2: this method can be paused
                       * This method use GasTokens of this contract (if present) to get a gas discount
                       *
                       * @param _amount : amount of underlying token to be lended
                       * @param _referral : referral address
                       * @return mintedTokens : amount of IdleTokens minted
                       */
                      function mintIdleToken(uint256 _amount, bool _skipRebalance, address _referral)
                        external nonReentrant whenNotPaused
                        returns (uint256 mintedTokens) {
                        _minterBlock = keccak256(abi.encodePacked(tx.origin, block.number));
                        _redeemGovTokens(msg.sender, false);
                        // Get current IdleToken price
                        uint256 idlePrice = _tokenPrice();
                        // transfer tokens to this contract
                        IERC20(token).safeTransferFrom(msg.sender, address(this), _amount);
                    
                        if (!_skipRebalance) {
                          // lend assets and rebalance the pool if needed
                          _rebalance();
                        }
                    
                        mintedTokens = _amount.mul(ONE_18).div(idlePrice);
                        _mint(msg.sender, mintedTokens);
                    
                        // Update avg price and/or userNoFeeQty
                        _updateUserFeeInfo(msg.sender, mintedTokens, idlePrice);
                        // Update user idx for each gov tokens
                        _updateUserGovIdx(msg.sender, mintedTokens);
                    
                        if (_referral != address(0)) {
                          emit Referral(_amount, _referral);
                        }
                      }
                    
                      /**
                       * Helper method for mintIdleToken, updates minter gov indexes
                       *
                       * @param _to : minter account
                       * @param _mintedTokens : number of newly minted tokens
                       */
                      function _updateUserGovIdx(address _to, uint256 _mintedTokens) internal {
                        address govToken;
                        uint256 usrBal = balanceOf(_to);
                        uint256 _govIdx;
                        uint256 _usrIdx;
                    
                        for (uint256 i = 0; i < govTokens.length; i++) {
                          govToken = govTokens[i];
                          _govIdx = govTokensIndexes[govToken];
                          _usrIdx = usersGovTokensIndexes[govToken][_to];
                    
                          // calculate user idx
                          usersGovTokensIndexes[govToken][_to] = usrBal.mul(_usrIdx).add(
                            _mintedTokens.mul(_govIdx.sub(_usrIdx))
                          ).div(usrBal);
                        }
                      }
                    
                      /**
                       * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                       *
                       * @param _amount : amount of IdleTokens to be burned
                       * @return redeemedTokens : amount of underlying tokens redeemed
                       */
                      function redeemIdleToken(uint256 _amount)
                        external nonReentrant
                        returns (uint256 redeemedTokens) {
                          _checkMintRedeemSameTx();
                    
                          _redeemGovTokens(msg.sender, false);
                    
                          uint256 price = _tokenPrice();
                          uint256 valueToRedeem = _amount.mul(price).div(ONE_18);
                          uint256 balanceUnderlying = IERC20(token).balanceOf(address(this));
                          uint256 idleSupply = totalSupply();
                    
                          if (valueToRedeem <= balanceUnderlying) {
                            redeemedTokens = valueToRedeem;
                          } else {
                            address currToken;
                            for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                              currToken = allAvailableTokens[i];
                              redeemedTokens = redeemedTokens.add(
                                _redeemProtocolTokens(
                                  currToken,
                                  // _amount * protocolPoolBalance / idleSupply
                                  _amount.mul(IERC20(currToken).balanceOf(address(this))).div(idleSupply), // amount to redeem
                                  address(this)
                                )
                              );
                            }
                            // Get a portion of the eventual unlent balance
                            redeemedTokens = redeemedTokens.add(_amount.mul(balanceUnderlying).div(idleSupply));
                          }
                          // get eventual performance fee
                          redeemedTokens = _getFee(_amount, redeemedTokens, price);
                          // burn idleTokens
                          _burn(msg.sender, _amount);
                          // send underlying minus fee to msg.sender
                          IERC20(token).safeTransfer(msg.sender, redeemedTokens);
                      }
                    
                      /**
                       * Here we calc the pool share one can withdraw given the amount of IdleToken they want to burn
                       * and send interest-bearing tokens (eg. cDAI/iDAI) directly to the user.
                       * Underlying (eg. DAI) is not redeemed here.
                       *
                       * @param _amount : amount of IdleTokens to be burned
                       */
                      function redeemInterestBearingTokens(uint256 _amount)
                        external nonReentrant whenPaused {
                          _checkMintRedeemSameTx();
                    
                          _redeemGovTokens(msg.sender, false);
                    
                          uint256 idleSupply = totalSupply();
                          address currentToken;
                    
                          for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                            currentToken = allAvailableTokens[i];
                            IERC20(currentToken).safeTransfer(
                              msg.sender,
                              _amount.mul(IERC20(currentToken).balanceOf(address(this))).div(idleSupply) // amount to redeem
                            );
                          }
                          // Get a portion of the eventual unlent balance
                          IERC20(token).safeTransfer(
                            msg.sender,
                            _amount.mul(IERC20(token).balanceOf(address(this))).div(idleSupply) // amount to redeem
                          );
                    
                          _burn(msg.sender, _amount);
                      }
                    
                      /**
                       * Dynamic allocate all the pool across different lending protocols if needed, use gas refund from gasToken
                       *
                       * NOTE: this method can be paused.
                       * msg.sender should approve this contract to spend GST2 tokens before calling
                       * this method
                       *
                       * @return : whether has rebalanced or not
                       */
                      function rebalanceWithGST()
                        external gasDiscountFrom(msg.sender)
                        returns (bool) {
                          return _rebalance();
                      }
                    
                      /**
                       * Dynamic allocate all the pool across different lending protocols if needed,
                       * rebalance without params
                       *
                       * NOTE: this method can be paused
                       *
                       * @return : whether has rebalanced or not
                       */
                      function rebalance() external returns (bool) {
                        return _rebalance();
                      }
                    
                      /**
                       * Allow any users to set new allocations as long as the new allocation
                       * gives a better avg APR than before
                       * Allocations should be in the format [100000, 0, 0, 0, ...] where length is the same
                       * as lastAllocations variable and the sum of all value should be == 100000
                       *
                       * This method is not callble if this instance of IdleToken is a risk adjusted instance
                       * NOTE: this method can be paused
                       *
                       * @param _newAllocations : array with new allocations in percentage
                       * @return : whether has rebalanced or not
                       * @return avgApr : the new avg apr after rebalance
                       */
                      function openRebalance(uint256[] calldata _newAllocations)
                        external whenNotPaused
                        returns (bool, uint256 avgApr) {
                          require(!isRiskAdjusted, "IDLE:NOT_ALLOWED");
                          uint256 initialAPR = _getAvgAPR();
                          // Validate and update rebalancer allocations
                          _setAllocations(_newAllocations);
                          bool hasRebalanced = _rebalance();
                          uint256 newAprAfterRebalance = _getAvgAPR();
                          require(newAprAfterRebalance > initialAPR, "IDLE:NOT_IMPROV");
                          return (hasRebalanced, newAprAfterRebalance);
                      }
                    
                      // internal
                      /**
                       * Get current idleToken price based on net asset value and totalSupply
                       *
                       * @return price: value of 1 idleToken in underlying
                       */
                      function _tokenPrice() internal view returns (uint256 price) {
                        uint256 totSupply = totalSupply();
                        if (totSupply == 0) {
                          return 10**(tokenDecimals);
                        }
                    
                        address currToken;
                        uint256 totNav = IERC20(token).balanceOf(address(this)).mul(ONE_18); // eventual underlying unlent balance
                    
                        for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                          currToken = allAvailableTokens[i];
                          totNav = totNav.add(
                            // NAV = price * poolSupply
                            ILendingProtocol(protocolWrappers[currToken]).getPriceInToken().mul(
                              IERC20(currToken).balanceOf(address(this))
                            )
                          );
                        }
                    
                        price = totNav.div(totSupply); // idleToken price in token wei
                      }
                    
                      /**
                       * Dynamic allocate all the pool across different lending protocols if needed
                       *
                       * NOTE: this method can be paused
                       *
                       * @return : whether has rebalanced or not
                       */
                      function _rebalance()
                        internal whenNotPaused
                        returns (bool) {
                          // check if we need to rebalance by looking at the last allocations submitted by rebalancer
                          uint256[] memory rebalancerLastAllocations = lastRebalancerAllocations;
                          bool areAllocationsEqual = rebalancerLastAllocations.length == lastAllocations.length;
                          if (areAllocationsEqual) {
                            for (uint256 i = 0; i < lastAllocations.length || !areAllocationsEqual; i++) {
                              if (lastAllocations[i] != rebalancerLastAllocations[i]) {
                                areAllocationsEqual = false;
                                break;
                              }
                            }
                          }
                    
                          uint256 balance = IERC20(token).balanceOf(address(this));
                          uint256 maxUnlentBalance;
                    
                          if (areAllocationsEqual && balance == 0) {
                            return false;
                          }
                    
                          if (balance > 0) {
                            maxUnlentBalance = _getCurrentPoolValue().mul(maxUnlentPerc).div(FULL_ALLOC);
                            if (lastAllocations.length == 0) {
                              // set in storage
                              lastAllocations = rebalancerLastAllocations;
                            }
                    
                            if (balance > maxUnlentBalance) {
                              // mint the difference
                              _mintWithAmounts(allAvailableTokens, _amountsFromAllocations(rebalancerLastAllocations, balance.sub(maxUnlentBalance)));
                            }
                          }
                    
                          if (areAllocationsEqual) {
                            return false;
                          }
                    
                          // Instead of redeeming everything during rebalance we redeem and mint only what needs
                          // to be reallocated
                          // get current allocations in underlying (it does not count unlent underlying)
                          (address[] memory tokenAddresses, uint256[] memory amounts, uint256 totalInUnderlying) = _getCurrentAllocations();
                    
                          if (balance == 0 && maxUnlentPerc > 0) {
                            totalInUnderlying = totalInUnderlying.sub(_getCurrentPoolValue().mul(maxUnlentPerc).div(FULL_ALLOC));
                          }
                          // calculate new allocations given the total (not counting unlent balance)
                          uint256[] memory newAmounts = _amountsFromAllocations(rebalancerLastAllocations, totalInUnderlying);
                          (uint256[] memory toMintAllocations, uint256 totalToMint, bool lowLiquidity) = _redeemAllNeeded(tokenAddresses, amounts, newAmounts);
                          // if some protocol has liquidity that we should redeem, we do not update
                          // lastAllocations to force another rebalance next time
                          if (!lowLiquidity) {
                            // Update lastAllocations with rebalancerLastAllocations
                            delete lastAllocations;
                            lastAllocations = rebalancerLastAllocations;
                          }
                    
                          // Do not count `maxUnlentPerc` from balance
                          if (maxUnlentBalance == 0 && maxUnlentPerc > 0) {
                            maxUnlentBalance = _getCurrentPoolValue().mul(maxUnlentPerc).div(FULL_ALLOC);
                          }
                    
                          uint256 totalRedeemd = IERC20(token).balanceOf(address(this));
                    
                          if (totalRedeemd <= maxUnlentBalance) {
                            return false;
                          }
                    
                          // Do not mint directly using toMintAllocations check with totalRedeemd
                          uint256[] memory tempAllocations = new uint256[](toMintAllocations.length);
                          for (uint256 i = 0; i < toMintAllocations.length; i++) {
                            // Calc what would have been the correct allocations percentage if all was available
                            tempAllocations[i] = toMintAllocations[i].mul(FULL_ALLOC).div(totalToMint);
                          }
                    
                          uint256[] memory partialAmounts = _amountsFromAllocations(tempAllocations, totalRedeemd.sub(maxUnlentBalance));
                          _mintWithAmounts(allAvailableTokens, partialAmounts);
                    
                          emit Rebalance(msg.sender, totalInUnderlying.add(maxUnlentBalance));
                    
                          return true; // hasRebalanced
                      }
                    
                      /**
                       * Redeem unclaimed governance tokens and update governance global index and user index if needed
                       * if called during redeem it will send all gov tokens accrued by a user to the user
                       *
                       * @param _to : user address
                       * @param _skipRedeem : flag to choose whether to send gov tokens to user or not
                       */
                      function _redeemGovTokens(address _to, bool _skipRedeem) internal {
                        if (govTokens.length == 0) {
                          return;
                        }
                        uint256 supply = totalSupply();
                        uint256 usrBal = balanceOf(_to);
                        address govToken;
                    
                        for (uint256 i = 0; i < govTokens.length; i++) {
                          govToken = govTokens[i];
                    
                          if (supply > 0) {
                            if (!_skipRedeem) {
                              _redeemGovTokensFromProtocol(govToken);
                            }
                    
                            // get current gov token balance
                            uint256 govBal = IERC20(govToken).balanceOf(address(this));
                            if (govBal > 0) {
                              // update global index with ratio of govTokens per idleToken
                              govTokensIndexes[govToken] = govTokensIndexes[govToken].add(
                                // check how much gov tokens for each idleToken we gained since last update
                                govBal.sub(govTokensLastBalances[govToken]).mul(ONE_18).div(supply)
                              );
                              // update global var with current govToken balance
                              govTokensLastBalances[govToken] = govBal;
                            }
                          }
                    
                          if (usrBal > 0) {
                            if (!_skipRedeem) {
                              uint256 usrIndex = usersGovTokensIndexes[govToken][_to];
                              // update current user index for this gov token
                              usersGovTokensIndexes[govToken][_to] = govTokensIndexes[govToken];
                              // check if user has accrued something
                              uint256 delta = govTokensIndexes[govToken].sub(usrIndex);
                              if (delta == 0) { continue; }
                              uint256 share = usrBal.mul(delta).div(ONE_18);
                              uint256 bal = IERC20(govToken).balanceOf(address(this));
                              // To avoid rounding issue
                              if (share > bal) {
                                share = bal;
                              }
                    
                              uint256 feeDue;
                              // no fee for IDLE governance token
                              if (feeAddress != address(0) && fee > 0 && govToken != IDLE) {
                                feeDue = share.mul(fee).div(FULL_ALLOC);
                                // Transfer gov token fee to feeAddress
                                IERC20(govToken).safeTransfer(feeAddress, feeDue);
                              }
                              // Transfer gov token to user
                              IERC20(govToken).safeTransfer(_to, share.sub(feeDue));
                              // Update last balance
                              govTokensLastBalances[govToken] = IERC20(govToken).balanceOf(address(this));
                            }
                          } else {
                            // save current index for this gov token
                            usersGovTokensIndexes[govToken][_to] = govTokensIndexes[govToken];
                          }
                        }
                      }
                    
                      /**
                       * Redeem a specific gov token
                       *
                       * @param _govToken : address of the gov token to redeem
                       */
                      function _redeemGovTokensFromProtocol(address _govToken) internal {
                        // In case new Gov tokens will be supported this should be updated
                        if (_govToken == COMP || _govToken == IDLE) {
                          address[] memory holders = new address[](1);
                          address[] memory tokens = new address[](1);
                          holders[0] = address(this);
                    
                          if (_govToken == IDLE) {
                            tokens[0] = address(this);
                            IdleController(idleController).claimIdle(holders, tokens);
                            return;
                          }
                          if (cToken != address(0)) {
                            tokens[0] = cToken;
                            Comptroller(CERC20(cToken).comptroller()).claimComp(holders, tokens, false, true);
                          }
                        }
                      }
                    
                      /**
                       * Update userNoFeeQty of a user on transfers and eventually avg price paid for each idle token
                       * userAvgPrice do not consider tokens bought when there was no fee
                       *
                       * @param from : user address of the sender || address(0) on mint
                       * @param usr : user that should have balance update
                       * @param qty : new amount deposited / transferred, in idleToken
                       * @param price : curr idleToken price in underlying
                       */
                      function _updateUserFeeInfoTransfer(address from, address usr, uint256 qty, uint256 price) private {
                        uint256 userNoFeeQtyFrom = userNoFeeQty[from];
                        if (userNoFeeQtyFrom >= qty) {
                          userNoFeeQty[from] = userNoFeeQtyFrom.sub(qty);
                          userNoFeeQty[usr] = userNoFeeQty[usr].add(qty);
                          // No avg price update needed
                          return;
                        }
                        // nofeeQty not counted
                        uint256 oldBalance = balanceOf(usr).sub(qty).sub(userNoFeeQty[usr]);
                        uint256 newQty = qty.sub(userNoFeeQtyFrom);
                        // (avgPrice * oldBalance) + (currPrice * newQty)) / totBalance
                        userAvgPrices[usr] = userAvgPrices[usr].mul(oldBalance).add(price.mul(newQty)).div(oldBalance.add(newQty));
                        // update no fee quantities
                        userNoFeeQty[from] = 0;
                        userNoFeeQty[usr] = userNoFeeQty[usr].add(userNoFeeQtyFrom);
                      }
                    
                      /**
                       * Update userNoFeeQty of a user on deposits and eventually avg price paid for each idle token
                       * userAvgPrice do not consider tokens bought when there was no fee
                       *
                       * @param usr : user that should have balance update
                       * @param qty : new amount deposited / transferred, in idleToken
                       * @param price : curr idleToken price in underlying
                       */
                      function _updateUserFeeInfo(address usr, uint256 qty, uint256 price) private {
                        if (fee == 0) { // on deposits with 0 fee
                          userNoFeeQty[usr] = userNoFeeQty[usr].add(qty);
                          return;
                        }
                        // on deposits with fee
                        uint256 totBalance = balanceOf(usr).sub(userNoFeeQty[usr]);
                        // noFeeQty should not be counted here
                        // (avgPrice * oldBalance) + (currPrice * newQty)) / totBalance
                        userAvgPrices[usr] = userAvgPrices[usr].mul(totBalance.sub(qty)).add(price.mul(qty)).div(totBalance);
                      }
                    
                      /**
                       * Calculate fee and send them to feeAddress
                       *
                       * @param amount : in idleTokens
                       * @param redeemed : in underlying
                       * @param currPrice : current idleToken price
                       * @return : net value in underlying
                       */
                      function _getFee(uint256 amount, uint256 redeemed, uint256 currPrice) internal returns (uint256) {
                        uint256 noFeeQty = userNoFeeQty[msg.sender]; // in idleTokens
                        bool hasEnoughNoFeeQty = noFeeQty >= amount;
                    
                        if (fee == 0 || hasEnoughNoFeeQty) {
                          userNoFeeQty[msg.sender] = hasEnoughNoFeeQty ? noFeeQty.sub(amount) : 0;
                          return redeemed;
                        }
                        userNoFeeQty[msg.sender] = 0;
                        uint256 elegibleGains = currPrice < userAvgPrices[msg.sender] ? 0 :
                          amount.sub(noFeeQty).mul(currPrice.sub(userAvgPrices[msg.sender])).div(ONE_18); // in underlyings
                        uint256 feeDue = elegibleGains.mul(fee).div(FULL_ALLOC);
                        IERC20(token).safeTransfer(feeAddress, feeDue);
                        return redeemed.sub(feeDue);
                      }
                    
                      /**
                       * Mint specific amounts of protocols tokens
                       *
                       * @param tokenAddresses : array of protocol tokens
                       * @param protocolAmounts : array of amounts to be minted
                       * @return : net value in underlying
                       */
                      function _mintWithAmounts(address[] memory tokenAddresses, uint256[] memory protocolAmounts) internal {
                        // mint for each protocol and update currentTokensUsed
                        uint256 currAmount;
                        address protWrapper;
                    
                        for (uint256 i = 0; i < protocolAmounts.length; i++) {
                          currAmount = protocolAmounts[i];
                          if (currAmount == 0) {
                            continue;
                          }
                          protWrapper = protocolWrappers[tokenAddresses[i]];
                          // Transfer _amount underlying token (eg. DAI) to protWrapper
                          IERC20(token).safeTransfer(protWrapper, currAmount);
                          ILendingProtocol(protWrapper).mint();
                        }
                      }
                    
                      /**
                       * Calculate amounts from percentage allocations (100000 => 100%)
                       *
                       * @param allocations : array of protocol allocations in percentage
                       * @param total : total amount
                       * @return : array with amounts
                       */
                      function _amountsFromAllocations(uint256[] memory allocations, uint256 total)
                        internal pure returns (uint256[] memory newAmounts) {
                        newAmounts = new uint256[](allocations.length);
                        uint256 currBalance;
                        uint256 allocatedBalance;
                    
                        for (uint256 i = 0; i < allocations.length; i++) {
                          if (i == allocations.length - 1) {
                            newAmounts[i] = total.sub(allocatedBalance);
                          } else {
                            currBalance = total.mul(allocations[i]).div(FULL_ALLOC);
                            allocatedBalance = allocatedBalance.add(currBalance);
                            newAmounts[i] = currBalance;
                          }
                        }
                        return newAmounts;
                      }
                    
                      /**
                       * Redeem all underlying needed from each protocol
                       *
                       * @param tokenAddresses : array of protocol tokens addresses
                       * @param amounts : array with current allocations in underlying
                       * @param newAmounts : array with new allocations in underlying
                       * @return toMintAllocations : array with amounts to be minted
                       * @return totalToMint : total amount that needs to be minted
                       */
                      function _redeemAllNeeded(
                        address[] memory tokenAddresses,
                        uint256[] memory amounts,
                        uint256[] memory newAmounts
                        ) internal returns (
                          uint256[] memory toMintAllocations,
                          uint256 totalToMint,
                          bool lowLiquidity
                        ) {
                        toMintAllocations = new uint256[](amounts.length);
                        ILendingProtocol protocol;
                        uint256 currAmount;
                        uint256 newAmount;
                        address currToken;
                        // check the difference between amounts and newAmounts
                        for (uint256 i = 0; i < amounts.length; i++) {
                          currToken = tokenAddresses[i];
                          newAmount = newAmounts[i];
                          currAmount = amounts[i];
                          protocol = ILendingProtocol(protocolWrappers[currToken]);
                          if (currAmount > newAmount) {
                            uint256 toRedeem = currAmount.sub(newAmount);
                            uint256 availableLiquidity = protocol.availableLiquidity();
                            if (availableLiquidity < toRedeem) {
                              lowLiquidity = true;
                              toRedeem = availableLiquidity;
                            }
                            // redeem the difference
                            _redeemProtocolTokens(
                              currToken,
                              // convert amount from underlying to protocol token
                              toRedeem.mul(ONE_18).div(protocol.getPriceInToken()),
                              address(this) // tokens are now in this contract
                            );
                          } else {
                            toMintAllocations[i] = newAmount.sub(currAmount);
                            totalToMint = totalToMint.add(toMintAllocations[i]);
                          }
                        }
                      }
                    
                      /**
                       * Get the contract balance of every protocol currently used
                       *
                       * @return tokenAddresses : array with all token addresses used,
                       *                          eg [cTokenAddress, iTokenAddress]
                       * @return amounts : array with all amounts for each protocol in order,
                       *                   eg [amountCompoundInUnderlying, amountFulcrumInUnderlying]
                       * @return total : total AUM in underlying
                       */
                      function _getCurrentAllocations() internal view
                        returns (address[] memory tokenAddresses, uint256[] memory amounts, uint256 total) {
                          // Get balance of every protocol implemented
                          tokenAddresses = new address[](allAvailableTokens.length);
                          amounts = new uint256[](allAvailableTokens.length);
                    
                          address currentToken;
                          uint256 currTokenPrice;
                    
                          for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                            currentToken = allAvailableTokens[i];
                            tokenAddresses[i] = currentToken;
                            currTokenPrice = ILendingProtocol(protocolWrappers[currentToken]).getPriceInToken();
                            amounts[i] = currTokenPrice.mul(
                              IERC20(currentToken).balanceOf(address(this))
                            ).div(ONE_18);
                            total = total.add(amounts[i]);
                          }
                    
                          // return addresses and respective amounts in underlying
                          return (tokenAddresses, amounts, total);
                      }
                    
                      /**
                       * Get the current pool value in underlying
                       *
                       * @return total : total AUM in underlying
                       */
                      function _getCurrentPoolValue() internal view
                        returns (uint256 total) {
                          // Get balance of every protocol implemented
                          address currentToken;
                    
                          for (uint256 i = 0; i < allAvailableTokens.length; i++) {
                            currentToken = allAvailableTokens[i];
                            total = total.add(ILendingProtocol(protocolWrappers[currentToken]).getPriceInToken().mul(
                              IERC20(currentToken).balanceOf(address(this))
                            ).div(ONE_18));
                          }
                    
                          // add unlent balance
                          total = total.add(IERC20(token).balanceOf(address(this)));
                      }
                    
                      /**
                       * Check that no mint has been made in the same block from the same EOA
                       */
                      function _checkMintRedeemSameTx() private view {
                        require(keccak256(abi.encodePacked(tx.origin, block.number)) != _minterBlock, "IDLE:REENTR");
                      }
                    
                      // ILendingProtocols calls
                      /**
                       * Redeem underlying tokens through protocol wrapper
                       *
                       * @param _amount : amount of `_token` to redeem
                       * @param _token : protocol token address
                       * @param _account : should be msg.sender when rebalancing and final user when redeeming
                       * @return tokens : new tokens minted
                       */
                      function _redeemProtocolTokens(address _token, uint256 _amount, address _account)
                        internal
                        returns (uint256 tokens) {
                          if (_amount == 0) {
                            return tokens;
                          }
                          // Transfer _amount of _protocolToken (eg. cDAI) to _wrapperAddr
                          IERC20(_token).safeTransfer(protocolWrappers[_token], _amount);
                          tokens = ILendingProtocol(protocolWrappers[_token]).redeem(_account);
                      }
                    }

                    File 8 of 11: CErc20Delegator
                    pragma solidity ^0.5.16;
                    import "./CTokenInterfaces.sol";
                    /**
                     * @title Compound's CErc20Delegator Contract
                     * @notice CTokens which wrap an EIP-20 underlying and delegate to an implementation
                     * @author Compound
                     */
                    contract CErc20Delegator is CTokenInterface, CErc20Interface, CDelegatorInterface {
                        /**
                         * @notice Construct a new money market
                         * @param underlying_ The address of the underlying asset
                         * @param comptroller_ The address of the Comptroller
                         * @param interestRateModel_ The address of the interest rate model
                         * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18
                         * @param name_ ERC-20 name of this token
                         * @param symbol_ ERC-20 symbol of this token
                         * @param decimals_ ERC-20 decimal precision of this token
                         * @param admin_ Address of the administrator of this token
                         * @param implementation_ The address of the implementation the contract delegates to
                         * @param becomeImplementationData The encoded args for becomeImplementation
                         */
                        constructor(address underlying_,
                                    ComptrollerInterface comptroller_,
                                    InterestRateModel interestRateModel_,
                                    uint initialExchangeRateMantissa_,
                                    string memory name_,
                                    string memory symbol_,
                                    uint8 decimals_,
                                    address payable admin_,
                                    address implementation_,
                                    bytes memory becomeImplementationData) public {
                            // Creator of the contract is admin during initialization
                            admin = msg.sender;
                            // First delegate gets to initialize the delegator (i.e. storage contract)
                            delegateTo(implementation_, abi.encodeWithSignature("initialize(address,address,address,uint256,string,string,uint8)",
                                                                                underlying_,
                                                                                comptroller_,
                                                                                interestRateModel_,
                                                                                initialExchangeRateMantissa_,
                                                                                name_,
                                                                                symbol_,
                                                                                decimals_));
                            // New implementations always get set via the settor (post-initialize)
                            _setImplementation(implementation_, false, becomeImplementationData);
                            // Set the proper admin now that initialization is done
                            admin = admin_;
                        }
                        /**
                         * @notice Called by the admin to update the implementation of the delegator
                         * @param implementation_ The address of the new implementation for delegation
                         * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation
                         * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation
                         */
                        function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) public {
                            require(msg.sender == admin, "CErc20Delegator::_setImplementation: Caller must be admin");
                            if (allowResign) {
                                delegateToImplementation(abi.encodeWithSignature("_resignImplementation()"));
                            }
                            address oldImplementation = implementation;
                            implementation = implementation_;
                            delegateToImplementation(abi.encodeWithSignature("_becomeImplementation(bytes)", becomeImplementationData));
                            emit NewImplementation(oldImplementation, implementation);
                        }
                        /**
                         * @notice Sender supplies assets into the market and receives cTokens in exchange
                         * @dev Accrues interest whether or not the operation succeeds, unless reverted
                         * @param mintAmount The amount of the underlying asset to supply
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function mint(uint mintAmount) external returns (uint) {
                            mintAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Sender redeems cTokens in exchange for the underlying asset
                         * @dev Accrues interest whether or not the operation succeeds, unless reverted
                         * @param redeemTokens The number of cTokens to redeem into underlying
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function redeem(uint redeemTokens) external returns (uint) {
                            redeemTokens; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Sender redeems cTokens in exchange for a specified amount of underlying asset
                         * @dev Accrues interest whether or not the operation succeeds, unless reverted
                         * @param redeemAmount The amount of underlying to redeem
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function redeemUnderlying(uint redeemAmount) external returns (uint) {
                            redeemAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                          * @notice Sender borrows assets from the protocol to their own address
                          * @param borrowAmount The amount of the underlying asset to borrow
                          * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                          */
                        function borrow(uint borrowAmount) external returns (uint) {
                            borrowAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Sender repays their own borrow
                         * @param repayAmount The amount to repay
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function repayBorrow(uint repayAmount) external returns (uint) {
                            repayAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Sender repays a borrow belonging to borrower
                         * @param borrower the account with the debt being payed off
                         * @param repayAmount The amount to repay
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint) {
                            borrower; repayAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice The sender liquidates the borrowers collateral.
                         *  The collateral seized is transferred to the liquidator.
                         * @param borrower The borrower of this cToken to be liquidated
                         * @param cTokenCollateral The market in which to seize collateral from the borrower
                         * @param repayAmount The amount of the underlying borrowed asset to repay
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint) {
                            borrower; repayAmount; cTokenCollateral; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Transfer `amount` tokens from `msg.sender` to `dst`
                         * @param dst The address of the destination account
                         * @param amount The number of tokens to transfer
                         * @return Whether or not the transfer succeeded
                         */
                        function transfer(address dst, uint amount) external returns (bool) {
                            dst; amount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Transfer `amount` tokens from `src` to `dst`
                         * @param src The address of the source account
                         * @param dst The address of the destination account
                         * @param amount The number of tokens to transfer
                         * @return Whether or not the transfer succeeded
                         */
                        function transferFrom(address src, address dst, uint256 amount) external returns (bool) {
                            src; dst; amount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Approve `spender` to transfer up to `amount` from `src`
                         * @dev This will overwrite the approval amount for `spender`
                         *  and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve)
                         * @param spender The address of the account which may transfer tokens
                         * @param amount The number of tokens that are approved (-1 means infinite)
                         * @return Whether or not the approval succeeded
                         */
                        function approve(address spender, uint256 amount) external returns (bool) {
                            spender; amount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Get the current allowance from `owner` for `spender`
                         * @param owner The address of the account which owns the tokens to be spent
                         * @param spender The address of the account which may transfer tokens
                         * @return The number of tokens allowed to be spent (-1 means infinite)
                         */
                        function allowance(address owner, address spender) external view returns (uint) {
                            owner; spender; // Shh
                            delegateToViewAndReturn();
                        }
                        /**
                         * @notice Get the token balance of the `owner`
                         * @param owner The address of the account to query
                         * @return The number of tokens owned by `owner`
                         */
                        function balanceOf(address owner) external view returns (uint) {
                            owner; // Shh
                            delegateToViewAndReturn();
                        }
                        /**
                         * @notice Get the underlying balance of the `owner`
                         * @dev This also accrues interest in a transaction
                         * @param owner The address of the account to query
                         * @return The amount of underlying owned by `owner`
                         */
                        function balanceOfUnderlying(address owner) external returns (uint) {
                            owner; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Get a snapshot of the account's balances, and the cached exchange rate
                         * @dev This is used by comptroller to more efficiently perform liquidity checks.
                         * @param account Address of the account to snapshot
                         * @return (possible error, token balance, borrow balance, exchange rate mantissa)
                         */
                        function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint) {
                            account; // Shh
                            delegateToViewAndReturn();
                        }
                        /**
                         * @notice Returns the current per-block borrow interest rate for this cToken
                         * @return The borrow interest rate per block, scaled by 1e18
                         */
                        function borrowRatePerBlock() external view returns (uint) {
                            delegateToViewAndReturn();
                        }
                        /**
                         * @notice Returns the current per-block supply interest rate for this cToken
                         * @return The supply interest rate per block, scaled by 1e18
                         */
                        function supplyRatePerBlock() external view returns (uint) {
                            delegateToViewAndReturn();
                        }
                        /**
                         * @notice Returns the current total borrows plus accrued interest
                         * @return The total borrows with interest
                         */
                        function totalBorrowsCurrent() external returns (uint) {
                            delegateAndReturn();
                        }
                        /**
                         * @notice Accrue interest to updated borrowIndex and then calculate account's borrow balance using the updated borrowIndex
                         * @param account The address whose balance should be calculated after updating borrowIndex
                         * @return The calculated balance
                         */
                        function borrowBalanceCurrent(address account) external returns (uint) {
                            account; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Return the borrow balance of account based on stored data
                         * @param account The address whose balance should be calculated
                         * @return The calculated balance
                         */
                        function borrowBalanceStored(address account) public view returns (uint) {
                            account; // Shh
                            delegateToViewAndReturn();
                        }
                       /**
                         * @notice Accrue interest then return the up-to-date exchange rate
                         * @return Calculated exchange rate scaled by 1e18
                         */
                        function exchangeRateCurrent() public returns (uint) {
                            delegateAndReturn();
                        }
                        /**
                         * @notice Calculates the exchange rate from the underlying to the CToken
                         * @dev This function does not accrue interest before calculating the exchange rate
                         * @return Calculated exchange rate scaled by 1e18
                         */
                        function exchangeRateStored() public view returns (uint) {
                            delegateToViewAndReturn();
                        }
                        /**
                         * @notice Get cash balance of this cToken in the underlying asset
                         * @return The quantity of underlying asset owned by this contract
                         */
                        function getCash() external view returns (uint) {
                            delegateToViewAndReturn();
                        }
                        /**
                          * @notice Applies accrued interest to total borrows and reserves.
                          * @dev This calculates interest accrued from the last checkpointed block
                          *      up to the current block and writes new checkpoint to storage.
                          */
                        function accrueInterest() public returns (uint) {
                            delegateAndReturn();
                        }
                        /**
                         * @notice Transfers collateral tokens (this market) to the liquidator.
                         * @dev Will fail unless called by another cToken during the process of liquidation.
                         *  Its absolutely critical to use msg.sender as the borrowed cToken and not a parameter.
                         * @param liquidator The account receiving seized collateral
                         * @param borrower The account having collateral seized
                         * @param seizeTokens The number of cTokens to seize
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint) {
                            liquidator; borrower; seizeTokens; // Shh
                            delegateAndReturn();
                        }
                        /*** Admin Functions ***/
                        /**
                          * @notice Begins transfer of admin rights. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
                          * @dev Admin function to begin change of admin. The newPendingAdmin must call `_acceptAdmin` to finalize the transfer.
                          * @param newPendingAdmin New pending admin.
                          * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                          */
                        function _setPendingAdmin(address payable newPendingAdmin) external returns (uint) {
                            newPendingAdmin; // Shh
                            delegateAndReturn();
                        }
                        /**
                          * @notice Sets a new comptroller for the market
                          * @dev Admin function to set a new comptroller
                          * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                          */
                        function _setComptroller(ComptrollerInterface newComptroller) public returns (uint) {
                            newComptroller; // Shh
                            delegateAndReturn();
                        }
                        /**
                          * @notice accrues interest and sets a new reserve factor for the protocol using _setReserveFactorFresh
                          * @dev Admin function to accrue interest and set a new reserve factor
                          * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                          */
                        function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint) {
                            newReserveFactorMantissa; // Shh
                            delegateAndReturn();
                        }
                        /**
                          * @notice Accepts transfer of admin rights. msg.sender must be pendingAdmin
                          * @dev Admin function for pending admin to accept role and update admin
                          * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                          */
                        function _acceptAdmin() external returns (uint) {
                            delegateAndReturn();
                        }
                        /**
                         * @notice Accrues interest and adds reserves by transferring from admin
                         * @param addAmount Amount of reserves to add
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function _addReserves(uint addAmount) external returns (uint) {
                            addAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Accrues interest and reduces reserves by transferring to admin
                         * @param reduceAmount Amount of reduction to reserves
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function _reduceReserves(uint reduceAmount) external returns (uint) {
                            reduceAmount; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Accrues interest and updates the interest rate model using _setInterestRateModelFresh
                         * @dev Admin function to accrue interest and update the interest rate model
                         * @param newInterestRateModel the new interest rate model to use
                         * @return uint 0=success, otherwise a failure (see ErrorReporter.sol for details)
                         */
                        function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint) {
                            newInterestRateModel; // Shh
                            delegateAndReturn();
                        }
                        /**
                         * @notice Internal method to delegate execution to another contract
                         * @dev It returns to the external caller whatever the implementation returns or forwards reverts
                         * @param callee The contract to delegatecall
                         * @param data The raw data to delegatecall
                         * @return The returned bytes from the delegatecall
                         */
                        function delegateTo(address callee, bytes memory data) internal returns (bytes memory) {
                            (bool success, bytes memory returnData) = callee.delegatecall(data);
                            assembly {
                                if eq(success, 0) {
                                    revert(add(returnData, 0x20), returndatasize)
                                }
                            }
                            return returnData;
                        }
                        /**
                         * @notice Delegates execution to the implementation contract
                         * @dev It returns to the external caller whatever the implementation returns or forwards reverts
                         * @param data The raw data to delegatecall
                         * @return The returned bytes from the delegatecall
                         */
                        function delegateToImplementation(bytes memory data) public returns (bytes memory) {
                            return delegateTo(implementation, data);
                        }
                        /**
                         * @notice Delegates execution to an implementation contract
                         * @dev It returns to the external caller whatever the implementation returns or forwards reverts
                         *  There are an additional 2 prefix uints from the wrapper returndata, which we ignore since we make an extra hop.
                         * @param data The raw data to delegatecall
                         * @return The returned bytes from the delegatecall
                         */
                        function delegateToViewImplementation(bytes memory data) public view returns (bytes memory) {
                            (bool success, bytes memory returnData) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", data));
                            assembly {
                                if eq(success, 0) {
                                    revert(add(returnData, 0x20), returndatasize)
                                }
                            }
                            return abi.decode(returnData, (bytes));
                        }
                        function delegateToViewAndReturn() private view returns (bytes memory) {
                            (bool success, ) = address(this).staticcall(abi.encodeWithSignature("delegateToImplementation(bytes)", msg.data));
                            assembly {
                                let free_mem_ptr := mload(0x40)
                                returndatacopy(free_mem_ptr, 0, returndatasize)
                                switch success
                                case 0 { revert(free_mem_ptr, returndatasize) }
                                default { return(add(free_mem_ptr, 0x40), returndatasize) }
                            }
                        }
                        function delegateAndReturn() private returns (bytes memory) {
                            (bool success, ) = implementation.delegatecall(msg.data);
                            assembly {
                                let free_mem_ptr := mload(0x40)
                                returndatacopy(free_mem_ptr, 0, returndatasize)
                                switch success
                                case 0 { revert(free_mem_ptr, returndatasize) }
                                default { return(free_mem_ptr, returndatasize) }
                            }
                        }
                        /**
                         * @notice Delegates execution to an implementation contract
                         * @dev It returns to the external caller whatever the implementation returns or forwards reverts
                         */
                        function () external payable {
                            require(msg.value == 0,"CErc20Delegator:fallback: cannot send value to fallback");
                            // delegate all other functions to current implementation
                            delegateAndReturn();
                        }
                    }
                    pragma solidity ^0.5.16;
                    import "./ComptrollerInterface.sol";
                    import "./InterestRateModel.sol";
                    contract CTokenStorage {
                        /**
                         * @dev Guard variable for re-entrancy checks
                         */
                        bool internal _notEntered;
                        /**
                         * @notice EIP-20 token name for this token
                         */
                        string public name;
                        /**
                         * @notice EIP-20 token symbol for this token
                         */
                        string public symbol;
                        /**
                         * @notice EIP-20 token decimals for this token
                         */
                        uint8 public decimals;
                        /**
                         * @notice Maximum borrow rate that can ever be applied (.0005% / block)
                         */
                        uint internal constant borrowRateMaxMantissa = 0.0005e16;
                        /**
                         * @notice Maximum fraction of interest that can be set aside for reserves
                         */
                        uint internal constant reserveFactorMaxMantissa = 1e18;
                        /**
                         * @notice Administrator for this contract
                         */
                        address payable public admin;
                        /**
                         * @notice Pending administrator for this contract
                         */
                        address payable public pendingAdmin;
                        /**
                         * @notice Contract which oversees inter-cToken operations
                         */
                        ComptrollerInterface public comptroller;
                        /**
                         * @notice Model which tells what the current interest rate should be
                         */
                        InterestRateModel public interestRateModel;
                        /**
                         * @notice Initial exchange rate used when minting the first CTokens (used when totalSupply = 0)
                         */
                        uint internal initialExchangeRateMantissa;
                        /**
                         * @notice Fraction of interest currently set aside for reserves
                         */
                        uint public reserveFactorMantissa;
                        /**
                         * @notice Block number that interest was last accrued at
                         */
                        uint public accrualBlockNumber;
                        /**
                         * @notice Accumulator of the total earned interest rate since the opening of the market
                         */
                        uint public borrowIndex;
                        /**
                         * @notice Total amount of outstanding borrows of the underlying in this market
                         */
                        uint public totalBorrows;
                        /**
                         * @notice Total amount of reserves of the underlying held in this market
                         */
                        uint public totalReserves;
                        /**
                         * @notice Total number of tokens in circulation
                         */
                        uint public totalSupply;
                        /**
                         * @notice Official record of token balances for each account
                         */
                        mapping (address => uint) internal accountTokens;
                        /**
                         * @notice Approved token transfer amounts on behalf of others
                         */
                        mapping (address => mapping (address => uint)) internal transferAllowances;
                        /**
                         * @notice Container for borrow balance information
                         * @member principal Total balance (with accrued interest), after applying the most recent balance-changing action
                         * @member interestIndex Global borrowIndex as of the most recent balance-changing action
                         */
                        struct BorrowSnapshot {
                            uint principal;
                            uint interestIndex;
                        }
                        /**
                         * @notice Mapping of account addresses to outstanding borrow balances
                         */
                        mapping(address => BorrowSnapshot) internal accountBorrows;
                    }
                    contract CTokenInterface is CTokenStorage {
                        /**
                         * @notice Indicator that this is a CToken contract (for inspection)
                         */
                        bool public constant isCToken = true;
                        /*** Market Events ***/
                        /**
                         * @notice Event emitted when interest is accrued
                         */
                        event AccrueInterest(uint cashPrior, uint interestAccumulated, uint borrowIndex, uint totalBorrows);
                        /**
                         * @notice Event emitted when tokens are minted
                         */
                        event Mint(address minter, uint mintAmount, uint mintTokens);
                        /**
                         * @notice Event emitted when tokens are redeemed
                         */
                        event Redeem(address redeemer, uint redeemAmount, uint redeemTokens);
                        /**
                         * @notice Event emitted when underlying is borrowed
                         */
                        event Borrow(address borrower, uint borrowAmount, uint accountBorrows, uint totalBorrows);
                        /**
                         * @notice Event emitted when a borrow is repaid
                         */
                        event RepayBorrow(address payer, address borrower, uint repayAmount, uint accountBorrows, uint totalBorrows);
                        /**
                         * @notice Event emitted when a borrow is liquidated
                         */
                        event LiquidateBorrow(address liquidator, address borrower, uint repayAmount, address cTokenCollateral, uint seizeTokens);
                        /*** Admin Events ***/
                        /**
                         * @notice Event emitted when pendingAdmin is changed
                         */
                        event NewPendingAdmin(address oldPendingAdmin, address newPendingAdmin);
                        /**
                         * @notice Event emitted when pendingAdmin is accepted, which means admin is updated
                         */
                        event NewAdmin(address oldAdmin, address newAdmin);
                        /**
                         * @notice Event emitted when comptroller is changed
                         */
                        event NewComptroller(ComptrollerInterface oldComptroller, ComptrollerInterface newComptroller);
                        /**
                         * @notice Event emitted when interestRateModel is changed
                         */
                        event NewMarketInterestRateModel(InterestRateModel oldInterestRateModel, InterestRateModel newInterestRateModel);
                        /**
                         * @notice Event emitted when the reserve factor is changed
                         */
                        event NewReserveFactor(uint oldReserveFactorMantissa, uint newReserveFactorMantissa);
                        /**
                         * @notice Event emitted when the reserves are added
                         */
                        event ReservesAdded(address benefactor, uint addAmount, uint newTotalReserves);
                        /**
                         * @notice Event emitted when the reserves are reduced
                         */
                        event ReservesReduced(address admin, uint reduceAmount, uint newTotalReserves);
                        /**
                         * @notice EIP20 Transfer event
                         */
                        event Transfer(address indexed from, address indexed to, uint amount);
                        /**
                         * @notice EIP20 Approval event
                         */
                        event Approval(address indexed owner, address indexed spender, uint amount);
                        /**
                         * @notice Failure event
                         */
                        event Failure(uint error, uint info, uint detail);
                        /*** User Interface ***/
                        function transfer(address dst, uint amount) external returns (bool);
                        function transferFrom(address src, address dst, uint amount) external returns (bool);
                        function approve(address spender, uint amount) external returns (bool);
                        function allowance(address owner, address spender) external view returns (uint);
                        function balanceOf(address owner) external view returns (uint);
                        function balanceOfUnderlying(address owner) external returns (uint);
                        function getAccountSnapshot(address account) external view returns (uint, uint, uint, uint);
                        function borrowRatePerBlock() external view returns (uint);
                        function supplyRatePerBlock() external view returns (uint);
                        function totalBorrowsCurrent() external returns (uint);
                        function borrowBalanceCurrent(address account) external returns (uint);
                        function borrowBalanceStored(address account) public view returns (uint);
                        function exchangeRateCurrent() public returns (uint);
                        function exchangeRateStored() public view returns (uint);
                        function getCash() external view returns (uint);
                        function accrueInterest() public returns (uint);
                        function seize(address liquidator, address borrower, uint seizeTokens) external returns (uint);
                        /*** Admin Functions ***/
                        function _setPendingAdmin(address payable newPendingAdmin) external returns (uint);
                        function _acceptAdmin() external returns (uint);
                        function _setComptroller(ComptrollerInterface newComptroller) public returns (uint);
                        function _setReserveFactor(uint newReserveFactorMantissa) external returns (uint);
                        function _reduceReserves(uint reduceAmount) external returns (uint);
                        function _setInterestRateModel(InterestRateModel newInterestRateModel) public returns (uint);
                    }
                    contract CErc20Storage {
                        /**
                         * @notice Underlying asset for this CToken
                         */
                        address public underlying;
                    }
                    contract CErc20Interface is CErc20Storage {
                        /*** User Interface ***/
                        function mint(uint mintAmount) external returns (uint);
                        function redeem(uint redeemTokens) external returns (uint);
                        function redeemUnderlying(uint redeemAmount) external returns (uint);
                        function borrow(uint borrowAmount) external returns (uint);
                        function repayBorrow(uint repayAmount) external returns (uint);
                        function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);
                        function liquidateBorrow(address borrower, uint repayAmount, CTokenInterface cTokenCollateral) external returns (uint);
                        /*** Admin Functions ***/
                        function _addReserves(uint addAmount) external returns (uint);
                    }
                    contract CDelegationStorage {
                        /**
                         * @notice Implementation address for this contract
                         */
                        address public implementation;
                    }
                    contract CDelegatorInterface is CDelegationStorage {
                        /**
                         * @notice Emitted when implementation is changed
                         */
                        event NewImplementation(address oldImplementation, address newImplementation);
                        /**
                         * @notice Called by the admin to update the implementation of the delegator
                         * @param implementation_ The address of the new implementation for delegation
                         * @param allowResign Flag to indicate whether to call _resignImplementation on the old implementation
                         * @param becomeImplementationData The encoded bytes data to be passed to _becomeImplementation
                         */
                        function _setImplementation(address implementation_, bool allowResign, bytes memory becomeImplementationData) public;
                    }
                    contract CDelegateInterface is CDelegationStorage {
                        /**
                         * @notice Called by the delegator on a delegate to initialize it for duty
                         * @dev Should revert if any issues arise which make it unfit for delegation
                         * @param data The encoded bytes data for any initialization
                         */
                        function _becomeImplementation(bytes memory data) public;
                        /**
                         * @notice Called by the delegator on a delegate to forfeit its responsibility
                         */
                        function _resignImplementation() public;
                    }
                    pragma solidity ^0.5.16;
                    contract ComptrollerInterface {
                        /// @notice Indicator that this is a Comptroller contract (for inspection)
                        bool public constant isComptroller = true;
                        /*** Assets You Are In ***/
                        function enterMarkets(address[] calldata cTokens) external returns (uint[] memory);
                        function exitMarket(address cToken) external returns (uint);
                        /*** Policy Hooks ***/
                        function mintAllowed(address cToken, address minter, uint mintAmount) external returns (uint);
                        function mintVerify(address cToken, address minter, uint mintAmount, uint mintTokens) external;
                        function redeemAllowed(address cToken, address redeemer, uint redeemTokens) external returns (uint);
                        function redeemVerify(address cToken, address redeemer, uint redeemAmount, uint redeemTokens) external;
                        function borrowAllowed(address cToken, address borrower, uint borrowAmount) external returns (uint);
                        function borrowVerify(address cToken, address borrower, uint borrowAmount) external;
                        function repayBorrowAllowed(
                            address cToken,
                            address payer,
                            address borrower,
                            uint repayAmount) external returns (uint);
                        function repayBorrowVerify(
                            address cToken,
                            address payer,
                            address borrower,
                            uint repayAmount,
                            uint borrowerIndex) external;
                        function liquidateBorrowAllowed(
                            address cTokenBorrowed,
                            address cTokenCollateral,
                            address liquidator,
                            address borrower,
                            uint repayAmount) external returns (uint);
                        function liquidateBorrowVerify(
                            address cTokenBorrowed,
                            address cTokenCollateral,
                            address liquidator,
                            address borrower,
                            uint repayAmount,
                            uint seizeTokens) external;
                        function seizeAllowed(
                            address cTokenCollateral,
                            address cTokenBorrowed,
                            address liquidator,
                            address borrower,
                            uint seizeTokens) external returns (uint);
                        function seizeVerify(
                            address cTokenCollateral,
                            address cTokenBorrowed,
                            address liquidator,
                            address borrower,
                            uint seizeTokens) external;
                        function transferAllowed(address cToken, address src, address dst, uint transferTokens) external returns (uint);
                        function transferVerify(address cToken, address src, address dst, uint transferTokens) external;
                        /*** Liquidity/Liquidation Calculations ***/
                        function liquidateCalculateSeizeTokens(
                            address cTokenBorrowed,
                            address cTokenCollateral,
                            uint repayAmount) external view returns (uint, uint);
                    }
                    pragma solidity ^0.5.16;
                    /**
                      * @title Compound's InterestRateModel Interface
                      * @author Compound
                      */
                    contract InterestRateModel {
                        /// @notice Indicator that this is an InterestRateModel contract (for inspection)
                        bool public constant isInterestRateModel = true;
                        /**
                          * @notice Calculates the current borrow interest rate per block
                          * @param cash The total amount of cash the market has
                          * @param borrows The total amount of borrows the market has outstanding
                          * @param reserves The total amnount of reserves the market has
                          * @return The borrow rate per block (as a percentage, and scaled by 1e18)
                          */
                        function getBorrowRate(uint cash, uint borrows, uint reserves) external view returns (uint);
                        /**
                          * @notice Calculates the current supply interest rate per block
                          * @param cash The total amount of cash the market has
                          * @param borrows The total amount of borrows the market has outstanding
                          * @param reserves The total amnount of reserves the market has
                          * @param reserveFactorMantissa The current reserve factor the market has
                          * @return The supply rate per block (as a percentage, and scaled by 1e18)
                          */
                        function getSupplyRate(uint cash, uint borrows, uint reserves, uint reserveFactorMantissa) external view returns (uint);
                    }
                    

                    File 9 of 11: IdleCompoundV2
                    // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
                     * the optional functions; to access them see {ERC20Detailed}.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                    
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                    
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                    
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                    
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to {approve}. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                    }
                    
                    // File: @openzeppelin/contracts/math/SafeMath.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            return sub(a, b, "SafeMath: subtraction overflow");
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         *
                         * _Available since v2.4.0._
                         */
                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b <= a, errorMessage);
                            uint256 c = a - b;
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                            if (a == 0) {
                                return 0;
                            }
                    
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            return div(a, b, "SafeMath: division by zero");
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            // Solidity only automatically asserts when dividing by 0
                            require(b > 0, errorMessage);
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            return mod(a, b, "SafeMath: modulo by zero");
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts with custom message when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b != 0, errorMessage);
                            return a % b;
                        }
                    }
                    
                    // File: @openzeppelin/contracts/utils/Address.sol
                    
                    pragma solidity ^0.5.5;
                    
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * This test is non-exhaustive, and there may be false-negatives: during the
                         * execution of a contract's constructor, its address will be reported as
                         * not containing a contract.
                         *
                         * IMPORTANT: It is unsafe to assume that an address for which this
                         * function returns false is an externally-owned account (EOA) and not a
                         * contract.
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies in extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                    
                            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                            // for accounts without code, i.e. `keccak256('')`
                            bytes32 codehash;
                            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { codehash := extcodehash(account) }
                            return (codehash != 0x0 && codehash != accountHash);
                        }
                    
                        /**
                         * @dev Converts an `address` into `address payable`. Note that this is
                         * simply a type cast: the actual underlying value is not changed.
                         *
                         * _Available since v2.4.0._
                         */
                        function toPayable(address account) internal pure returns (address payable) {
                            return address(uint160(account));
                        }
                    
                        /**
                         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                         * `recipient`, forwarding all available gas and reverting on errors.
                         *
                         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                         * of certain opcodes, possibly making contracts go over the 2300 gas limit
                         * imposed by `transfer`, making them unable to receive funds via
                         * `transfer`. {sendValue} removes this limitation.
                         *
                         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                         *
                         * IMPORTANT: because control is transferred to `recipient`, care must be
                         * taken to not create reentrancy vulnerabilities. Consider using
                         * {ReentrancyGuard} or the
                         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         *
                         * _Available since v2.4.0._
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                    
                            // solhint-disable-next-line avoid-call-value
                            (bool success, ) = recipient.call.value(amount)("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                    }
                    
                    // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                     * contract returns false). Tokens that return no value (and instead revert or
                     * throw on failure) are also supported, non-reverting calls are assumed to be
                     * successful.
                     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                     */
                    library SafeERC20 {
                        using SafeMath for uint256;
                        using Address for address;
                    
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                    
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                    
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                    
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        /**
                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                         * @param token The token targeted by the call.
                         * @param data The call data (encoded using abi.encode or one of its variants).
                         */
                        function callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves.
                    
                            // A Solidity high level call has three parts:
                            //  1. The target address is checked to verify it contains contract code
                            //  2. The call itself is made, and success asserted
                            //  3. The return value is decoded, which in turn checks the size of the returned data.
                            // solhint-disable-next-line max-line-length
                            require(address(token).isContract(), "SafeERC20: call to non-contract");
                    
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = address(token).call(data);
                            require(success, "SafeERC20: low-level call failed");
                    
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    
                    // File: @openzeppelin/contracts/GSN/Context.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with GSN meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    contract Context {
                        // Empty internal constructor, to prevent people from mistakenly deploying
                        // an instance of this contract, which should be used via inheritance.
                        constructor () internal { }
                        // solhint-disable-previous-line no-empty-blocks
                    
                        function _msgSender() internal view returns (address payable) {
                            return msg.sender;
                        }
                    
                        function _msgData() internal view returns (bytes memory) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    
                    // File: @openzeppelin/contracts/ownership/Ownable.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    contract Ownable is Context {
                        address private _owner;
                    
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                    
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () internal {
                            _owner = _msgSender();
                            emit OwnershipTransferred(address(0), _owner);
                        }
                    
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view returns (address) {
                            return _owner;
                        }
                    
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(isOwner(), "Ownable: caller is not the owner");
                            _;
                        }
                    
                        /**
                         * @dev Returns true if the caller is the current owner.
                         */
                        function isOwner() public view returns (bool) {
                            return _msgSender() == _owner;
                        }
                    
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public onlyOwner {
                            _transferOwnership(newOwner);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         */
                        function _transferOwnership(address newOwner) internal {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    
                    // File: contracts/interfaces/CERC20.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface CERC20 {
                      function mint(uint256 mintAmount) external returns (uint256);
                      function comptroller() external view returns (address);
                      function redeem(uint256 redeemTokens) external returns (uint256);
                      function exchangeRateStored() external view returns (uint256);
                      function supplyRatePerBlock() external view returns (uint256);
                    
                      function borrowRatePerBlock() external view returns (uint256);
                      function totalReserves() external view returns (uint256);
                      function getCash() external view returns (uint256);
                      function totalBorrows() external view returns (uint256);
                      function reserveFactorMantissa() external view returns (uint256);
                      function interestRateModel() external view returns (address);
                    }
                    
                    // File: contracts/interfaces/ILendingProtocol.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface ILendingProtocol {
                      function mint() external returns (uint256);
                      function redeem(address account) external returns (uint256);
                      function nextSupplyRate(uint256 amount) external view returns (uint256);
                      function nextSupplyRateWithParams(uint256[] calldata params) external view returns (uint256);
                      function getAPR() external view returns (uint256);
                      function getPriceInToken() external view returns (uint256);
                      function token() external view returns (address);
                      function underlying() external view returns (address);
                      function availableLiquidity() external view returns (uint256);
                    }
                    
                    // File: contracts/interfaces/WhitePaperInterestRateModel.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface WhitePaperInterestRateModel {
                      function getBorrowRate(uint256 cash, uint256 borrows, uint256 _reserves) external view returns (uint256, uint256);
                      function getSupplyRate(uint256 cash, uint256 borrows, uint256 reserves, uint256 reserveFactorMantissa) external view returns (uint256);
                      function multiplier() external view returns (uint256);
                      function baseRate() external view returns (uint256);
                      function blocksPerYear() external view returns (uint256);
                      function dsrPerBlock() external view returns (uint256);
                    }
                    
                    // File: contracts/wrappers/IdleCompoundV2.sol
                    
                    /**
                     * @title: Compound wrapper
                     * @summary: Used for interacting with Compound. Has
                     *           a common interface with all other protocol wrappers.
                     *           This contract holds assets only during a tx, after tx it should be empty
                     * @author: Idle Labs Inc., idle.finance
                     */
                    pragma solidity 0.5.16;
                    
                    
                    
                    
                    
                    
                    
                    
                    contract IdleCompoundV2 is ILendingProtocol, Ownable {
                      using SafeERC20 for IERC20;
                      using SafeMath for uint256;
                    
                      // protocol token (cToken) address
                      address public token;
                      // underlying token (token eg DAI) address
                      address public underlying;
                      address public idleToken;
                      uint256 public blocksPerYear;
                    
                      /**
                       * @param _token : cToken address
                       * @param _underlying : underlying token (eg DAI) address
                       */
                      constructor(address _token, address _underlying) public {
                        require(_token != address(0) && _underlying != address(0), 'COMP: some addr is 0');
                    
                        token = _token;
                        underlying = _underlying;
                        blocksPerYear = 2371428;
                        IERC20(_underlying).safeApprove(_token, uint256(-1));
                      }
                    
                      /**
                       * Throws if called by any account other than IdleToken contract.
                       */
                      modifier onlyIdle() {
                        require(msg.sender == idleToken, "Ownable: caller is not IdleToken");
                        _;
                      }
                    
                      // onlyOwner
                      /**
                       * sets idleToken address
                       * NOTE: can be called only once. It's not on the constructor because we are deploying this contract
                       *       after the IdleToken contract
                       * @param _idleToken : idleToken address
                       */
                      function setIdleToken(address _idleToken)
                        external onlyOwner {
                          require(idleToken == address(0), "idleToken addr already set");
                          require(_idleToken != address(0), "_idleToken addr is 0");
                          idleToken = _idleToken;
                      }
                    
                      /**
                       * sets blocksPerYear address
                       *
                       * @param _blocksPerYear : avg blocks per year
                       */
                      function setBlocksPerYear(uint256 _blocksPerYear)
                        external onlyOwner {
                          require(_blocksPerYear != 0, "_blocksPerYear is 0");
                          blocksPerYear = _blocksPerYear;
                      }
                      // end onlyOwner
                    
                      /**
                       * Calculate next supply rate for Compound, given an `_amount` supplied (last array param)
                       * and all other params supplied.
                       *
                       * @param params : array with all params needed for calculation
                       * @return : yearly net rate
                       */
                      function nextSupplyRateWithParams(uint256[] calldata params)
                        external view
                        returns (uint256) {
                          CERC20 cToken = CERC20(token);
                          WhitePaperInterestRateModel white = WhitePaperInterestRateModel(cToken.interestRateModel());
                          uint256 ratePerBlock = white.getSupplyRate(
                            params[1].add(params[5]),
                            params[0],
                            params[2],
                            params[3]
                          );
                          return ratePerBlock.mul(params[4]).mul(100);
                      }
                    
                      /**
                       * Calculate next supply rate for Compound, given an `_amount` supplied
                       *
                       * @param _amount : new underlying amount supplied (eg DAI)
                       * @return : yearly net rate
                       */
                      function nextSupplyRate(uint256 _amount)
                        external view
                        returns (uint256) {
                          CERC20 cToken = CERC20(token);
                          WhitePaperInterestRateModel white = WhitePaperInterestRateModel(cToken.interestRateModel());
                          uint256 ratePerBlock = white.getSupplyRate(
                            cToken.getCash().add(_amount),
                            cToken.totalBorrows(),
                            cToken.totalReserves(),
                            cToken.reserveFactorMantissa()
                          );
                          return ratePerBlock.mul(blocksPerYear).mul(100);
                      }
                    
                      /**
                       * @return current price of cToken in underlying
                       */
                      function getPriceInToken()
                        external view
                        returns (uint256) {
                          return CERC20(token).exchangeRateStored();
                      }
                    
                      /**
                       * @return apr : current yearly net rate
                       */
                      function getAPR()
                        external view
                        returns (uint256 apr) {
                          CERC20 cToken = CERC20(token);
                          uint256 cRate = cToken.supplyRatePerBlock(); // interest % per block
                          apr = cRate.mul(blocksPerYear).mul(100);
                      }
                    
                      /**
                       * Gets all underlying tokens in this contract and mints cTokens
                       * tokens are then transferred to msg.sender
                       * NOTE: underlying tokens needs to be sended here before calling this
                       *
                       * @return iTokens minted
                       */
                      function mint()
                        external onlyIdle
                        returns (uint256 cTokens) {
                          uint256 balance = IERC20(underlying).balanceOf(address(this));
                          if (balance == 0) {
                            return cTokens;
                          }
                          // get a handle for the corresponding cToken contract
                          CERC20 _cToken = CERC20(token);
                          // mint the cTokens and assert there is no error
                          require(_cToken.mint(balance) == 0, "Error minting cTokens");
                          // cTokens are now in this contract
                          cTokens = IERC20(token).balanceOf(address(this));
                          // transfer them to the caller
                          IERC20(token).safeTransfer(msg.sender, cTokens);
                      }
                    
                      /**
                       * Gets all cTokens in this contract and redeems underlying tokens.
                       * underlying tokens are then transferred to `_account`
                       * NOTE: iTokens needs to be sended here before calling this
                       *
                       * @return underlying tokens redeemd
                       */
                      function redeem(address _account)
                        external onlyIdle
                        returns (uint256 tokens) {
                          // Funds needs to be sended here before calling this
                          CERC20 _cToken = CERC20(token);
                          IERC20 _underlying = IERC20(underlying);
                          // redeem all underlying sent in this contract
                          require(_cToken.redeem(IERC20(token).balanceOf(address(this))) == 0, "Error redeeming cTokens");
                    
                          tokens = _underlying.balanceOf(address(this));
                          _underlying.safeTransfer(_account, tokens);
                      }
                    
                      function availableLiquidity() external view returns (uint256) {
                        return CERC20(token).getCash();
                      }
                    }

                    File 10 of 11: AToken
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            require(b <= a, "SafeMath: subtraction overflow");
                            uint256 c = a - b;
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
                            if (a == 0) {
                                return 0;
                            }
                    
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Solidity only automatically asserts when dividing by 0
                            require(b > 0, "SafeMath: division by zero");
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            require(b != 0, "SafeMath: modulo by zero");
                            return a % b;
                        }
                    }
                    
                    
                    /**
                    * @title WadRayMath library
                    * @author Aave
                    * @dev Provides mul and div function for wads (decimal numbers with 18 digits precision) and rays (decimals with 27 digits)
                    **/
                    
                    library WadRayMath {
                        using SafeMath for uint256;
                    
                        uint256 internal constant WAD = 1e18;
                        uint256 internal constant halfWAD = WAD / 2;
                    
                        uint256 internal constant RAY = 1e27;
                        uint256 internal constant halfRAY = RAY / 2;
                    
                        uint256 internal constant WAD_RAY_RATIO = 1e9;
                    
                        /**
                        * @return one ray, 1e27
                        **/
                        function ray() internal pure returns (uint256) {
                            return RAY;
                        }
                    
                        /**
                        * @return one wad, 1e18
                        **/
                    
                        function wad() internal pure returns (uint256) {
                            return WAD;
                        }
                    
                        /**
                        * @return half ray, 1e27/2
                        **/
                        function halfRay() internal pure returns (uint256) {
                            return halfRAY;
                        }
                    
                        /**
                        * @return half ray, 1e18/2
                        **/
                        function halfWad() internal pure returns (uint256) {
                            return halfWAD;
                        }
                    
                        /**
                        * @dev multiplies two wad, rounding half up to the nearest wad
                        * @param a wad
                        * @param b wad
                        * @return the result of a*b, in wad
                        **/
                        function wadMul(uint256 a, uint256 b) internal pure returns (uint256) {
                            return halfWAD.add(a.mul(b)).div(WAD);
                        }
                    
                        /**
                        * @dev divides two wad, rounding half up to the nearest wad
                        * @param a wad
                        * @param b wad
                        * @return the result of a/b, in wad
                        **/
                        function wadDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 halfB = b / 2;
                    
                            return halfB.add(a.mul(WAD)).div(b);
                        }
                    
                        /**
                        * @dev multiplies two ray, rounding half up to the nearest ray
                        * @param a ray
                        * @param b ray
                        * @return the result of a*b, in ray
                        **/
                        function rayMul(uint256 a, uint256 b) internal pure returns (uint256) {
                            return halfRAY.add(a.mul(b)).div(RAY);
                        }
                    
                        /**
                        * @dev divides two ray, rounding half up to the nearest ray
                        * @param a ray
                        * @param b ray
                        * @return the result of a/b, in ray
                        **/
                        function rayDiv(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 halfB = b / 2;
                    
                            return halfB.add(a.mul(RAY)).div(b);
                        }
                    
                        /**
                        * @dev casts ray down to wad
                        * @param a ray
                        * @return a casted to wad, rounded half up to the nearest wad
                        **/
                        function rayToWad(uint256 a) internal pure returns (uint256) {
                            uint256 halfRatio = WAD_RAY_RATIO / 2;
                    
                            return halfRatio.add(a).div(WAD_RAY_RATIO);
                        }
                    
                        /**
                        * @dev convert wad up to ray
                        * @param a wad
                        * @return a converted in ray
                        **/
                        function wadToRay(uint256 a) internal pure returns (uint256) {
                            return a.mul(WAD_RAY_RATIO);
                        }
                    
                        /**
                        * @dev calculates base^exp. The code uses the ModExp precompile
                        * @return base^exp, in ray
                        */
                        //solium-disable-next-line
                        function rayPow(uint256 x, uint256 n) internal pure returns (uint256 z) {
                    
                            z = n % 2 != 0 ? x : RAY;
                    
                            for (n /= 2; n != 0; n /= 2) {
                                x = rayMul(x, x);
                    
                                if (n % 2 != 0) {
                                    z = rayMul(z, x);
                                }
                            }
                        }
                    
                    }
                    
                    
                    /**
                     * @dev Contract module that helps prevent reentrant calls to a function.
                     *
                     * Inheriting from `ReentrancyGuard` will make the `nonReentrant` modifier
                     * available, which can be aplied to functions to make sure there are no nested
                     * (reentrant) calls to them.
                     *
                     * Note that because there is a single `nonReentrant` guard, functions marked as
                     * `nonReentrant` may not call one another. This can be worked around by making
                     * those functions `private`, and then adding `external` `nonReentrant` entry
                     * points to them.
                     */
                    contract ReentrancyGuard {
                        /// @dev counter to allow mutex lock with only one SSTORE operation
                        uint256 private _guardCounter;
                    
                        constructor () internal {
                            // The counter starts at one to prevent changing it from zero to a non-zero
                            // value, which is a more expensive operation.
                            _guardCounter = 1;
                        }
                    
                        /**
                         * @dev Prevents a contract from calling itself, directly or indirectly.
                         * Calling a `nonReentrant` function from another `nonReentrant`
                         * function is not supported. It is possible to prevent this from happening
                         * by making the `nonReentrant` function external, and make it call a
                         * `private` function that does the actual work.
                         */
                        modifier nonReentrant() {
                            _guardCounter += 1;
                            uint256 localCounter = _guardCounter;
                            _;
                            require(localCounter == _guardCounter, "ReentrancyGuard: reentrant call");
                        }
                    }
                    
                    
                    
                    /**
                     * @dev Collection of functions related to the address type,
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * This test is non-exhaustive, and there may be false-negatives: during the
                         * execution of a contract's constructor, its address will be reported as
                         * not containing a contract.
                         *
                         * > It is unsafe to assume that an address for which this function returns
                         * false is an externally-owned account (EOA) and not a contract.
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies in extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                    
                            uint256 size;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { size := extcodesize(account) }
                            return size > 0;
                        }
                    }
                    
                    
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
                     * the optional functions; to access them see `ERC20Detailed`.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                    
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                    
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a `Transfer` event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through `transferFrom`. This is
                         * zero by default.
                         *
                         * This value changes when `approve` or `transferFrom` are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                    
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * > Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an `Approval` event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a `Transfer` event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                    
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to `approve`. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                    }
                    
                    /**
                     * @dev Implementation of the `IERC20` interface.
                     *
                     * This implementation is agnostic to the way tokens are created. This means
                     * that a supply mechanism has to be added in a derived contract using `_mint`.
                     * For a generic mechanism see `ERC20Mintable`.
                     *
                     * *For a detailed writeup see our guide [How to implement supply
                     * mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).*
                     *
                     * We have followed general OpenZeppelin guidelines: functions revert instead
                     * of returning `false` on failure. This behavior is nonetheless conventional
                     * and does not conflict with the expectations of ERC20 applications.
                     *
                     * Additionally, an `Approval` event is emitted on calls to `transferFrom`.
                     * This allows applications to reconstruct the allowance for all accounts just
                     * by listening to said events. Other implementations of the EIP may not emit
                     * these events, as it isn't required by the specification.
                     *
                     * Finally, the non-standard `decreaseAllowance` and `increaseAllowance`
                     * functions have been added to mitigate the well-known issues around setting
                     * allowances. See `IERC20.approve`.
                     */
                    contract ERC20 is IERC20 {
                        using SafeMath for uint256;
                    
                        mapping (address => uint256) private _balances;
                    
                        mapping (address => mapping (address => uint256)) private _allowances;
                    
                        uint256 private _totalSupply;
                    
                        /**
                         * @dev See `IERC20.totalSupply`.
                         */
                        function totalSupply() public view returns (uint256) {
                            return _totalSupply;
                        }
                    
                        /**
                         * @dev See `IERC20.balanceOf`.
                         */
                        function balanceOf(address account) public view returns (uint256) {
                            return _balances[account];
                        }
                    
                        /**
                         * @dev See `IERC20.transfer`.
                         *
                         * Requirements:
                         *
                         * - `recipient` cannot be the zero address.
                         * - the caller must have a balance of at least `amount`.
                         */
                        function transfer(address recipient, uint256 amount) public returns (bool) {
                            _transfer(msg.sender, recipient, amount);
                            return true;
                        }
                    
                        /**
                         * @dev See `IERC20.allowance`.
                         */
                        function allowance(address owner, address spender) public view returns (uint256) {
                            return _allowances[owner][spender];
                        }
                    
                        /**
                         * @dev See `IERC20.approve`.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function approve(address spender, uint256 value) public returns (bool) {
                            _approve(msg.sender, spender, value);
                            return true;
                        }
                    
                        /**
                         * @dev See `IERC20.transferFrom`.
                         *
                         * Emits an `Approval` event indicating the updated allowance. This is not
                         * required by the EIP. See the note at the beginning of `ERC20`;
                         *
                         * Requirements:
                         * - `sender` and `recipient` cannot be the zero address.
                         * - `sender` must have a balance of at least `value`.
                         * - the caller must have allowance for `sender`'s tokens of at least
                         * `amount`.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) public returns (bool) {
                            _transfer(sender, recipient, amount);
                            _approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
                            return true;
                        }
                    
                        /**
                         * @dev Atomically increases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to `approve` that can be used as a mitigation for
                         * problems described in `IERC20.approve`.
                         *
                         * Emits an `Approval` event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         */
                        function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
                            _approve(msg.sender, spender, _allowances[msg.sender][spender].add(addedValue));
                            return true;
                        }
                    
                        /**
                         * @dev Atomically decreases the allowance granted to `spender` by the caller.
                         *
                         * This is an alternative to `approve` that can be used as a mitigation for
                         * problems described in `IERC20.approve`.
                         *
                         * Emits an `Approval` event indicating the updated allowance.
                         *
                         * Requirements:
                         *
                         * - `spender` cannot be the zero address.
                         * - `spender` must have allowance for the caller of at least
                         * `subtractedValue`.
                         */
                        function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
                            _approve(msg.sender, spender, _allowances[msg.sender][spender].sub(subtractedValue));
                            return true;
                        }
                    
                        /**
                         * @dev Moves tokens `amount` from `sender` to `recipient`.
                         *
                         * This is internal function is equivalent to `transfer`, and can be used to
                         * e.g. implement automatic token fees, slashing mechanisms, etc.
                         *
                         * Emits a `Transfer` event.
                         *
                         * Requirements:
                         *
                         * - `sender` cannot be the zero address.
                         * - `recipient` cannot be the zero address.
                         * - `sender` must have a balance of at least `amount`.
                         */
                        function _transfer(address sender, address recipient, uint256 amount) internal {
                            require(sender != address(0), "ERC20: transfer from the zero address");
                            require(recipient != address(0), "ERC20: transfer to the zero address");
                    
                            _balances[sender] = _balances[sender].sub(amount);
                            _balances[recipient] = _balances[recipient].add(amount);
                            emit Transfer(sender, recipient, amount);
                        }
                    
                        /** @dev Creates `amount` tokens and assigns them to `account`, increasing
                         * the total supply.
                         *
                         * Emits a `Transfer` event with `from` set to the zero address.
                         *
                         * Requirements
                         *
                         * - `to` cannot be the zero address.
                         */
                        function _mint(address account, uint256 amount) internal {
                            require(account != address(0), "ERC20: mint to the zero address");
                    
                            _totalSupply = _totalSupply.add(amount);
                            _balances[account] = _balances[account].add(amount);
                            emit Transfer(address(0), account, amount);
                        }
                    
                         /**
                         * @dev Destoys `amount` tokens from `account`, reducing the
                         * total supply.
                         *
                         * Emits a `Transfer` event with `to` set to the zero address.
                         *
                         * Requirements
                         *
                         * - `account` cannot be the zero address.
                         * - `account` must have at least `amount` tokens.
                         */
                        function _burn(address account, uint256 value) internal {
                            require(account != address(0), "ERC20: burn from the zero address");
                    
                            _totalSupply = _totalSupply.sub(value);
                            _balances[account] = _balances[account].sub(value);
                            emit Transfer(account, address(0), value);
                        }
                    
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
                         *
                         * This is internal function is equivalent to `approve`, and can be used to
                         * e.g. set automatic allowances for certain subsystems, etc.
                         *
                         * Emits an `Approval` event.
                         *
                         * Requirements:
                         *
                         * - `owner` cannot be the zero address.
                         * - `spender` cannot be the zero address.
                         */
                        function _approve(address owner, address spender, uint256 value) internal {
                            require(owner != address(0), "ERC20: approve from the zero address");
                            require(spender != address(0), "ERC20: approve to the zero address");
                    
                            _allowances[owner][spender] = value;
                            emit Approval(owner, spender, value);
                        }
                    
                        /**
                         * @dev Destoys `amount` tokens from `account`.`amount` is then deducted
                         * from the caller's allowance.
                         *
                         * See `_burn` and `_approve`.
                         */
                        function _burnFrom(address account, uint256 amount) internal {
                            _burn(account, amount);
                            _approve(account, msg.sender, _allowances[account][msg.sender].sub(amount));
                        }
                    }
                    
                    /**
                     * @dev Optional functions from the ERC20 standard.
                     */
                    contract ERC20Detailed is IERC20 {
                        string private _name;
                        string private _symbol;
                        uint8 private _decimals;
                    
                        /**
                         * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
                         * these values are immutable: they can only be set once during
                         * construction.
                         */
                        constructor (string memory name, string memory symbol, uint8 decimals) public {
                            _name = name;
                            _symbol = symbol;
                            _decimals = decimals;
                        }
                    
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() public view returns (string memory) {
                            return _name;
                        }
                    
                        /**
                         * @dev Returns the symbol of the token, usually a shorter version of the
                         * name.
                         */
                        function symbol() public view returns (string memory) {
                            return _symbol;
                        }
                    
                        /**
                         * @dev Returns the number of decimals used to get its user representation.
                         * For example, if `decimals` equals `2`, a balance of `505` tokens should
                         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                         *
                         * Tokens usually opt for a value of 18, imitating the relationship between
                         * Ether and Wei.
                         *
                         * > Note that this information is only used for _display_ purposes: it in
                         * no way affects any of the arithmetic of the contract, including
                         * `IERC20.balanceOf` and `IERC20.transfer`.
                         */
                        function decimals() public view returns (uint8) {
                            return _decimals;
                        }
                    }
                    
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                     * contract returns false). Tokens that return no value (and instead revert or
                     * throw on failure) are also supported, non-reverting calls are assumed to be
                     * successful.
                     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                     */
                    library SafeERC20 {
                        using SafeMath for uint256;
                        using Address for address;
                    
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                    
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                    
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                    
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).sub(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        /**
                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                         * @param token The token targeted by the call.
                         * @param data The call data (encoded using abi.encode or one of its variants).
                         */
                        function callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves.
                    
                            // A Solidity high level call has three parts:
                            //  1. The target address is checked to verify it contains contract code
                            //  2. The call itself is made, and success asserted
                            //  3. The return value is decoded, which in turn checks the size of the returned data.
                            // solhint-disable-next-line max-line-length
                            require(address(token).isContract(), "SafeERC20: call to non-contract");
                    
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = address(token).call(data);
                            require(success, "SafeERC20: low-level call failed");
                    
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    
                    /**
                     * @title VersionedInitializable
                     *
                     * @dev Helper contract to support initializer functions. To use it, replace
                     * the constructor with a function that has the `initializer` modifier.
                     * WARNING: Unlike constructors, initializer functions must be manually
                     * invoked. This applies both to deploying an Initializable contract, as well
                     * as extending an Initializable contract via inheritance.
                     * WARNING: When used with inheritance, manual care must be taken to not invoke
                     * a parent initializer twice, or ensure that all initializers are idempotent,
                     * because this is not dealt with automatically as with constructors.
                     *
                     * @author Aave, inspired by the OpenZeppelin Initializable contract
                     */
                    contract VersionedInitializable {
                        /**
                       * @dev Indicates that the contract has been initialized.
                       */
                        uint256 private lastInitializedRevision = 0;
                    
                        /**
                       * @dev Indicates that the contract is in the process of being initialized.
                       */
                        bool private initializing;
                    
                        /**
                       * @dev Modifier to use in the initializer function of a contract.
                       */
                        modifier initializer() {
                            uint256 revision = getRevision();
                            require(initializing || isConstructor() || revision > lastInitializedRevision, "Contract instance has already been initialized");
                    
                            bool isTopLevelCall = !initializing;
                            if (isTopLevelCall) {
                                initializing = true;
                                lastInitializedRevision = revision;
                            }
                    
                            _;
                    
                            if (isTopLevelCall) {
                                initializing = false;
                            }
                        }
                    
                        /// @dev returns the revision number of the contract.
                        /// Needs to be defined in the inherited class as a constant.
                        function getRevision() internal pure returns(uint256);
                    
                    
                        /// @dev Returns true if and only if the function is running in the constructor
                        function isConstructor() private view returns (bool) {
                            // extcodesize checks the size of the code stored in an address, and
                            // address returns the current address. Since the code is still not
                            // deployed when running a constructor, any checks on its code size will
                            // yield zero, making it an effective way to detect if a contract is
                            // under construction or not.
                            uint256 cs;
                            //solium-disable-next-line
                            assembly {
                                cs := extcodesize(address)
                            }
                            return cs == 0;
                        }
                    
                        // Reserved storage space to allow for layout changes in the future.
                        uint256[50] private ______gap;
                    }
                    
                    
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be aplied to your functions to restrict their use to
                     * the owner.
                     */
                    contract Ownable {
                        address private _owner;
                    
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                    
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () internal {
                            _owner = msg.sender;
                            emit OwnershipTransferred(address(0), _owner);
                        }
                    
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view returns (address) {
                            return _owner;
                        }
                    
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(isOwner(), "Ownable: caller is not the owner");
                            _;
                        }
                    
                        /**
                         * @dev Returns true if the caller is the current owner.
                         */
                        function isOwner() public view returns (bool) {
                            return msg.sender == _owner;
                        }
                    
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * > Note: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public onlyOwner {
                            _transferOwnership(newOwner);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         */
                        function _transferOwnership(address newOwner) internal {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    
                    /**
                     * @title Proxy
                     * @dev Implements delegation of calls to other contracts, with proper
                     * forwarding of return values and bubbling of failures.
                     * It defines a fallback function that delegates all calls to the address
                     * returned by the abstract _implementation() internal function.
                     */
                    contract Proxy {
                        /**
                       * @dev Fallback function.
                       * Implemented entirely in `_fallback`.
                       */
                        function() external payable {
                            _fallback();
                        }
                    
                        /**
                       * @return The Address of the implementation.
                       */
                        function _implementation() internal view returns (address);
                    
                        /**
                       * @dev Delegates execution to an implementation contract.
                       * This is a low level function that doesn't return to its internal call site.
                       * It will return to the external caller whatever the implementation returns.
                       * @param implementation Address to delegate.
                       */
                        function _delegate(address implementation) internal {
                            //solium-disable-next-line
                            assembly {
                                // Copy msg.data. We take full control of memory in this inline assembly
                                // block because it will not return to Solidity code. We overwrite the
                                // Solidity scratch pad at memory position 0.
                                calldatacopy(0, 0, calldatasize)
                    
                                // Call the implementation.
                                // out and outsize are 0 because we don't know the size yet.
                                let result := delegatecall(gas, implementation, 0, calldatasize, 0, 0)
                    
                                // Copy the returned data.
                                returndatacopy(0, 0, returndatasize)
                    
                                switch result
                                    // delegatecall returns 0 on error.
                                    case 0 {
                                        revert(0, returndatasize)
                                    }
                                    default {
                                        return(0, returndatasize)
                                    }
                            }
                        }
                    
                        /**
                       * @dev Function that is run as the first thing in the fallback function.
                       * Can be redefined in derived contracts to add functionality.
                       * Redefinitions must call super._willFallback().
                       */
                        function _willFallback() internal {}
                    
                        /**
                       * @dev fallback implementation.
                       * Extracted to enable manual triggering.
                       */
                        function _fallback() internal {
                            _willFallback();
                            _delegate(_implementation());
                        }
                    }
                    
                    /**
                     * @title BaseUpgradeabilityProxy
                     * @dev This contract implements a proxy that allows to change the
                     * implementation address to which it will delegate.
                     * Such a change is called an implementation upgrade.
                     */
                    contract BaseUpgradeabilityProxy is Proxy {
                        /**
                       * @dev Emitted when the implementation is upgraded.
                       * @param implementation Address of the new implementation.
                       */
                        event Upgraded(address indexed implementation);
                    
                        /**
                       * @dev Storage slot with the address of the current implementation.
                       * This is the keccak-256 hash of "eip1967.proxy.implementation" subtracted by 1, and is
                       * validated in the constructor.
                       */
                        bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
                    
                        /**
                       * @dev Returns the current implementation.
                       * @return Address of the current implementation
                       */
                        function _implementation() internal view returns (address impl) {
                            bytes32 slot = IMPLEMENTATION_SLOT;
                            //solium-disable-next-line
                            assembly {
                                impl := sload(slot)
                            }
                        }
                    
                        /**
                       * @dev Upgrades the proxy to a new implementation.
                       * @param newImplementation Address of the new implementation.
                       */
                        function _upgradeTo(address newImplementation) internal {
                            _setImplementation(newImplementation);
                            emit Upgraded(newImplementation);
                        }
                    
                        /**
                       * @dev Sets the implementation address of the proxy.
                       * @param newImplementation Address of the new implementation.
                       */
                        function _setImplementation(address newImplementation) internal {
                            require(
                                Address.isContract(newImplementation),
                                "Cannot set a proxy implementation to a non-contract address"
                            );
                    
                            bytes32 slot = IMPLEMENTATION_SLOT;
                    
                            //solium-disable-next-line
                            assembly {
                                sstore(slot, newImplementation)
                            }
                        }
                    }
                    
                    
                    
                    /**
                     * @title BaseAdminUpgradeabilityProxy
                     * @dev This contract combines an upgradeability proxy with an authorization
                     * mechanism for administrative tasks.
                     * All external functions in this contract must be guarded by the
                     * `ifAdmin` modifier. See ethereum/solidity#3864 for a Solidity
                     * feature proposal that would enable this to be done automatically.
                     */
                    contract BaseAdminUpgradeabilityProxy is BaseUpgradeabilityProxy {
                        /**
                       * @dev Emitted when the administration has been transferred.
                       * @param previousAdmin Address of the previous admin.
                       * @param newAdmin Address of the new admin.
                       */
                        event AdminChanged(address previousAdmin, address newAdmin);
                    
                        /**
                       * @dev Storage slot with the admin of the contract.
                       * This is the keccak-256 hash of "eip1967.proxy.admin" subtracted by 1, and is
                       * validated in the constructor.
                       */
                    
                        bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
                    
                        /**
                       * @dev Modifier to check whether the `msg.sender` is the admin.
                       * If it is, it will run the function. Otherwise, it will delegate the call
                       * to the implementation.
                       */
                        modifier ifAdmin() {
                            if (msg.sender == _admin()) {
                                _;
                            } else {
                                _fallback();
                            }
                        }
                    
                        /**
                       * @return The address of the proxy admin.
                       */
                        function admin() external ifAdmin returns (address) {
                            return _admin();
                        }
                    
                        /**
                       * @return The address of the implementation.
                       */
                        function implementation() external ifAdmin returns (address) {
                            return _implementation();
                        }
                    
                        /**
                       * @dev Changes the admin of the proxy.
                       * Only the current admin can call this function.
                       * @param newAdmin Address to transfer proxy administration to.
                       */
                        function changeAdmin(address newAdmin) external ifAdmin {
                            require(newAdmin != address(0), "Cannot change the admin of a proxy to the zero address");
                            emit AdminChanged(_admin(), newAdmin);
                            _setAdmin(newAdmin);
                        }
                    
                        /**
                       * @dev Upgrade the backing implementation of the proxy.
                       * Only the admin can call this function.
                       * @param newImplementation Address of the new implementation.
                       */
                        function upgradeTo(address newImplementation) external ifAdmin {
                            _upgradeTo(newImplementation);
                        }
                    
                        /**
                       * @dev Upgrade the backing implementation of the proxy and call a function
                       * on the new implementation.
                       * This is useful to initialize the proxied contract.
                       * @param newImplementation Address of the new implementation.
                       * @param data Data to send as msg.data in the low level call.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       */
                        function upgradeToAndCall(address newImplementation, bytes calldata data) external payable ifAdmin {
                            _upgradeTo(newImplementation);
                            (bool success, ) = newImplementation.delegatecall(data);
                            require(success);
                        }
                    
                        /**
                       * @return The admin slot.
                       */
                        function _admin() internal view returns (address adm) {
                            bytes32 slot = ADMIN_SLOT;
                            //solium-disable-next-line
                            assembly {
                                adm := sload(slot)
                            }
                        }
                    
                        /**
                       * @dev Sets the address of the proxy admin.
                       * @param newAdmin Address of the new proxy admin.
                       */
                        function _setAdmin(address newAdmin) internal {
                            bytes32 slot = ADMIN_SLOT;
                            //solium-disable-next-line
                            assembly {
                                sstore(slot, newAdmin)
                            }
                        }
                    
                        /**
                       * @dev Only fall back when the sender is not the admin.
                       */
                        function _willFallback() internal {
                            require(msg.sender != _admin(), "Cannot call fallback function from the proxy admin");
                            super._willFallback();
                        }
                    }
                    
                    /**
                     * @title UpgradeabilityProxy
                     * @dev Extends BaseUpgradeabilityProxy with a constructor for initializing
                     * implementation and init data.
                     */
                    contract UpgradeabilityProxy is BaseUpgradeabilityProxy {
                        /**
                       * @dev Contract constructor.
                       * @param _logic Address of the initial implementation.
                       * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                       */
                        constructor(address _logic, bytes memory _data) public payable {
                            assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                            _setImplementation(_logic);
                            if (_data.length > 0) {
                                (bool success, ) = _logic.delegatecall(_data);
                                require(success);
                            }
                        }
                    }
                    
                    
                    
                    
                    /**
                     * @title AdminUpgradeabilityProxy
                     * @dev Extends from BaseAdminUpgradeabilityProxy with a constructor for 
                     * initializing the implementation, admin, and init data.
                     */
                    contract AdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, UpgradeabilityProxy {
                        /**
                       * Contract constructor.
                       * @param _logic address of the initial implementation.
                       * @param _admin Address of the proxy administrator.
                       * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                       */
                        constructor(address _logic, address _admin, bytes memory _data) public payable UpgradeabilityProxy(_logic, _data) {
                            assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                            _setAdmin(_admin);
                        }
                    }
                    
                    
                    
                    /**
                     * @title InitializableUpgradeabilityProxy
                     * @dev Extends BaseUpgradeabilityProxy with an initializer for initializing
                     * implementation and init data.
                     */
                    contract InitializableUpgradeabilityProxy is BaseUpgradeabilityProxy {
                        /**
                       * @dev Contract initializer.
                       * @param _logic Address of the initial implementation.
                       * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                       */
                        function initialize(address _logic, bytes memory _data) public payable {
                            require(_implementation() == address(0));
                            assert(IMPLEMENTATION_SLOT == bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1));
                            _setImplementation(_logic);
                            if (_data.length > 0) {
                                (bool success, ) = _logic.delegatecall(_data);
                                require(success);
                            }
                        }
                    }
                    
                    contract AddressStorage {
                        mapping(bytes32 => address) private addresses;
                    
                        function getAddress(bytes32 _key) public view returns (address) {
                            return addresses[_key];
                        }
                    
                        function _setAddress(bytes32 _key, address _value) internal {
                            addresses[_key] = _value;
                        }
                    
                    }
                    
                    /**
                     * @title InitializableAdminUpgradeabilityProxy
                     * @dev Extends from BaseAdminUpgradeabilityProxy with an initializer for 
                     * initializing the implementation, admin, and init data.
                     */
                    contract InitializableAdminUpgradeabilityProxy is BaseAdminUpgradeabilityProxy, InitializableUpgradeabilityProxy {
                        /**
                       * Contract initializer.
                       * @param _logic address of the initial implementation.
                       * @param _admin Address of the proxy administrator.
                       * @param _data Data to send as msg.data to the implementation to initialize the proxied contract.
                       * It should include the signature and the parameters of the function to be called, as described in
                       * https://solidity.readthedocs.io/en/v0.4.24/abi-spec.html#function-selector-and-argument-encoding.
                       * This parameter is optional, if no data is given the initialization call to proxied contract will be skipped.
                       */
                        function initialize(address _logic, address _admin, bytes memory _data) public payable {
                            require(_implementation() == address(0));
                            InitializableUpgradeabilityProxy.initialize(_logic, _data);
                            assert(ADMIN_SLOT == bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1));
                            _setAdmin(_admin);
                        }
                    }
                    
                    
                    
                    /**
                    @title ILendingPoolAddressesProvider interface
                    @notice provides the interface to fetch the LendingPoolCore address
                     */
                    
                    contract ILendingPoolAddressesProvider {
                    
                        function getLendingPool() public view returns (address);
                        function setLendingPoolImpl(address _pool) public;
                    
                        function getLendingPoolCore() public view returns (address payable);
                        function setLendingPoolCoreImpl(address _lendingPoolCore) public;
                    
                        function getLendingPoolConfigurator() public view returns (address);
                        function setLendingPoolConfiguratorImpl(address _configurator) public;
                    
                        function getLendingPoolDataProvider() public view returns (address);
                        function setLendingPoolDataProviderImpl(address _provider) public;
                    
                        function getLendingPoolParametersProvider() public view returns (address);
                        function setLendingPoolParametersProviderImpl(address _parametersProvider) public;
                    
                        function getTokenDistributor() public view returns (address);
                        function setTokenDistributor(address _tokenDistributor) public;
                    
                    
                        function getFeeProvider() public view returns (address);
                        function setFeeProviderImpl(address _feeProvider) public;
                    
                        function getLendingPoolLiquidationManager() public view returns (address);
                        function setLendingPoolLiquidationManager(address _manager) public;
                    
                        function getLendingPoolManager() public view returns (address);
                        function setLendingPoolManager(address _lendingPoolManager) public;
                    
                        function getPriceOracle() public view returns (address);
                        function setPriceOracle(address _priceOracle) public;
                    
                        function getLendingRateOracle() public view returns (address);
                        function setLendingRateOracle(address _lendingRateOracle) public;
                    
                    }
                    
                    
                    
                    
                    /**
                    * @title LendingPoolAddressesProvider contract
                    * @notice Is the main registry of the protocol. All the different components of the protocol are accessible
                    * through the addresses provider.
                    * @author Aave
                    **/
                    
                    contract LendingPoolAddressesProvider is Ownable, ILendingPoolAddressesProvider, AddressStorage {
                        //events
                        event LendingPoolUpdated(address indexed newAddress);
                        event LendingPoolCoreUpdated(address indexed newAddress);
                        event LendingPoolParametersProviderUpdated(address indexed newAddress);
                        event LendingPoolManagerUpdated(address indexed newAddress);
                        event LendingPoolConfiguratorUpdated(address indexed newAddress);
                        event LendingPoolLiquidationManagerUpdated(address indexed newAddress);
                        event LendingPoolDataProviderUpdated(address indexed newAddress);
                        event EthereumAddressUpdated(address indexed newAddress);
                        event PriceOracleUpdated(address indexed newAddress);
                        event LendingRateOracleUpdated(address indexed newAddress);
                        event FeeProviderUpdated(address indexed newAddress);
                        event TokenDistributorUpdated(address indexed newAddress);
                    
                        event ProxyCreated(bytes32 id, address indexed newAddress);
                    
                        bytes32 private constant LENDING_POOL = "LENDING_POOL";
                        bytes32 private constant LENDING_POOL_CORE = "LENDING_POOL_CORE";
                        bytes32 private constant LENDING_POOL_CONFIGURATOR = "LENDING_POOL_CONFIGURATOR";
                        bytes32 private constant LENDING_POOL_PARAMETERS_PROVIDER = "PARAMETERS_PROVIDER";
                        bytes32 private constant LENDING_POOL_MANAGER = "LENDING_POOL_MANAGER";
                        bytes32 private constant LENDING_POOL_LIQUIDATION_MANAGER = "LIQUIDATION_MANAGER";
                        bytes32 private constant LENDING_POOL_FLASHLOAN_PROVIDER = "FLASHLOAN_PROVIDER";
                        bytes32 private constant DATA_PROVIDER = "DATA_PROVIDER";
                        bytes32 private constant ETHEREUM_ADDRESS = "ETHEREUM_ADDRESS";
                        bytes32 private constant PRICE_ORACLE = "PRICE_ORACLE";
                        bytes32 private constant LENDING_RATE_ORACLE = "LENDING_RATE_ORACLE";
                        bytes32 private constant FEE_PROVIDER = "FEE_PROVIDER";
                        bytes32 private constant WALLET_BALANCE_PROVIDER = "WALLET_BALANCE_PROVIDER";
                        bytes32 private constant TOKEN_DISTRIBUTOR = "TOKEN_DISTRIBUTOR";
                    
                    
                        /**
                        * @dev returns the address of the LendingPool proxy
                        * @return the lending pool proxy address
                        **/
                        function getLendingPool() public view returns (address) {
                            return getAddress(LENDING_POOL);
                        }
                    
                    
                        /**
                        * @dev updates the implementation of the lending pool
                        * @param _pool the new lending pool implementation
                        **/
                        function setLendingPoolImpl(address _pool) public onlyOwner {
                            updateImplInternal(LENDING_POOL, _pool);
                            emit LendingPoolUpdated(_pool);
                        }
                    
                        /**
                        * @dev returns the address of the LendingPoolCore proxy
                        * @return the lending pool core proxy address
                         */
                        function getLendingPoolCore() public view returns (address payable) {
                            address payable core = address(uint160(getAddress(LENDING_POOL_CORE)));
                            return core;
                        }
                    
                        /**
                        * @dev updates the implementation of the lending pool core
                        * @param _lendingPoolCore the new lending pool core implementation
                        **/
                        function setLendingPoolCoreImpl(address _lendingPoolCore) public onlyOwner {
                            updateImplInternal(LENDING_POOL_CORE, _lendingPoolCore);
                            emit LendingPoolCoreUpdated(_lendingPoolCore);
                        }
                    
                        /**
                        * @dev returns the address of the LendingPoolConfigurator proxy
                        * @return the lending pool configurator proxy address
                        **/
                        function getLendingPoolConfigurator() public view returns (address) {
                            return getAddress(LENDING_POOL_CONFIGURATOR);
                        }
                    
                        /**
                        * @dev updates the implementation of the lending pool configurator
                        * @param _configurator the new lending pool configurator implementation
                        **/
                        function setLendingPoolConfiguratorImpl(address _configurator) public onlyOwner {
                            updateImplInternal(LENDING_POOL_CONFIGURATOR, _configurator);
                            emit LendingPoolConfiguratorUpdated(_configurator);
                        }
                    
                        /**
                        * @dev returns the address of the LendingPoolDataProvider proxy
                        * @return the lending pool data provider proxy address
                         */
                        function getLendingPoolDataProvider() public view returns (address) {
                            return getAddress(DATA_PROVIDER);
                        }
                    
                        /**
                        * @dev updates the implementation of the lending pool data provider
                        * @param _provider the new lending pool data provider implementation
                        **/
                        function setLendingPoolDataProviderImpl(address _provider) public onlyOwner {
                            updateImplInternal(DATA_PROVIDER, _provider);
                            emit LendingPoolDataProviderUpdated(_provider);
                        }
                    
                        /**
                        * @dev returns the address of the LendingPoolParametersProvider proxy
                        * @return the address of the Lending pool parameters provider proxy
                        **/
                        function getLendingPoolParametersProvider() public view returns (address) {
                            return getAddress(LENDING_POOL_PARAMETERS_PROVIDER);
                        }
                    
                        /**
                        * @dev updates the implementation of the lending pool parameters provider
                        * @param _parametersProvider the new lending pool parameters provider implementation
                        **/
                        function setLendingPoolParametersProviderImpl(address _parametersProvider) public onlyOwner {
                            updateImplInternal(LENDING_POOL_PARAMETERS_PROVIDER, _parametersProvider);
                            emit LendingPoolParametersProviderUpdated(_parametersProvider);
                        }
                    
                        /**
                        * @dev returns the address of the FeeProvider proxy
                        * @return the address of the Fee provider proxy
                        **/
                        function getFeeProvider() public view returns (address) {
                            return getAddress(FEE_PROVIDER);
                        }
                    
                        /**
                        * @dev updates the implementation of the FeeProvider proxy
                        * @param _feeProvider the new lending pool fee provider implementation
                        **/
                        function setFeeProviderImpl(address _feeProvider) public onlyOwner {
                            updateImplInternal(FEE_PROVIDER, _feeProvider);
                            emit FeeProviderUpdated(_feeProvider);
                        }
                    
                        /**
                        * @dev returns the address of the LendingPoolLiquidationManager. Since the manager is used
                        * through delegateCall within the LendingPool contract, the proxy contract pattern does not work properly hence
                        * the addresses are changed directly.
                        * @return the address of the Lending pool liquidation manager
                        **/
                    
                        function getLendingPoolLiquidationManager() public view returns (address) {
                            return getAddress(LENDING_POOL_LIQUIDATION_MANAGER);
                        }
                    
                        /**
                        * @dev updates the address of the Lending pool liquidation manager
                        * @param _manager the new lending pool liquidation manager address
                        **/
                        function setLendingPoolLiquidationManager(address _manager) public onlyOwner {
                            _setAddress(LENDING_POOL_LIQUIDATION_MANAGER, _manager);
                            emit LendingPoolLiquidationManagerUpdated(_manager);
                        }
                    
                        /**
                        * @dev the functions below are storing specific addresses that are outside the context of the protocol
                        * hence the upgradable proxy pattern is not used
                        **/
                    
                    
                        function getLendingPoolManager() public view returns (address) {
                            return getAddress(LENDING_POOL_MANAGER);
                        }
                    
                        function setLendingPoolManager(address _lendingPoolManager) public onlyOwner {
                            _setAddress(LENDING_POOL_MANAGER, _lendingPoolManager);
                            emit LendingPoolManagerUpdated(_lendingPoolManager);
                        }
                    
                        function getPriceOracle() public view returns (address) {
                            return getAddress(PRICE_ORACLE);
                        }
                    
                        function setPriceOracle(address _priceOracle) public onlyOwner {
                            _setAddress(PRICE_ORACLE, _priceOracle);
                            emit PriceOracleUpdated(_priceOracle);
                        }
                    
                        function getLendingRateOracle() public view returns (address) {
                            return getAddress(LENDING_RATE_ORACLE);
                        }
                    
                        function setLendingRateOracle(address _lendingRateOracle) public onlyOwner {
                            _setAddress(LENDING_RATE_ORACLE, _lendingRateOracle);
                            emit LendingRateOracleUpdated(_lendingRateOracle);
                        }
                    
                    
                        function getTokenDistributor() public view returns (address) {
                            return getAddress(TOKEN_DISTRIBUTOR);
                        }
                    
                        function setTokenDistributor(address _tokenDistributor) public onlyOwner {
                            _setAddress(TOKEN_DISTRIBUTOR, _tokenDistributor);
                            emit TokenDistributorUpdated(_tokenDistributor);
                        }
                    
                    
                        /**
                        * @dev internal function to update the implementation of a specific component of the protocol
                        * @param _id the id of the contract to be updated
                        * @param _newAddress the address of the new implementation
                        **/
                        function updateImplInternal(bytes32 _id, address _newAddress) internal {
                            address payable proxyAddress = address(uint160(getAddress(_id)));
                    
                            InitializableAdminUpgradeabilityProxy proxy = InitializableAdminUpgradeabilityProxy(proxyAddress);
                            bytes memory params = abi.encodeWithSignature("initialize(address)", address(this));
                    
                            if (proxyAddress == address(0)) {
                                proxy = new InitializableAdminUpgradeabilityProxy();
                                proxy.initialize(_newAddress, address(this), params);
                                _setAddress(_id, address(proxy));
                                emit ProxyCreated(_id, address(proxy));
                            } else {
                                proxy.upgradeToAndCall(_newAddress, params);
                            }
                    
                        }
                    }
                    
                    contract UintStorage {
                        mapping(bytes32 => uint256) private uints;
                    
                        function getUint(bytes32 _key) public view returns (uint256) {
                            return uints[_key];
                        }
                    
                        function _setUint(bytes32 _key, uint256 _value) internal {
                            uints[_key] = _value;
                        }
                    
                    }
                    
                    
                    /**
                    * @title LendingPoolParametersProvider
                    * @author Aave
                    * @notice stores the configuration parameters of the Lending Pool contract
                    **/
                    
                    contract LendingPoolParametersProvider is VersionedInitializable {
                    
                        uint256 private constant MAX_STABLE_RATE_BORROW_SIZE_PERCENT = 25;
                        uint256 private constant REBALANCE_DOWN_RATE_DELTA = (1e27)/5;
                        uint256 private constant FLASHLOAN_FEE_TOTAL = 35;
                        uint256 private constant FLASHLOAN_FEE_PROTOCOL = 3000;
                    
                        uint256 constant private DATA_PROVIDER_REVISION = 0x1;
                    
                        function getRevision() internal pure returns(uint256) {
                            return DATA_PROVIDER_REVISION;
                        }
                    
                        /**
                        * @dev initializes the LendingPoolParametersProvider after it's added to the proxy
                        * @param _addressesProvider the address of the LendingPoolAddressesProvider
                        */
                        function initialize(address _addressesProvider) public initializer {
                        }
                        /**
                        * @dev returns the maximum stable rate borrow size, in percentage of the available liquidity.
                        **/
                        function getMaxStableRateBorrowSizePercent() external pure returns (uint256)  {
                            return MAX_STABLE_RATE_BORROW_SIZE_PERCENT;
                        }
                    
                        /**
                        * @dev returns the delta between the current stable rate and the user stable rate at
                        *      which the borrow position of the user will be rebalanced (scaled down)
                        **/
                        function getRebalanceDownRateDelta() external pure returns (uint256) {
                            return REBALANCE_DOWN_RATE_DELTA;
                        }
                    
                        /**
                        * @dev returns the fee applied to a flashloan and the portion to redirect to the protocol, in basis points.
                        **/
                        function getFlashLoanFeesInBips() external pure returns (uint256, uint256) {
                            return (FLASHLOAN_FEE_TOTAL, FLASHLOAN_FEE_PROTOCOL);
                        }
                    }
                    
                    /**
                    * @title CoreLibrary library
                    * @author Aave
                    * @notice Defines the data structures of the reserves and the user data
                    **/
                    library CoreLibrary {
                        using SafeMath for uint256;
                        using WadRayMath for uint256;
                    
                        enum InterestRateMode {NONE, STABLE, VARIABLE}
                    
                        uint256 internal constant SECONDS_PER_YEAR = 365 days;
                    
                        struct UserReserveData {
                            //principal amount borrowed by the user.
                            uint256 principalBorrowBalance;
                            //cumulated variable borrow index for the user. Expressed in ray
                            uint256 lastVariableBorrowCumulativeIndex;
                            //origination fee cumulated by the user
                            uint256 originationFee;
                            // stable borrow rate at which the user has borrowed. Expressed in ray
                            uint256 stableBorrowRate;
                            uint40 lastUpdateTimestamp;
                            //defines if a specific deposit should or not be used as a collateral in borrows
                            bool useAsCollateral;
                        }
                    
                        struct ReserveData {
                            /**
                            * @dev refer to the whitepaper, section 1.1 basic concepts for a formal description of these properties.
                            **/
                            //the liquidity index. Expressed in ray
                            uint256 lastLiquidityCumulativeIndex;
                            //the current supply rate. Expressed in ray
                            uint256 currentLiquidityRate;
                            //the total borrows of the reserve at a stable rate. Expressed in the currency decimals
                            uint256 totalBorrowsStable;
                            //the total borrows of the reserve at a variable rate. Expressed in the currency decimals
                            uint256 totalBorrowsVariable;
                            //the current variable borrow rate. Expressed in ray
                            uint256 currentVariableBorrowRate;
                            //the current stable borrow rate. Expressed in ray
                            uint256 currentStableBorrowRate;
                            //the current average stable borrow rate (weighted average of all the different stable rate loans). Expressed in ray
                            uint256 currentAverageStableBorrowRate;
                            //variable borrow index. Expressed in ray
                            uint256 lastVariableBorrowCumulativeIndex;
                            //the ltv of the reserve. Expressed in percentage (0-100)
                            uint256 baseLTVasCollateral;
                            //the liquidation threshold of the reserve. Expressed in percentage (0-100)
                            uint256 liquidationThreshold;
                            //the liquidation bonus of the reserve. Expressed in percentage
                            uint256 liquidationBonus;
                            //the decimals of the reserve asset
                            uint256 decimals;
                            /**
                            * @dev address of the aToken representing the asset
                            **/
                            address aTokenAddress;
                            /**
                            * @dev address of the interest rate strategy contract
                            **/
                            address interestRateStrategyAddress;
                            uint40 lastUpdateTimestamp;
                            // borrowingEnabled = true means users can borrow from this reserve
                            bool borrowingEnabled;
                            // usageAsCollateralEnabled = true means users can use this reserve as collateral
                            bool usageAsCollateralEnabled;
                            // isStableBorrowRateEnabled = true means users can borrow at a stable rate
                            bool isStableBorrowRateEnabled;
                            // isActive = true means the reserve has been activated and properly configured
                            bool isActive;
                            // isFreezed = true means the reserve only allows repays and redeems, but not deposits, new borrowings or rate swap
                            bool isFreezed;
                        }
                    
                        /**
                        * @dev returns the ongoing normalized income for the reserve.
                        * a value of 1e27 means there is no income. As time passes, the income is accrued.
                        * A value of 2*1e27 means that the income of the reserve is double the initial amount.
                        * @param _reserve the reserve object
                        * @return the normalized income. expressed in ray
                        **/
                        function getNormalizedIncome(CoreLibrary.ReserveData storage _reserve)
                            internal
                            view
                            returns (uint256)
                        {
                            uint256 cumulated = calculateLinearInterest(
                                _reserve
                                    .currentLiquidityRate,
                                _reserve
                                    .lastUpdateTimestamp
                            )
                                .rayMul(_reserve.lastLiquidityCumulativeIndex);
                    
                            return cumulated;
                    
                        }
                    
                        /**
                        * @dev Updates the liquidity cumulative index Ci and variable borrow cumulative index Bvc. Refer to the whitepaper for
                        * a formal specification.
                        * @param _self the reserve object
                        **/
                        function updateCumulativeIndexes(ReserveData storage _self) internal {
                            uint256 totalBorrows = getTotalBorrows(_self);
                    
                            if (totalBorrows > 0) {
                                //only cumulating if there is any income being produced
                                uint256 cumulatedLiquidityInterest = calculateLinearInterest(
                                    _self.currentLiquidityRate,
                                    _self.lastUpdateTimestamp
                                );
                    
                                _self.lastLiquidityCumulativeIndex = cumulatedLiquidityInterest.rayMul(
                                    _self.lastLiquidityCumulativeIndex
                                );
                    
                                uint256 cumulatedVariableBorrowInterest = calculateCompoundedInterest(
                                    _self.currentVariableBorrowRate,
                                    _self.lastUpdateTimestamp
                                );
                                _self.lastVariableBorrowCumulativeIndex = cumulatedVariableBorrowInterest.rayMul(
                                    _self.lastVariableBorrowCumulativeIndex
                                );
                            }
                        }
                    
                        /**
                        * @dev accumulates a predefined amount of asset to the reserve as a fixed, one time income. Used for example to accumulate
                        * the flashloan fee to the reserve, and spread it through the depositors.
                        * @param _self the reserve object
                        * @param _totalLiquidity the total liquidity available in the reserve
                        * @param _amount the amount to accomulate
                        **/
                        function cumulateToLiquidityIndex(
                            ReserveData storage _self,
                            uint256 _totalLiquidity,
                            uint256 _amount
                        ) internal {
                            uint256 amountToLiquidityRatio = _amount.wadToRay().rayDiv(_totalLiquidity.wadToRay());
                    
                            uint256 cumulatedLiquidity = amountToLiquidityRatio.add(WadRayMath.ray());
                    
                            _self.lastLiquidityCumulativeIndex = cumulatedLiquidity.rayMul(
                                _self.lastLiquidityCumulativeIndex
                            );
                        }
                    
                        /**
                        * @dev initializes a reserve
                        * @param _self the reserve object
                        * @param _aTokenAddress the address of the overlying atoken contract
                        * @param _decimals the number of decimals of the underlying asset
                        * @param _interestRateStrategyAddress the address of the interest rate strategy contract
                        **/
                        function init(
                            ReserveData storage _self,
                            address _aTokenAddress,
                            uint256 _decimals,
                            address _interestRateStrategyAddress
                        ) external {
                            require(_self.aTokenAddress == address(0), "Reserve has already been initialized");
                    
                            if (_self.lastLiquidityCumulativeIndex == 0) {
                                //if the reserve has not been initialized yet
                                _self.lastLiquidityCumulativeIndex = WadRayMath.ray();
                            }
                    
                            if (_self.lastVariableBorrowCumulativeIndex == 0) {
                                _self.lastVariableBorrowCumulativeIndex = WadRayMath.ray();
                            }
                    
                            _self.aTokenAddress = _aTokenAddress;
                            _self.decimals = _decimals;
                    
                            _self.interestRateStrategyAddress = _interestRateStrategyAddress;
                            _self.isActive = true;
                            _self.isFreezed = false;
                    
                        }
                    
                        /**
                        * @dev enables borrowing on a reserve
                        * @param _self the reserve object
                        * @param _stableBorrowRateEnabled true if the stable borrow rate must be enabled by default, false otherwise
                        **/
                        function enableBorrowing(ReserveData storage _self, bool _stableBorrowRateEnabled) external {
                            require(_self.borrowingEnabled == false, "Reserve is already enabled");
                    
                            _self.borrowingEnabled = true;
                            _self.isStableBorrowRateEnabled = _stableBorrowRateEnabled;
                    
                        }
                    
                        /**
                        * @dev disables borrowing on a reserve
                        * @param _self the reserve object
                        **/
                        function disableBorrowing(ReserveData storage _self) external {
                            _self.borrowingEnabled = false;
                        }
                    
                        /**
                        * @dev enables a reserve to be used as collateral
                        * @param _self the reserve object
                        * @param _baseLTVasCollateral the loan to value of the asset when used as collateral
                        * @param _liquidationThreshold the threshold at which loans using this asset as collateral will be considered undercollateralized
                        * @param _liquidationBonus the bonus liquidators receive to liquidate this asset
                        **/
                        function enableAsCollateral(
                            ReserveData storage _self,
                            uint256 _baseLTVasCollateral,
                            uint256 _liquidationThreshold,
                            uint256 _liquidationBonus
                        ) external {
                            require(
                                _self.usageAsCollateralEnabled == false,
                                "Reserve is already enabled as collateral"
                            );
                    
                            _self.usageAsCollateralEnabled = true;
                            _self.baseLTVasCollateral = _baseLTVasCollateral;
                            _self.liquidationThreshold = _liquidationThreshold;
                            _self.liquidationBonus = _liquidationBonus;
                    
                            if (_self.lastLiquidityCumulativeIndex == 0)
                                _self.lastLiquidityCumulativeIndex = WadRayMath.ray();
                    
                        }
                    
                        /**
                        * @dev disables a reserve as collateral
                        * @param _self the reserve object
                        **/
                        function disableAsCollateral(ReserveData storage _self) external {
                            _self.usageAsCollateralEnabled = false;
                        }
                    
                    
                    
                        /**
                        * @dev calculates the compounded borrow balance of a user
                        * @param _self the userReserve object
                        * @param _reserve the reserve object
                        * @return the user compounded borrow balance
                        **/
                        function getCompoundedBorrowBalance(
                            CoreLibrary.UserReserveData storage _self,
                            CoreLibrary.ReserveData storage _reserve
                        ) internal view returns (uint256) {
                            if (_self.principalBorrowBalance == 0) return 0;
                    
                            uint256 principalBorrowBalanceRay = _self.principalBorrowBalance.wadToRay();
                            uint256 compoundedBalance = 0;
                            uint256 cumulatedInterest = 0;
                    
                            if (_self.stableBorrowRate > 0) {
                                cumulatedInterest = calculateCompoundedInterest(
                                    _self.stableBorrowRate,
                                    _self.lastUpdateTimestamp
                                );
                            } else {
                                //variable interest
                                cumulatedInterest = calculateCompoundedInterest(
                                    _reserve
                                        .currentVariableBorrowRate,
                                    _reserve
                                        .lastUpdateTimestamp
                                )
                                    .rayMul(_reserve.lastVariableBorrowCumulativeIndex)
                                    .rayDiv(_self.lastVariableBorrowCumulativeIndex);
                            }
                    
                            compoundedBalance = principalBorrowBalanceRay.rayMul(cumulatedInterest).rayToWad();
                    
                            if (compoundedBalance == _self.principalBorrowBalance) {
                                //solium-disable-next-line
                                if (_self.lastUpdateTimestamp != block.timestamp) {
                                    //no interest cumulation because of the rounding - we add 1 wei
                                    //as symbolic cumulated interest to avoid interest free loans.
                    
                                    return _self.principalBorrowBalance.add(1 wei);
                                }
                            }
                    
                            return compoundedBalance;
                        }
                    
                        /**
                        * @dev increases the total borrows at a stable rate on a specific reserve and updates the
                        * average stable rate consequently
                        * @param _reserve the reserve object
                        * @param _amount the amount to add to the total borrows stable
                        * @param _rate the rate at which the amount has been borrowed
                        **/
                        function increaseTotalBorrowsStableAndUpdateAverageRate(
                            ReserveData storage _reserve,
                            uint256 _amount,
                            uint256 _rate
                        ) internal {
                            uint256 previousTotalBorrowStable = _reserve.totalBorrowsStable;
                            //updating reserve borrows stable
                            _reserve.totalBorrowsStable = _reserve.totalBorrowsStable.add(_amount);
                    
                            //update the average stable rate
                            //weighted average of all the borrows
                            uint256 weightedLastBorrow = _amount.wadToRay().rayMul(_rate);
                            uint256 weightedPreviousTotalBorrows = previousTotalBorrowStable.wadToRay().rayMul(
                                _reserve.currentAverageStableBorrowRate
                            );
                    
                            _reserve.currentAverageStableBorrowRate = weightedLastBorrow
                                .add(weightedPreviousTotalBorrows)
                                .rayDiv(_reserve.totalBorrowsStable.wadToRay());
                        }
                    
                        /**
                        * @dev decreases the total borrows at a stable rate on a specific reserve and updates the
                        * average stable rate consequently
                        * @param _reserve the reserve object
                        * @param _amount the amount to substract to the total borrows stable
                        * @param _rate the rate at which the amount has been repaid
                        **/
                        function decreaseTotalBorrowsStableAndUpdateAverageRate(
                            ReserveData storage _reserve,
                            uint256 _amount,
                            uint256 _rate
                        ) internal {
                            require(_reserve.totalBorrowsStable >= _amount, "Invalid amount to decrease");
                    
                            uint256 previousTotalBorrowStable = _reserve.totalBorrowsStable;
                    
                            //updating reserve borrows stable
                            _reserve.totalBorrowsStable = _reserve.totalBorrowsStable.sub(_amount);
                    
                            if (_reserve.totalBorrowsStable == 0) {
                                _reserve.currentAverageStableBorrowRate = 0; //no income if there are no stable rate borrows
                                return;
                            }
                    
                            //update the average stable rate
                            //weighted average of all the borrows
                            uint256 weightedLastBorrow = _amount.wadToRay().rayMul(_rate);
                            uint256 weightedPreviousTotalBorrows = previousTotalBorrowStable.wadToRay().rayMul(
                                _reserve.currentAverageStableBorrowRate
                            );
                    
                            require(
                                weightedPreviousTotalBorrows >= weightedLastBorrow,
                                "The amounts to subtract don't match"
                            );
                    
                            _reserve.currentAverageStableBorrowRate = weightedPreviousTotalBorrows
                                .sub(weightedLastBorrow)
                                .rayDiv(_reserve.totalBorrowsStable.wadToRay());
                        }
                    
                        /**
                        * @dev increases the total borrows at a variable rate
                        * @param _reserve the reserve object
                        * @param _amount the amount to add to the total borrows variable
                        **/
                        function increaseTotalBorrowsVariable(ReserveData storage _reserve, uint256 _amount) internal {
                            _reserve.totalBorrowsVariable = _reserve.totalBorrowsVariable.add(_amount);
                        }
                    
                        /**
                        * @dev decreases the total borrows at a variable rate
                        * @param _reserve the reserve object
                        * @param _amount the amount to substract to the total borrows variable
                        **/
                        function decreaseTotalBorrowsVariable(ReserveData storage _reserve, uint256 _amount) internal {
                            require(
                                _reserve.totalBorrowsVariable >= _amount,
                                "The amount that is being subtracted from the variable total borrows is incorrect"
                            );
                            _reserve.totalBorrowsVariable = _reserve.totalBorrowsVariable.sub(_amount);
                        }
                    
                        /**
                        * @dev function to calculate the interest using a linear interest rate formula
                        * @param _rate the interest rate, in ray
                        * @param _lastUpdateTimestamp the timestamp of the last update of the interest
                        * @return the interest rate linearly accumulated during the timeDelta, in ray
                        **/
                    
                        function calculateLinearInterest(uint256 _rate, uint40 _lastUpdateTimestamp)
                            internal
                            view
                            returns (uint256)
                        {
                            //solium-disable-next-line
                            uint256 timeDifference = block.timestamp.sub(uint256(_lastUpdateTimestamp));
                    
                            uint256 timeDelta = timeDifference.wadToRay().rayDiv(SECONDS_PER_YEAR.wadToRay());
                    
                            return _rate.rayMul(timeDelta).add(WadRayMath.ray());
                        }
                    
                        /**
                        * @dev function to calculate the interest using a compounded interest rate formula
                        * @param _rate the interest rate, in ray
                        * @param _lastUpdateTimestamp the timestamp of the last update of the interest
                        * @return the interest rate compounded during the timeDelta, in ray
                        **/
                        function calculateCompoundedInterest(uint256 _rate, uint40 _lastUpdateTimestamp)
                            internal
                            view
                            returns (uint256)
                        {
                            //solium-disable-next-line
                            uint256 timeDifference = block.timestamp.sub(uint256(_lastUpdateTimestamp));
                    
                            uint256 ratePerSecond = _rate.div(SECONDS_PER_YEAR);
                    
                            return ratePerSecond.add(WadRayMath.ray()).rayPow(timeDifference);
                        }
                    
                        /**
                        * @dev returns the total borrows on the reserve
                        * @param _reserve the reserve object
                        * @return the total borrows (stable + variable)
                        **/
                        function getTotalBorrows(CoreLibrary.ReserveData storage _reserve)
                            internal
                            view
                            returns (uint256)
                        {
                            return _reserve.totalBorrowsStable.add(_reserve.totalBorrowsVariable);
                        }
                    
                    }
                    
                    
                    
                    /**
                    * @title IPriceOracleGetter interface
                    * @notice Interface for the Aave price oracle.
                    **/
                    
                    interface IPriceOracleGetter {
                        /**
                        * @dev returns the asset price in ETH
                        * @param _asset the address of the asset
                        * @return the ETH price of the asset
                        **/
                        function getAssetPrice(address _asset) external view returns (uint256);
                    }
                    
                    /**
                    * @title IFeeProvider interface
                    * @notice Interface for the Aave fee provider.
                    **/
                    
                    interface IFeeProvider {
                        function calculateLoanOriginationFee(address _user, uint256 _amount) external view returns (uint256);
                        function getLoanOriginationFeePercentage() external view returns (uint256);
                    }
                    
                    /**
                    * @title LendingPoolDataProvider contract
                    * @author Aave
                    * @notice Implements functions to fetch data from the core, and aggregate them in order to allow computation
                    * on the compounded balances and the account balances in ETH
                    **/
                    contract LendingPoolDataProvider is VersionedInitializable {
                        using SafeMath for uint256;
                        using WadRayMath for uint256;
                    
                        LendingPoolCore public core;
                        LendingPoolAddressesProvider public addressesProvider;
                    
                        /**
                        * @dev specifies the health factor threshold at which the user position is liquidated.
                        * 1e18 by default, if the health factor drops below 1e18, the loan can be liquidated.
                        **/
                        uint256 public constant HEALTH_FACTOR_LIQUIDATION_THRESHOLD = 1e18;
                    
                        uint256 public constant DATA_PROVIDER_REVISION = 0x1;
                    
                        function getRevision() internal pure returns (uint256) {
                            return DATA_PROVIDER_REVISION;
                        }
                    
                        function initialize(LendingPoolAddressesProvider _addressesProvider) public initializer {
                            addressesProvider = _addressesProvider;
                            core = LendingPoolCore(_addressesProvider.getLendingPoolCore());
                        }
                    
                        /**
                        * @dev struct to hold calculateUserGlobalData() local computations
                        **/
                        struct UserGlobalDataLocalVars {
                            uint256 reserveUnitPrice;
                            uint256 tokenUnit;
                            uint256 compoundedLiquidityBalance;
                            uint256 compoundedBorrowBalance;
                            uint256 reserveDecimals;
                            uint256 baseLtv;
                            uint256 liquidationThreshold;
                            uint256 originationFee;
                            bool usageAsCollateralEnabled;
                            bool userUsesReserveAsCollateral;
                            address currentReserve;
                        }
                    
                        /**
                        * @dev calculates the user data across the reserves.
                        * this includes the total liquidity/collateral/borrow balances in ETH,
                        * the average Loan To Value, the average Liquidation Ratio, and the Health factor.
                        * @param _user the address of the user
                        * @return the total liquidity, total collateral, total borrow balances of the user in ETH.
                        * also the average Ltv, liquidation threshold, and the health factor
                        **/
                        function calculateUserGlobalData(address _user)
                            public
                            view
                            returns (
                                uint256 totalLiquidityBalanceETH,
                                uint256 totalCollateralBalanceETH,
                                uint256 totalBorrowBalanceETH,
                                uint256 totalFeesETH,
                                uint256 currentLtv,
                                uint256 currentLiquidationThreshold,
                                uint256 healthFactor,
                                bool healthFactorBelowThreshold
                            )
                        {
                            IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
                    
                            // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
                            UserGlobalDataLocalVars memory vars;
                    
                            address[] memory reserves = core.getReserves();
                    
                            for (uint256 i = 0; i < reserves.length; i++) {
                                vars.currentReserve = reserves[i];
                    
                                (
                                    vars.compoundedLiquidityBalance,
                                    vars.compoundedBorrowBalance,
                                    vars.originationFee,
                                    vars.userUsesReserveAsCollateral
                                ) = core.getUserBasicReserveData(vars.currentReserve, _user);
                    
                                if (vars.compoundedLiquidityBalance == 0 && vars.compoundedBorrowBalance == 0) {
                                    continue;
                                }
                    
                                //fetch reserve data
                                (
                                    vars.reserveDecimals,
                                    vars.baseLtv,
                                    vars.liquidationThreshold,
                                    vars.usageAsCollateralEnabled
                                ) = core.getReserveConfiguration(vars.currentReserve);
                    
                                vars.tokenUnit = 10 ** vars.reserveDecimals;
                                vars.reserveUnitPrice = oracle.getAssetPrice(vars.currentReserve);
                    
                                //liquidity and collateral balance
                                if (vars.compoundedLiquidityBalance > 0) {
                                    uint256 liquidityBalanceETH = vars
                                        .reserveUnitPrice
                                        .mul(vars.compoundedLiquidityBalance)
                                        .div(vars.tokenUnit);
                                    totalLiquidityBalanceETH = totalLiquidityBalanceETH.add(liquidityBalanceETH);
                    
                                    if (vars.usageAsCollateralEnabled && vars.userUsesReserveAsCollateral) {
                                        totalCollateralBalanceETH = totalCollateralBalanceETH.add(liquidityBalanceETH);
                                        currentLtv = currentLtv.add(liquidityBalanceETH.mul(vars.baseLtv));
                                        currentLiquidationThreshold = currentLiquidationThreshold.add(
                                            liquidityBalanceETH.mul(vars.liquidationThreshold)
                                        );
                                    }
                                }
                    
                                if (vars.compoundedBorrowBalance > 0) {
                                    totalBorrowBalanceETH = totalBorrowBalanceETH.add(
                                        vars.reserveUnitPrice.mul(vars.compoundedBorrowBalance).div(vars.tokenUnit)
                                    );
                                    totalFeesETH = totalFeesETH.add(
                                        vars.originationFee.mul(vars.reserveUnitPrice).div(vars.tokenUnit)
                                    );
                                }
                            }
                    
                            currentLtv = totalCollateralBalanceETH > 0 ? currentLtv.div(totalCollateralBalanceETH) : 0;
                            currentLiquidationThreshold = totalCollateralBalanceETH > 0
                                ? currentLiquidationThreshold.div(totalCollateralBalanceETH)
                                : 0;
                    
                            healthFactor = calculateHealthFactorFromBalancesInternal(
                                totalCollateralBalanceETH,
                                totalBorrowBalanceETH,
                                totalFeesETH,
                                currentLiquidationThreshold
                            );
                            healthFactorBelowThreshold = healthFactor < HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
                    
                        }
                    
                        struct balanceDecreaseAllowedLocalVars {
                            uint256 decimals;
                            uint256 collateralBalanceETH;
                            uint256 borrowBalanceETH;
                            uint256 totalFeesETH;
                            uint256 currentLiquidationThreshold;
                            uint256 reserveLiquidationThreshold;
                            uint256 amountToDecreaseETH;
                            uint256 collateralBalancefterDecrease;
                            uint256 liquidationThresholdAfterDecrease;
                            uint256 healthFactorAfterDecrease;
                            bool reserveUsageAsCollateralEnabled;
                        }
                    
                        /**
                        * @dev check if a specific balance decrease is allowed (i.e. doesn't bring the user borrow position health factor under 1e18)
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        * @param _amount the amount to decrease
                        * @return true if the decrease of the balance is allowed
                        **/
                    
                        function balanceDecreaseAllowed(address _reserve, address _user, uint256 _amount)
                            external
                            view
                            returns (bool)
                        {
                            // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
                            balanceDecreaseAllowedLocalVars memory vars;
                    
                            (
                                vars.decimals,
                                ,
                                vars.reserveLiquidationThreshold,
                                vars.reserveUsageAsCollateralEnabled
                            ) = core.getReserveConfiguration(_reserve);
                    
                            if (
                                !vars.reserveUsageAsCollateralEnabled ||
                                !core.isUserUseReserveAsCollateralEnabled(_reserve, _user)
                            ) {
                                return true; //if reserve is not used as collateral, no reasons to block the transfer
                            }
                    
                            (
                                ,
                                vars.collateralBalanceETH,
                                vars.borrowBalanceETH,
                                vars.totalFeesETH,
                                ,
                                vars.currentLiquidationThreshold,
                                ,
                    
                            ) = calculateUserGlobalData(_user);
                    
                            if (vars.borrowBalanceETH == 0) {
                                return true; //no borrows - no reasons to block the transfer
                            }
                    
                            IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
                    
                            vars.amountToDecreaseETH = oracle.getAssetPrice(_reserve).mul(_amount).div(
                                10 ** vars.decimals
                            );
                    
                            vars.collateralBalancefterDecrease = vars.collateralBalanceETH.sub(
                                vars.amountToDecreaseETH
                            );
                    
                            //if there is a borrow, there can't be 0 collateral
                            if (vars.collateralBalancefterDecrease == 0) {
                                return false;
                            }
                    
                            vars.liquidationThresholdAfterDecrease = vars
                                .collateralBalanceETH
                                .mul(vars.currentLiquidationThreshold)
                                .sub(vars.amountToDecreaseETH.mul(vars.reserveLiquidationThreshold))
                                .div(vars.collateralBalancefterDecrease);
                    
                            uint256 healthFactorAfterDecrease = calculateHealthFactorFromBalancesInternal(
                                vars.collateralBalancefterDecrease,
                                vars.borrowBalanceETH,
                                vars.totalFeesETH,
                                vars.liquidationThresholdAfterDecrease
                            );
                    
                            return healthFactorAfterDecrease > HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
                    
                        }
                    
                        /**
                       * @notice calculates the amount of collateral needed in ETH to cover a new borrow.
                       * @param _reserve the reserve from which the user wants to borrow
                       * @param _amount the amount the user wants to borrow
                       * @param _fee the fee for the amount that the user needs to cover
                       * @param _userCurrentBorrowBalanceTH the current borrow balance of the user (before the borrow)
                       * @param _userCurrentLtv the average ltv of the user given his current collateral
                       * @return the total amount of collateral in ETH to cover the current borrow balance + the new amount + fee
                       **/
                        function calculateCollateralNeededInETH(
                            address _reserve,
                            uint256 _amount,
                            uint256 _fee,
                            uint256 _userCurrentBorrowBalanceTH,
                            uint256 _userCurrentFeesETH,
                            uint256 _userCurrentLtv
                        ) external view returns (uint256) {
                            uint256 reserveDecimals = core.getReserveDecimals(_reserve);
                    
                            IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
                    
                            uint256 requestedBorrowAmountETH = oracle
                                .getAssetPrice(_reserve)
                                .mul(_amount.add(_fee))
                                .div(10 ** reserveDecimals); //price is in ether
                    
                            //add the current already borrowed amount to the amount requested to calculate the total collateral needed.
                            uint256 collateralNeededInETH = _userCurrentBorrowBalanceTH
                                .add(_userCurrentFeesETH)
                                .add(requestedBorrowAmountETH)
                                .mul(100)
                                .div(_userCurrentLtv); //LTV is calculated in percentage
                    
                            return collateralNeededInETH;
                    
                        }
                    
                        /**
                        * @dev calculates the equivalent amount in ETH that an user can borrow, depending on the available collateral and the
                        * average Loan To Value.
                        * @param collateralBalanceETH the total collateral balance
                        * @param borrowBalanceETH the total borrow balance
                        * @param totalFeesETH the total fees
                        * @param ltv the average loan to value
                        * @return the amount available to borrow in ETH for the user
                        **/
                    
                        function calculateAvailableBorrowsETHInternal(
                            uint256 collateralBalanceETH,
                            uint256 borrowBalanceETH,
                            uint256 totalFeesETH,
                            uint256 ltv
                        ) internal view returns (uint256) {
                            uint256 availableBorrowsETH = collateralBalanceETH.mul(ltv).div(100); //ltv is in percentage
                    
                            if (availableBorrowsETH < borrowBalanceETH) {
                                return 0;
                            }
                    
                            availableBorrowsETH = availableBorrowsETH.sub(borrowBalanceETH.add(totalFeesETH));
                            //calculate fee
                            uint256 borrowFee = IFeeProvider(addressesProvider.getFeeProvider())
                                .calculateLoanOriginationFee(msg.sender, availableBorrowsETH);
                            return availableBorrowsETH.sub(borrowFee);
                        }
                    
                        /**
                        * @dev calculates the health factor from the corresponding balances
                        * @param collateralBalanceETH the total collateral balance in ETH
                        * @param borrowBalanceETH the total borrow balance in ETH
                        * @param totalFeesETH the total fees in ETH
                        * @param liquidationThreshold the avg liquidation threshold
                        **/
                        function calculateHealthFactorFromBalancesInternal(
                            uint256 collateralBalanceETH,
                            uint256 borrowBalanceETH,
                            uint256 totalFeesETH,
                            uint256 liquidationThreshold
                        ) internal pure returns (uint256) {
                            if (borrowBalanceETH == 0) return uint256(-1);
                    
                            return
                                (collateralBalanceETH.mul(liquidationThreshold).div(100)).wadDiv(
                                    borrowBalanceETH.add(totalFeesETH)
                                );
                        }
                    
                        /**
                        * @dev returns the health factor liquidation threshold
                        **/
                        function getHealthFactorLiquidationThreshold() public pure returns (uint256) {
                            return HEALTH_FACTOR_LIQUIDATION_THRESHOLD;
                        }
                    
                        /**
                        * @dev accessory functions to fetch data from the lendingPoolCore
                        **/
                        function getReserveConfigurationData(address _reserve)
                            external
                            view
                            returns (
                                uint256 ltv,
                                uint256 liquidationThreshold,
                                uint256 liquidationBonus,
                                address rateStrategyAddress,
                                bool usageAsCollateralEnabled,
                                bool borrowingEnabled,
                                bool stableBorrowRateEnabled,
                                bool isActive
                            )
                        {
                            (, ltv, liquidationThreshold, usageAsCollateralEnabled) = core.getReserveConfiguration(
                                _reserve
                            );
                            stableBorrowRateEnabled = core.getReserveIsStableBorrowRateEnabled(_reserve);
                            borrowingEnabled = core.isReserveBorrowingEnabled(_reserve);
                            isActive = core.getReserveIsActive(_reserve);
                            liquidationBonus = core.getReserveLiquidationBonus(_reserve);
                    
                            rateStrategyAddress = core.getReserveInterestRateStrategyAddress(_reserve);
                        }
                    
                        function getReserveData(address _reserve)
                            external
                            view
                            returns (
                                uint256 totalLiquidity,
                                uint256 availableLiquidity,
                                uint256 totalBorrowsStable,
                                uint256 totalBorrowsVariable,
                                uint256 liquidityRate,
                                uint256 variableBorrowRate,
                                uint256 stableBorrowRate,
                                uint256 averageStableBorrowRate,
                                uint256 utilizationRate,
                                uint256 liquidityIndex,
                                uint256 variableBorrowIndex,
                                address aTokenAddress,
                                uint40 lastUpdateTimestamp
                            )
                        {
                            totalLiquidity = core.getReserveTotalLiquidity(_reserve);
                            availableLiquidity = core.getReserveAvailableLiquidity(_reserve);
                            totalBorrowsStable = core.getReserveTotalBorrowsStable(_reserve);
                            totalBorrowsVariable = core.getReserveTotalBorrowsVariable(_reserve);
                            liquidityRate = core.getReserveCurrentLiquidityRate(_reserve);
                            variableBorrowRate = core.getReserveCurrentVariableBorrowRate(_reserve);
                            stableBorrowRate = core.getReserveCurrentStableBorrowRate(_reserve);
                            averageStableBorrowRate = core.getReserveCurrentAverageStableBorrowRate(_reserve);
                            utilizationRate = core.getReserveUtilizationRate(_reserve);
                            liquidityIndex = core.getReserveLiquidityCumulativeIndex(_reserve);
                            variableBorrowIndex = core.getReserveVariableBorrowsCumulativeIndex(_reserve);
                            aTokenAddress = core.getReserveATokenAddress(_reserve);
                            lastUpdateTimestamp = core.getReserveLastUpdate(_reserve);
                        }
                    
                        function getUserAccountData(address _user)
                            external
                            view
                            returns (
                                uint256 totalLiquidityETH,
                                uint256 totalCollateralETH,
                                uint256 totalBorrowsETH,
                                uint256 totalFeesETH,
                                uint256 availableBorrowsETH,
                                uint256 currentLiquidationThreshold,
                                uint256 ltv,
                                uint256 healthFactor
                            )
                        {
                            (
                                totalLiquidityETH,
                                totalCollateralETH,
                                totalBorrowsETH,
                                totalFeesETH,
                                ltv,
                                currentLiquidationThreshold,
                                healthFactor,
                    
                            ) = calculateUserGlobalData(_user);
                    
                            availableBorrowsETH = calculateAvailableBorrowsETHInternal(
                                totalCollateralETH,
                                totalBorrowsETH,
                                totalFeesETH,
                                ltv
                            );
                        }
                    
                        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
                            )
                        {
                            currentATokenBalance = AToken(core.getReserveATokenAddress(_reserve)).balanceOf(_user);
                            CoreLibrary.InterestRateMode mode = core.getUserCurrentBorrowRateMode(_reserve, _user);
                            (principalBorrowBalance, currentBorrowBalance, ) = core.getUserBorrowBalances(
                                _reserve,
                                _user
                            );
                    
                            //default is 0, if mode == CoreLibrary.InterestRateMode.NONE
                            if (mode == CoreLibrary.InterestRateMode.STABLE) {
                                borrowRate = core.getUserCurrentStableBorrowRate(_reserve, _user);
                            } else if (mode == CoreLibrary.InterestRateMode.VARIABLE) {
                                borrowRate = core.getReserveCurrentVariableBorrowRate(_reserve);
                            }
                    
                            borrowRateMode = uint256(mode);
                            liquidityRate = core.getReserveCurrentLiquidityRate(_reserve);
                            originationFee = core.getUserOriginationFee(_reserve, _user);
                            variableBorrowIndex = core.getUserVariableBorrowCumulativeIndex(_reserve, _user);
                            lastUpdateTimestamp = core.getUserLastUpdate(_reserve, _user);
                            usageAsCollateralEnabled = core.isUserUseReserveAsCollateralEnabled(_reserve, _user);
                        }
                    }
                    
                    
                    /**
                     * @title Aave ERC20 AToken
                     *
                     * @dev Implementation of the interest bearing token for the DLP protocol.
                     * @author Aave
                     */
                    contract AToken is ERC20, ERC20Detailed {
                        using WadRayMath for uint256;
                    
                        uint256 public constant UINT_MAX_VALUE = uint256(-1);
                    
                        /**
                        * @dev emitted after the redeem action
                        * @param _from the address performing the redeem
                        * @param _value the amount to be redeemed
                        * @param _fromBalanceIncrease the cumulated balance since the last update of the user
                        * @param _fromIndex the last index of the user
                        **/
                        event Redeem(
                            address indexed _from,
                            uint256 _value,
                            uint256 _fromBalanceIncrease,
                            uint256 _fromIndex
                        );
                    
                        /**
                        * @dev emitted after the mint action
                        * @param _from the address performing the mint
                        * @param _value the amount to be minted
                        * @param _fromBalanceIncrease the cumulated balance since the last update of the user
                        * @param _fromIndex the last index of the user
                        **/
                        event MintOnDeposit(
                            address indexed _from,
                            uint256 _value,
                            uint256 _fromBalanceIncrease,
                            uint256 _fromIndex
                        );
                    
                        /**
                        * @dev emitted during the liquidation action, when the liquidator reclaims the underlying
                        * asset
                        * @param _from the address from which the tokens are being burned
                        * @param _value the amount to be burned
                        * @param _fromBalanceIncrease the cumulated balance since the last update of the user
                        * @param _fromIndex the last index of the user
                        **/
                        event BurnOnLiquidation(
                            address indexed _from,
                            uint256 _value,
                            uint256 _fromBalanceIncrease,
                            uint256 _fromIndex
                        );
                    
                        /**
                        * @dev emitted during the transfer action
                        * @param _from the address from which the tokens are being transferred
                        * @param _to the adress of the destination
                        * @param _value the amount to be minted
                        * @param _fromBalanceIncrease the cumulated balance since the last update of the user
                        * @param _toBalanceIncrease the cumulated balance since the last update of the destination
                        * @param _fromIndex the last index of the user
                        * @param _toIndex the last index of the liquidator
                        **/
                        event BalanceTransfer(
                            address indexed _from,
                            address indexed _to,
                            uint256 _value,
                            uint256 _fromBalanceIncrease,
                            uint256 _toBalanceIncrease,
                            uint256 _fromIndex,
                            uint256 _toIndex
                        );
                    
                        /**
                        * @dev emitted when the accumulation of the interest
                        * by an user is redirected to another user
                        * @param _from the address from which the interest is being redirected
                        * @param _to the adress of the destination
                        * @param _fromBalanceIncrease the cumulated balance since the last update of the user
                        * @param _fromIndex the last index of the user
                        **/
                        event InterestStreamRedirected(
                            address indexed _from,
                            address indexed _to,
                            uint256 _redirectedBalance,
                            uint256 _fromBalanceIncrease,
                            uint256 _fromIndex
                        );
                    
                        /**
                        * @dev emitted when the redirected balance of an user is being updated
                        * @param _targetAddress the address of which the balance is being updated
                        * @param _targetBalanceIncrease the cumulated balance since the last update of the target
                        * @param _targetIndex the last index of the user
                        * @param _redirectedBalanceAdded the redirected balance being added
                        * @param _redirectedBalanceRemoved the redirected balance being removed
                        **/
                        event RedirectedBalanceUpdated(
                            address indexed _targetAddress,
                            uint256 _targetBalanceIncrease,
                            uint256 _targetIndex,
                            uint256 _redirectedBalanceAdded,
                            uint256 _redirectedBalanceRemoved
                        );
                    
                        event InterestRedirectionAllowanceChanged(
                            address indexed _from,
                            address indexed _to
                        );
                    
                        address public underlyingAssetAddress;
                    
                        mapping (address => uint256) private userIndexes;
                        mapping (address => address) private interestRedirectionAddresses;
                        mapping (address => uint256) private redirectedBalances;
                        mapping (address => address) private interestRedirectionAllowances;
                    
                        LendingPoolAddressesProvider private addressesProvider;
                        LendingPoolCore private core;
                        LendingPool private pool;
                        LendingPoolDataProvider private dataProvider;
                    
                        modifier onlyLendingPool {
                            require(
                                msg.sender == address(pool),
                                "The caller of this function must be a lending pool"
                            );
                            _;
                        }
                    
                        modifier whenTransferAllowed(address _from, uint256 _amount) {
                            require(isTransferAllowed(_from, _amount), "Transfer cannot be allowed.");
                            _;
                        }
                    
                        constructor(
                            LendingPoolAddressesProvider _addressesProvider,
                            address _underlyingAsset,
                            uint8 _underlyingAssetDecimals,
                            string memory _name,
                            string memory _symbol
                        ) public ERC20Detailed(_name, _symbol, _underlyingAssetDecimals) {
                    
                            addressesProvider = _addressesProvider;
                            core = LendingPoolCore(addressesProvider.getLendingPoolCore());
                            pool = LendingPool(addressesProvider.getLendingPool());
                            dataProvider = LendingPoolDataProvider(addressesProvider.getLendingPoolDataProvider());
                            underlyingAssetAddress = _underlyingAsset;
                        }
                    
                        /**
                         * @notice ERC20 implementation internal function backing transfer() and transferFrom()
                         * @dev validates the transfer before allowing it. NOTE: This is not standard ERC20 behavior
                         **/
                        function _transfer(address _from, address _to, uint256 _amount) internal whenTransferAllowed(_from, _amount) {
                    
                            executeTransferInternal(_from, _to, _amount);
                        }
                    
                    
                        /**
                        * @dev redirects the interest generated to a target address.
                        * when the interest is redirected, the user balance is added to
                        * the recepient redirected balance.
                        * @param _to the address to which the interest will be redirected
                        **/
                        function redirectInterestStream(address _to) external {
                            redirectInterestStreamInternal(msg.sender, _to);
                        }
                    
                        /**
                        * @dev redirects the interest generated by _from to a target address.
                        * when the interest is redirected, the user balance is added to
                        * the recepient redirected balance. The caller needs to have allowance on
                        * the interest redirection to be able to execute the function.
                        * @param _from the address of the user whom interest is being redirected
                        * @param _to the address to which the interest will be redirected
                        **/
                        function redirectInterestStreamOf(address _from, address _to) external {
                            require(
                                msg.sender == interestRedirectionAllowances[_from],
                                "Caller is not allowed to redirect the interest of the user"
                            );
                            redirectInterestStreamInternal(_from,_to);
                        }
                    
                        /**
                        * @dev gives allowance to an address to execute the interest redirection
                        * on behalf of the caller.
                        * @param _to the address to which the interest will be redirected. Pass address(0) to reset
                        * the allowance.
                        **/
                        function allowInterestRedirectionTo(address _to) external {
                            require(_to != msg.sender, "User cannot give allowance to himself");
                            interestRedirectionAllowances[msg.sender] = _to;
                            emit InterestRedirectionAllowanceChanged(
                                msg.sender,
                                _to
                            );
                        }
                    
                        /**
                        * @dev redeems aToken for the underlying asset
                        * @param _amount the amount being redeemed
                        **/
                        function redeem(uint256 _amount) external {
                    
                            require(_amount > 0, "Amount to redeem needs to be > 0");
                    
                            //cumulates the balance of the user
                            (,
                            uint256 currentBalance,
                            uint256 balanceIncrease,
                            uint256 index) = cumulateBalanceInternal(msg.sender);
                    
                            uint256 amountToRedeem = _amount;
                    
                            //if amount is equal to uint(-1), the user wants to redeem everything
                            if(_amount == UINT_MAX_VALUE){
                                amountToRedeem = currentBalance;
                            }
                    
                            require(amountToRedeem <= currentBalance, "User cannot redeem more than the available balance");
                    
                            //check that the user is allowed to redeem the amount
                            require(isTransferAllowed(msg.sender, amountToRedeem), "Transfer cannot be allowed.");
                    
                            //if the user is redirecting his interest towards someone else,
                            //we update the redirected balance of the redirection address by adding the accrued interest,
                            //and removing the amount to redeem
                            updateRedirectedBalanceOfRedirectionAddressInternal(msg.sender, balanceIncrease, amountToRedeem);
                    
                            // burns tokens equivalent to the amount requested
                            _burn(msg.sender, amountToRedeem);
                    
                            bool userIndexReset = false;
                            //reset the user data if the remaining balance is 0
                            if(currentBalance.sub(amountToRedeem) == 0){
                                userIndexReset = resetDataOnZeroBalanceInternal(msg.sender);
                            }
                    
                            // executes redeem of the underlying asset
                            pool.redeemUnderlying(
                                underlyingAssetAddress,
                                msg.sender,
                                amountToRedeem,
                                currentBalance.sub(amountToRedeem)
                            );
                    
                            emit Redeem(msg.sender, amountToRedeem, balanceIncrease, userIndexReset ? 0 : index);
                        }
                    
                        /**
                         * @dev mints token in the event of users depositing the underlying asset into the lending pool
                         * only lending pools can call this function
                         * @param _account the address receiving the minted tokens
                         * @param _amount the amount of tokens to mint
                         */
                        function mintOnDeposit(address _account, uint256 _amount) external onlyLendingPool {
                    
                            //cumulates the balance of the user
                            (,
                            ,
                            uint256 balanceIncrease,
                            uint256 index) = cumulateBalanceInternal(_account);
                    
                             //if the user is redirecting his interest towards someone else,
                            //we update the redirected balance of the redirection address by adding the accrued interest
                            //and the amount deposited
                            updateRedirectedBalanceOfRedirectionAddressInternal(_account, balanceIncrease.add(_amount), 0);
                    
                            //mint an equivalent amount of tokens to cover the new deposit
                            _mint(_account, _amount);
                    
                            emit MintOnDeposit(_account, _amount, balanceIncrease, index);
                        }
                    
                        /**
                         * @dev burns token in the event of a borrow being liquidated, in case the liquidators reclaims the underlying asset
                         * Transfer of the liquidated asset is executed by the lending pool contract.
                         * only lending pools can call this function
                         * @param _account the address from which burn the aTokens
                         * @param _value the amount to burn
                         **/
                        function burnOnLiquidation(address _account, uint256 _value) external onlyLendingPool {
                    
                            //cumulates the balance of the user being liquidated
                            (,uint256 accountBalance,uint256 balanceIncrease,uint256 index) = cumulateBalanceInternal(_account);
                    
                            //adds the accrued interest and substracts the burned amount to
                            //the redirected balance
                            updateRedirectedBalanceOfRedirectionAddressInternal(_account, balanceIncrease, _value);
                    
                            //burns the requested amount of tokens
                            _burn(_account, _value);
                    
                            bool userIndexReset = false;
                            //reset the user data if the remaining balance is 0
                            if(accountBalance.sub(_value) == 0){
                                userIndexReset = resetDataOnZeroBalanceInternal(_account);
                            }
                    
                            emit BurnOnLiquidation(_account, _value, balanceIncrease, userIndexReset ? 0 : index);
                        }
                    
                        /**
                         * @dev transfers tokens in the event of a borrow being liquidated, in case the liquidators reclaims the aToken
                         *      only lending pools can call this function
                         * @param _from the address from which transfer the aTokens
                         * @param _to the destination address
                         * @param _value the amount to transfer
                         **/
                        function transferOnLiquidation(address _from, address _to, uint256 _value) external onlyLendingPool {
                    
                            //being a normal transfer, the Transfer() and BalanceTransfer() are emitted
                            //so no need to emit a specific event here
                            executeTransferInternal(_from, _to, _value);
                        }
                    
                        /**
                        * @dev calculates the balance of the user, which is the
                        * principal balance + interest generated by the principal balance + interest generated by the redirected balance
                        * @param _user the user for which the balance is being calculated
                        * @return the total balance of the user
                        **/
                        function balanceOf(address _user) public view returns(uint256) {
                    
                            //current principal balance of the user
                            uint256 currentPrincipalBalance = super.balanceOf(_user);
                            //balance redirected by other users to _user for interest rate accrual
                            uint256 redirectedBalance = redirectedBalances[_user];
                    
                            if(currentPrincipalBalance == 0 && redirectedBalance == 0){
                                return 0;
                            }
                            //if the _user is not redirecting the interest to anybody, accrues
                            //the interest for himself
                    
                            if(interestRedirectionAddresses[_user] == address(0)){
                    
                                //accruing for himself means that both the principal balance and
                                //the redirected balance partecipate in the interest
                                return calculateCumulatedBalanceInternal(
                                    _user,
                                    currentPrincipalBalance.add(redirectedBalance)
                                    )
                                    .sub(redirectedBalance);
                            }
                            else {
                                //if the user redirected the interest, then only the redirected
                                //balance generates interest. In that case, the interest generated
                                //by the redirected balance is added to the current principal balance.
                                return currentPrincipalBalance.add(
                                    calculateCumulatedBalanceInternal(
                                        _user,
                                        redirectedBalance
                                    )
                                    .sub(redirectedBalance)
                                );
                            }
                        }
                    
                        /**
                        * @dev returns the principal balance of the user. The principal balance is the last
                        * updated stored balance, which does not consider the perpetually accruing interest.
                        * @param _user the address of the user
                        * @return the principal balance of the user
                        **/
                        function principalBalanceOf(address _user) external view returns(uint256) {
                            return super.balanceOf(_user);
                        }
                    
                    
                        /**
                        * @dev calculates the total supply of the specific aToken
                        * since the balance of every single user increases over time, the total supply
                        * does that too.
                        * @return the current total supply
                        **/
                        function totalSupply() public view returns(uint256) {
                    
                            uint256 currentSupplyPrincipal = super.totalSupply();
                    
                            if(currentSupplyPrincipal == 0){
                                return 0;
                            }
                    
                            return currentSupplyPrincipal
                                .wadToRay()
                                .rayMul(core.getReserveNormalizedIncome(underlyingAssetAddress))
                                .rayToWad();
                        }
                    
                    
                        /**
                         * @dev Used to validate transfers before actually executing them.
                         * @param _user address of the user to check
                         * @param _amount the amount to check
                         * @return true if the _user can transfer _amount, false otherwise
                         **/
                        function isTransferAllowed(address _user, uint256 _amount) public view returns (bool) {
                            return dataProvider.balanceDecreaseAllowed(underlyingAssetAddress, _user, _amount);
                        }
                    
                        /**
                        * @dev returns the last index of the user, used to calculate the balance of the user
                        * @param _user address of the user
                        * @return the last user index
                        **/
                        function getUserIndex(address _user) external view returns(uint256) {
                            return userIndexes[_user];
                        }
                    
                    
                        /**
                        * @dev returns the address to which the interest is redirected
                        * @param _user address of the user
                        * @return 0 if there is no redirection, an address otherwise
                        **/
                        function getInterestRedirectionAddress(address _user) external view returns(address) {
                            return interestRedirectionAddresses[_user];
                        }
                    
                        /**
                        * @dev returns the redirected balance of the user. The redirected balance is the balance
                        * redirected by other accounts to the user, that is accrueing interest for him.
                        * @param _user address of the user
                        * @return the total redirected balance
                        **/
                        function getRedirectedBalance(address _user) external view returns(uint256) {
                            return redirectedBalances[_user];
                        }
                    
                        /**
                        * @dev accumulates the accrued interest of the user to the principal balance
                        * @param _user the address of the user for which the interest is being accumulated
                        * @return the previous principal balance, the new principal balance, the balance increase
                        * and the new user index
                        **/
                        function cumulateBalanceInternal(address _user)
                            internal
                            returns(uint256, uint256, uint256, uint256) {
                    
                            uint256 previousPrincipalBalance = super.balanceOf(_user);
                    
                            //calculate the accrued interest since the last accumulation
                            uint256 balanceIncrease = balanceOf(_user).sub(previousPrincipalBalance);
                            //mints an amount of tokens equivalent to the amount accumulated
                            _mint(_user, balanceIncrease);
                            //updates the user index
                            uint256 index = userIndexes[_user] = core.getReserveNormalizedIncome(underlyingAssetAddress);
                            return (
                                previousPrincipalBalance,
                                previousPrincipalBalance.add(balanceIncrease),
                                balanceIncrease,
                                index
                            );
                        }
                    
                        /**
                        * @dev updates the redirected balance of the user. If the user is not redirecting his
                        * interest, nothing is executed.
                        * @param _user the address of the user for which the interest is being accumulated
                        * @param _balanceToAdd the amount to add to the redirected balance
                        * @param _balanceToRemove the amount to remove from the redirected balance
                        **/
                        function updateRedirectedBalanceOfRedirectionAddressInternal(
                            address _user,
                            uint256 _balanceToAdd,
                            uint256 _balanceToRemove
                        ) internal {
                    
                            address redirectionAddress = interestRedirectionAddresses[_user];
                            //if there isn't any redirection, nothing to be done
                            if(redirectionAddress == address(0)){
                                return;
                            }
                    
                            //compound balances of the redirected address
                            (,,uint256 balanceIncrease, uint256 index) = cumulateBalanceInternal(redirectionAddress);
                    
                            //updating the redirected balance
                            redirectedBalances[redirectionAddress] = redirectedBalances[redirectionAddress]
                                .add(_balanceToAdd)
                                .sub(_balanceToRemove);
                    
                            //if the interest of redirectionAddress is also being redirected, we need to update
                            //the redirected balance of the redirection target by adding the balance increase
                            address targetOfRedirectionAddress = interestRedirectionAddresses[redirectionAddress];
                    
                            if(targetOfRedirectionAddress != address(0)){
                                redirectedBalances[targetOfRedirectionAddress] = redirectedBalances[targetOfRedirectionAddress].add(balanceIncrease);
                            }
                    
                            emit RedirectedBalanceUpdated(
                                redirectionAddress,
                                balanceIncrease,
                                index,
                                _balanceToAdd,
                                _balanceToRemove
                            );
                        }
                    
                        /**
                        * @dev calculate the interest accrued by _user on a specific balance
                        * @param _user the address of the user for which the interest is being accumulated
                        * @param _balance the balance on which the interest is calculated
                        * @return the interest rate accrued
                        **/
                        function calculateCumulatedBalanceInternal(
                            address _user,
                            uint256 _balance
                        ) internal view returns (uint256) {
                            return _balance
                                .wadToRay()
                                .rayMul(core.getReserveNormalizedIncome(underlyingAssetAddress))
                                .rayDiv(userIndexes[_user])
                                .rayToWad();
                        }
                    
                        /**
                        * @dev executes the transfer of aTokens, invoked by both _transfer() and
                        *      transferOnLiquidation()
                        * @param _from the address from which transfer the aTokens
                        * @param _to the destination address
                        * @param _value the amount to transfer
                        **/
                        function executeTransferInternal(
                            address _from,
                            address _to,
                            uint256 _value
                        ) internal {
                    
                            require(_value > 0, "Transferred amount needs to be greater than zero");
                    
                            //cumulate the balance of the sender
                            (,
                            uint256 fromBalance,
                            uint256 fromBalanceIncrease,
                            uint256 fromIndex
                            ) = cumulateBalanceInternal(_from);
                    
                            //cumulate the balance of the receiver
                            (,
                            ,
                            uint256 toBalanceIncrease,
                            uint256 toIndex
                            ) = cumulateBalanceInternal(_to);
                    
                            //if the sender is redirecting his interest towards someone else,
                            //adds to the redirected balance the accrued interest and removes the amount
                            //being transferred
                            updateRedirectedBalanceOfRedirectionAddressInternal(_from, fromBalanceIncrease, _value);
                    
                            //if the receiver is redirecting his interest towards someone else,
                            //adds to the redirected balance the accrued interest and the amount
                            //being transferred
                            updateRedirectedBalanceOfRedirectionAddressInternal(_to, toBalanceIncrease.add(_value), 0);
                    
                            //performs the transfer
                            super._transfer(_from, _to, _value);
                    
                            bool fromIndexReset = false;
                            //reset the user data if the remaining balance is 0
                            if(fromBalance.sub(_value) == 0){
                                fromIndexReset = resetDataOnZeroBalanceInternal(_from);
                            }
                    
                            emit BalanceTransfer(
                                _from,
                                _to,
                                _value,
                                fromBalanceIncrease,
                                toBalanceIncrease,
                                fromIndexReset ? 0 : fromIndex,
                                toIndex
                            );
                        }
                    
                        /**
                        * @dev executes the redirection of the interest from one address to another.
                        * immediately after redirection, the destination address will start to accrue interest.
                        * @param _from the address from which transfer the aTokens
                        * @param _to the destination address
                        **/
                        function redirectInterestStreamInternal(
                            address _from,
                            address _to
                        ) internal {
                    
                            address currentRedirectionAddress = interestRedirectionAddresses[_from];
                    
                            require(_to != currentRedirectionAddress, "Interest is already redirected to the user");
                    
                            //accumulates the accrued interest to the principal
                            (uint256 previousPrincipalBalance,
                            uint256 fromBalance,
                            uint256 balanceIncrease,
                            uint256 fromIndex) = cumulateBalanceInternal(_from);
                    
                            require(fromBalance > 0, "Interest stream can only be redirected if there is a valid balance");
                    
                            //if the user is already redirecting the interest to someone, before changing
                            //the redirection address we substract the redirected balance of the previous
                            //recipient
                            if(currentRedirectionAddress != address(0)){
                                updateRedirectedBalanceOfRedirectionAddressInternal(_from,0, previousPrincipalBalance);
                            }
                    
                            //if the user is redirecting the interest back to himself,
                            //we simply set to 0 the interest redirection address
                            if(_to == _from) {
                                interestRedirectionAddresses[_from] = address(0);
                                emit InterestStreamRedirected(
                                    _from,
                                    address(0),
                                    fromBalance,
                                    balanceIncrease,
                                    fromIndex
                                );
                                return;
                            }
                    
                            //first set the redirection address to the new recipient
                            interestRedirectionAddresses[_from] = _to;
                    
                            //adds the user balance to the redirected balance of the destination
                            updateRedirectedBalanceOfRedirectionAddressInternal(_from,fromBalance,0);
                    
                            emit InterestStreamRedirected(
                                _from,
                                _to,
                                fromBalance,
                                balanceIncrease,
                                fromIndex
                            );
                        }
                    
                        /**
                        * @dev function to reset the interest stream redirection and the user index, if the
                        * user has no balance left.
                        * @param _user the address of the user
                        * @return true if the user index has also been reset, false otherwise. useful to emit the proper user index value
                        **/
                        function resetDataOnZeroBalanceInternal(address _user) internal returns(bool) {
                    
                            //if the user has 0 principal balance, the interest stream redirection gets reset
                            interestRedirectionAddresses[_user] = address(0);
                    
                            //emits a InterestStreamRedirected event to notify that the redirection has been reset
                            emit InterestStreamRedirected(_user, address(0),0,0,0);
                    
                            //if the redirected balance is also 0, we clear up the user index
                            if(redirectedBalances[_user] == 0){
                                userIndexes[_user] = 0;
                                return true;
                            }
                            else{
                                return false;
                            }
                        }
                    }
                    
                    /**
                    * @title IFlashLoanReceiver interface
                    * @notice Interface for the Aave fee IFlashLoanReceiver.
                    * @author Aave
                    * @dev implement this interface to develop a flashloan-compatible flashLoanReceiver contract
                    **/
                    interface IFlashLoanReceiver {
                    
                        function executeOperation(address _reserve, uint256 _amount, uint256 _fee, bytes calldata _params) external;
                    }
                    
                    /**
                    * @title ILendingRateOracle interface
                    * @notice Interface for the Aave borrow rate oracle. Provides the average market borrow rate to be used as a base for the stable borrow rate calculations
                    **/
                    
                    interface ILendingRateOracle {
                        /**
                        @dev returns the market borrow rate in ray
                        **/
                        function getMarketBorrowRate(address _asset) external view returns (uint256);
                    
                        /**
                        @dev sets the market borrow rate. Rate value must be in ray
                        **/
                        function setMarketBorrowRate(address _asset, uint256 _rate) external;
                    }
                    
                    /**
                    @title IReserveInterestRateStrategyInterface interface
                    @notice Interface for the calculation of the interest rates.
                    */
                    
                    interface IReserveInterestRateStrategy {
                    
                        /**
                        * @dev returns the base variable borrow rate, in rays
                        */
                    
                        function getBaseVariableBorrowRate() external view returns (uint256);
                        /**
                        * @dev calculates the liquidity, stable, and variable rates depending on the current utilization rate
                        *      and the base parameters
                        *
                        */
                        function calculateInterestRates(
                            address _reserve,
                            uint256 _utilizationRate,
                            uint256 _totalBorrowsStable,
                            uint256 _totalBorrowsVariable,
                            uint256 _averageStableBorrowRate)
                        external
                        view
                        returns (uint256 liquidityRate, uint256 stableBorrowRate, uint256 variableBorrowRate);
                    }
                    
                    library EthAddressLib {
                    
                        /**
                        * @dev returns the address used within the protocol to identify ETH
                        * @return the address assigned to ETH
                         */
                        function ethAddress() internal pure returns(address) {
                            return 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
                        }
                    }
                    
                    /**
                    * @title LendingPoolCore contract
                    * @author Aave
                    * @notice Holds the state of the lending pool and all the funds deposited
                    * @dev NOTE: The core does not enforce security checks on the update of the state
                    * (eg, updateStateOnBorrow() does not enforce that borrowed is enabled on the reserve).
                    * The check that an action can be performed is a duty of the overlying LendingPool contract.
                    **/
                    
                    contract LendingPoolCore is VersionedInitializable {
                        using SafeMath for uint256;
                        using WadRayMath for uint256;
                        using CoreLibrary for CoreLibrary.ReserveData;
                        using CoreLibrary for CoreLibrary.UserReserveData;
                        using SafeERC20 for ERC20;
                        using Address for address payable;
                    
                        /**
                        * @dev Emitted when the state of a reserve is updated
                        * @param reserve the address of the reserve
                        * @param liquidityRate the new liquidity rate
                        * @param stableBorrowRate the new stable borrow rate
                        * @param variableBorrowRate the new variable borrow rate
                        * @param liquidityIndex the new liquidity index
                        * @param variableBorrowIndex the new variable borrow index
                        **/
                        event ReserveUpdated(
                            address indexed reserve,
                            uint256 liquidityRate,
                            uint256 stableBorrowRate,
                            uint256 variableBorrowRate,
                            uint256 liquidityIndex,
                            uint256 variableBorrowIndex
                        );
                    
                        address public lendingPoolAddress;
                    
                        LendingPoolAddressesProvider public addressesProvider;
                    
                        /**
                        * @dev only lending pools can use functions affected by this modifier
                        **/
                        modifier onlyLendingPool {
                            require(lendingPoolAddress == msg.sender, "The caller must be a lending pool contract");
                            _;
                        }
                    
                        /**
                        * @dev only lending pools configurator can use functions affected by this modifier
                        **/
                        modifier onlyLendingPoolConfigurator {
                            require(
                                addressesProvider.getLendingPoolConfigurator() == msg.sender,
                                "The caller must be a lending pool configurator contract"
                            );
                            _;
                        }
                    
                        mapping(address => CoreLibrary.ReserveData) internal reserves;
                        mapping(address => mapping(address => CoreLibrary.UserReserveData)) internal usersReserveData;
                    
                        address[] public reservesList;
                    
                        uint256 public constant CORE_REVISION = 0x4;
                    
                        /**
                        * @dev returns the revision number of the contract
                        **/
                        function getRevision() internal pure returns (uint256) {
                            return CORE_REVISION;
                        }
                    
                        /**
                        * @dev initializes the Core contract, invoked upon registration on the AddressesProvider
                        * @param _addressesProvider the addressesProvider contract
                        **/
                    
                        function initialize(LendingPoolAddressesProvider _addressesProvider) public initializer {
                            addressesProvider = _addressesProvider;
                            refreshConfigInternal();
                        }
                    
                        /**
                        * @dev updates the state of the core as a result of a deposit action
                        * @param _reserve the address of the reserve in which the deposit is happening
                        * @param _user the address of the the user depositing
                        * @param _amount the amount being deposited
                        * @param _isFirstDeposit true if the user is depositing for the first time
                        **/
                    
                        function updateStateOnDeposit(
                            address _reserve,
                            address _user,
                            uint256 _amount,
                            bool _isFirstDeposit
                        ) external onlyLendingPool {
                            reserves[_reserve].updateCumulativeIndexes();
                            updateReserveInterestRatesAndTimestampInternal(_reserve, _amount, 0);
                    
                            if (_isFirstDeposit) {
                                //if this is the first deposit of the user, we configure the deposit as enabled to be used as collateral
                                setUserUseReserveAsCollateral(_reserve, _user, true);
                            }
                        }
                    
                        /**
                        * @dev updates the state of the core as a result of a redeem action
                        * @param _reserve the address of the reserve in which the redeem is happening
                        * @param _user the address of the the user redeeming
                        * @param _amountRedeemed the amount being redeemed
                        * @param _userRedeemedEverything true if the user is redeeming everything
                        **/
                        function updateStateOnRedeem(
                            address _reserve,
                            address _user,
                            uint256 _amountRedeemed,
                            bool _userRedeemedEverything
                        ) external onlyLendingPool {
                            //compound liquidity and variable borrow interests
                            reserves[_reserve].updateCumulativeIndexes();
                            updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountRedeemed);
                    
                            //if user redeemed everything the useReserveAsCollateral flag is reset
                            if (_userRedeemedEverything) {
                                setUserUseReserveAsCollateral(_reserve, _user, false);
                            }
                        }
                    
                        /**
                        * @dev updates the state of the core as a result of a flashloan action
                        * @param _reserve the address of the reserve in which the flashloan is happening
                        * @param _income the income of the protocol as a result of the action
                        **/
                        function updateStateOnFlashLoan(
                            address _reserve,
                            uint256 _availableLiquidityBefore,
                            uint256 _income,
                            uint256 _protocolFee
                        ) external onlyLendingPool {
                            transferFlashLoanProtocolFeeInternal(_reserve, _protocolFee);
                    
                            //compounding the cumulated interest
                            reserves[_reserve].updateCumulativeIndexes();
                    
                            uint256 totalLiquidityBefore = _availableLiquidityBefore.add(
                                getReserveTotalBorrows(_reserve)
                            );
                    
                            //compounding the received fee into the reserve
                            reserves[_reserve].cumulateToLiquidityIndex(totalLiquidityBefore, _income);
                    
                            //refresh interest rates
                            updateReserveInterestRatesAndTimestampInternal(_reserve, _income, 0);
                        }
                    
                        /**
                        * @dev updates the state of the core as a consequence of a borrow action.
                        * @param _reserve the address of the reserve on which the user is borrowing
                        * @param _user the address of the borrower
                        * @param _amountBorrowed the new amount borrowed
                        * @param _borrowFee the fee on the amount borrowed
                        * @param _rateMode the borrow rate mode (stable, variable)
                        * @return the new borrow rate for the user
                        **/
                        function updateStateOnBorrow(
                            address _reserve,
                            address _user,
                            uint256 _amountBorrowed,
                            uint256 _borrowFee,
                            CoreLibrary.InterestRateMode _rateMode
                        ) external onlyLendingPool returns (uint256, uint256) {
                            // getting the previous borrow data of the user
                            (uint256 principalBorrowBalance, , uint256 balanceIncrease) = getUserBorrowBalances(
                                _reserve,
                                _user
                            );
                    
                            updateReserveStateOnBorrowInternal(
                                _reserve,
                                _user,
                                principalBorrowBalance,
                                balanceIncrease,
                                _amountBorrowed,
                                _rateMode
                            );
                    
                            updateUserStateOnBorrowInternal(
                                _reserve,
                                _user,
                                _amountBorrowed,
                                balanceIncrease,
                                _borrowFee,
                                _rateMode
                            );
                    
                            updateReserveInterestRatesAndTimestampInternal(_reserve, 0, _amountBorrowed);
                    
                            return (getUserCurrentBorrowRate(_reserve, _user), balanceIncrease);
                        }
                    
                        /**
                        * @dev updates the state of the core as a consequence of a repay action.
                        * @param _reserve the address of the reserve on which the user is repaying
                        * @param _user the address of the borrower
                        * @param _paybackAmountMinusFees the amount being paid back minus fees
                        * @param _originationFeeRepaid the fee on the amount that is being repaid
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @param _repaidWholeLoan true if the user is repaying the whole loan
                        **/
                    
                        function updateStateOnRepay(
                            address _reserve,
                            address _user,
                            uint256 _paybackAmountMinusFees,
                            uint256 _originationFeeRepaid,
                            uint256 _balanceIncrease,
                            bool _repaidWholeLoan
                        ) external onlyLendingPool {
                            updateReserveStateOnRepayInternal(
                                _reserve,
                                _user,
                                _paybackAmountMinusFees,
                                _balanceIncrease
                            );
                            updateUserStateOnRepayInternal(
                                _reserve,
                                _user,
                                _paybackAmountMinusFees,
                                _originationFeeRepaid,
                                _balanceIncrease,
                                _repaidWholeLoan
                            );
                    
                            updateReserveInterestRatesAndTimestampInternal(_reserve, _paybackAmountMinusFees, 0);
                        }
                    
                        /**
                        * @dev updates the state of the core as a consequence of a swap rate action.
                        * @param _reserve the address of the reserve on which the user is repaying
                        * @param _user the address of the borrower
                        * @param _principalBorrowBalance the amount borrowed by the user
                        * @param _compoundedBorrowBalance the amount borrowed plus accrued interest
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @param _currentRateMode the current interest rate mode for the user
                        **/
                        function updateStateOnSwapRate(
                            address _reserve,
                            address _user,
                            uint256 _principalBorrowBalance,
                            uint256 _compoundedBorrowBalance,
                            uint256 _balanceIncrease,
                            CoreLibrary.InterestRateMode _currentRateMode
                        ) external onlyLendingPool returns (CoreLibrary.InterestRateMode, uint256) {
                            updateReserveStateOnSwapRateInternal(
                                _reserve,
                                _user,
                                _principalBorrowBalance,
                                _compoundedBorrowBalance,
                                _currentRateMode
                            );
                    
                            CoreLibrary.InterestRateMode newRateMode = updateUserStateOnSwapRateInternal(
                                _reserve,
                                _user,
                                _balanceIncrease,
                                _currentRateMode
                            );
                    
                            updateReserveInterestRatesAndTimestampInternal(_reserve, 0, 0);
                    
                            return (newRateMode, getUserCurrentBorrowRate(_reserve, _user));
                        }
                    
                        /**
                        * @dev updates the state of the core as a consequence of a liquidation action.
                        * @param _principalReserve the address of the principal reserve that is being repaid
                        * @param _collateralReserve the address of the collateral reserve that is being liquidated
                        * @param _user the address of the borrower
                        * @param _amountToLiquidate the amount being repaid by the liquidator
                        * @param _collateralToLiquidate the amount of collateral being liquidated
                        * @param _feeLiquidated the amount of origination fee being liquidated
                        * @param _liquidatedCollateralForFee the amount of collateral equivalent to the origination fee + bonus
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @param _liquidatorReceivesAToken true if the liquidator will receive aTokens, false otherwise
                        **/
                        function updateStateOnLiquidation(
                            address _principalReserve,
                            address _collateralReserve,
                            address _user,
                            uint256 _amountToLiquidate,
                            uint256 _collateralToLiquidate,
                            uint256 _feeLiquidated,
                            uint256 _liquidatedCollateralForFee,
                            uint256 _balanceIncrease,
                            bool _liquidatorReceivesAToken
                        ) external onlyLendingPool {
                            updatePrincipalReserveStateOnLiquidationInternal(
                                _principalReserve,
                                _user,
                                _amountToLiquidate,
                                _balanceIncrease
                            );
                    
                            updateCollateralReserveStateOnLiquidationInternal(
                                _collateralReserve
                            );
                    
                            updateUserStateOnLiquidationInternal(
                                _principalReserve,
                                _user,
                                _amountToLiquidate,
                                _feeLiquidated,
                                _balanceIncrease
                            );
                    
                            updateReserveInterestRatesAndTimestampInternal(_principalReserve, _amountToLiquidate, 0);
                    
                            if (!_liquidatorReceivesAToken) {
                                updateReserveInterestRatesAndTimestampInternal(
                                    _collateralReserve,
                                    0,
                                    _collateralToLiquidate.add(_liquidatedCollateralForFee)
                                );
                            }
                    
                        }
                    
                        /**
                        * @dev updates the state of the core as a consequence of a stable rate rebalance
                        * @param _reserve the address of the principal reserve where the user borrowed
                        * @param _user the address of the borrower
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @return the new stable rate for the user
                        **/
                        function updateStateOnRebalance(address _reserve, address _user, uint256 _balanceIncrease)
                            external
                            onlyLendingPool
                            returns (uint256)
                        {
                            updateReserveStateOnRebalanceInternal(_reserve, _user, _balanceIncrease);
                    
                            //update user data and rebalance the rate
                            updateUserStateOnRebalanceInternal(_reserve, _user, _balanceIncrease);
                            updateReserveInterestRatesAndTimestampInternal(_reserve, 0, 0);
                            return usersReserveData[_user][_reserve].stableBorrowRate;
                        }
                    
                        /**
                        * @dev enables or disables a reserve as collateral
                        * @param _reserve the address of the principal reserve where the user deposited
                        * @param _user the address of the depositor
                        * @param _useAsCollateral true if the depositor wants to use the reserve as collateral
                        **/
                        function setUserUseReserveAsCollateral(address _reserve, address _user, bool _useAsCollateral)
                            public
                            onlyLendingPool
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            user.useAsCollateral = _useAsCollateral;
                        }
                    
                        /**
                        * @notice ETH/token transfer functions
                        **/
                    
                        /**
                        * @dev fallback function enforces that the caller is a contract, to support flashloan transfers
                        **/
                        function() external payable {
                            //only contracts can send ETH to the core
                            require(msg.sender.isContract(), "Only contracts can send ether to the Lending pool core");
                    
                        }
                    
                        /**
                        * @dev transfers to the user a specific amount from the reserve.
                        * @param _reserve the address of the reserve where the transfer is happening
                        * @param _user the address of the user receiving the transfer
                        * @param _amount the amount being transferred
                        **/
                        function transferToUser(address _reserve, address payable _user, uint256 _amount)
                            external
                            onlyLendingPool
                        {
                            if (_reserve != EthAddressLib.ethAddress()) {
                                ERC20(_reserve).safeTransfer(_user, _amount);
                            } else {
                                //solium-disable-next-line
                                (bool result, ) = _user.call.value(_amount).gas(50000)("");
                                require(result, "Transfer of ETH failed");
                            }
                        }
                    
                        /**
                        * @dev transfers the protocol fees to the fees collection address
                        * @param _token the address of the token being transferred
                        * @param _user the address of the user from where the transfer is happening
                        * @param _amount the amount being transferred
                        * @param _destination the fee receiver address
                        **/
                    
                        function transferToFeeCollectionAddress(
                            address _token,
                            address _user,
                            uint256 _amount,
                            address _destination
                        ) external payable onlyLendingPool {
                            address payable feeAddress = address(uint160(_destination)); //cast the address to payable
                    
                            if (_token != EthAddressLib.ethAddress()) {
                                require(
                                    msg.value == 0,
                                    "User is sending ETH along with the ERC20 transfer. Check the value attribute of the transaction"
                                );
                                ERC20(_token).safeTransferFrom(_user, feeAddress, _amount);
                            } else {
                                require(msg.value >= _amount, "The amount and the value sent to deposit do not match");
                                //solium-disable-next-line
                                (bool result, ) = feeAddress.call.value(_amount).gas(50000)("");
                                require(result, "Transfer of ETH failed");
                            }
                        }
                    
                        /**
                        * @dev transfers the fees to the fees collection address in the case of liquidation
                        * @param _token the address of the token being transferred
                        * @param _amount the amount being transferred
                        * @param _destination the fee receiver address
                        **/
                        function liquidateFee(
                            address _token,
                            uint256 _amount,
                            address _destination
                        ) external payable onlyLendingPool {
                            address payable feeAddress = address(uint160(_destination)); //cast the address to payable
                            require(
                                msg.value == 0,
                                "Fee liquidation does not require any transfer of value"
                            );
                    
                            if (_token != EthAddressLib.ethAddress()) {
                                ERC20(_token).safeTransfer(feeAddress, _amount);
                            } else {
                                //solium-disable-next-line
                                (bool result, ) = feeAddress.call.value(_amount).gas(50000)("");
                                require(result, "Transfer of ETH failed");
                            }
                        }
                    
                        /**
                        * @dev transfers an amount from a user to the destination reserve
                        * @param _reserve the address of the reserve where the amount is being transferred
                        * @param _user the address of the user from where the transfer is happening
                        * @param _amount the amount being transferred
                        **/
                        function transferToReserve(address _reserve, address payable _user, uint256 _amount)
                            external
                            payable
                            onlyLendingPool
                        {
                            if (_reserve != EthAddressLib.ethAddress()) {
                                require(msg.value == 0, "User is sending ETH along with the ERC20 transfer.");
                                ERC20(_reserve).safeTransferFrom(_user, address(this), _amount);
                    
                            } else {
                                require(msg.value >= _amount, "The amount and the value sent to deposit do not match");
                    
                                if (msg.value > _amount) {
                                    //send back excess ETH
                                    uint256 excessAmount = msg.value.sub(_amount);
                                    //solium-disable-next-line
                                    (bool result, ) = _user.call.value(excessAmount).gas(50000)("");
                                    require(result, "Transfer of ETH failed");
                                }
                            }
                        }
                    
                        /**
                        * @notice data access functions
                        **/
                    
                        /**
                        * @dev returns the basic data (balances, fee accrued, reserve enabled/disabled as collateral)
                        * needed to calculate the global account data in the LendingPoolDataProvider
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        * @return the user deposited balance, the principal borrow balance, the fee, and if the reserve is enabled as collateral or not
                        **/
                        function getUserBasicReserveData(address _reserve, address _user)
                            external
                            view
                            returns (uint256, uint256, uint256, bool)
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            uint256 underlyingBalance = getUserUnderlyingAssetBalance(_reserve, _user);
                    
                            if (user.principalBorrowBalance == 0) {
                                return (underlyingBalance, 0, 0, user.useAsCollateral);
                            }
                    
                            return (
                                underlyingBalance,
                                user.getCompoundedBorrowBalance(reserve),
                                user.originationFee,
                                user.useAsCollateral
                            );
                        }
                    
                        /**
                        * @dev checks if a user is allowed to borrow at a stable rate
                        * @param _reserve the reserve address
                        * @param _user the user
                        * @param _amount the amount the the user wants to borrow
                        * @return true if the user is allowed to borrow at a stable rate, false otherwise
                        **/
                    
                        function isUserAllowedToBorrowAtStable(address _reserve, address _user, uint256 _amount)
                            external
                            view
                            returns (bool)
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            if (!reserve.isStableBorrowRateEnabled) return false;
                    
                            return
                                !user.useAsCollateral ||
                                !reserve.usageAsCollateralEnabled ||
                                _amount > getUserUnderlyingAssetBalance(_reserve, _user);
                        }
                    
                        /**
                        * @dev gets the underlying asset balance of a user based on the corresponding aToken balance.
                        * @param _reserve the reserve address
                        * @param _user the user address
                        * @return the underlying deposit balance of the user
                        **/
                    
                        function getUserUnderlyingAssetBalance(address _reserve, address _user)
                            public
                            view
                            returns (uint256)
                        {
                            AToken aToken = AToken(reserves[_reserve].aTokenAddress);
                            return aToken.balanceOf(_user);
                    
                        }
                    
                        /**
                        * @dev gets the interest rate strategy contract address for the reserve
                        * @param _reserve the reserve address
                        * @return the address of the interest rate strategy contract
                        **/
                        function getReserveInterestRateStrategyAddress(address _reserve) public view returns (address) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.interestRateStrategyAddress;
                        }
                    
                        /**
                        * @dev gets the aToken contract address for the reserve
                        * @param _reserve the reserve address
                        * @return the address of the aToken contract
                        **/
                    
                        function getReserveATokenAddress(address _reserve) public view returns (address) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.aTokenAddress;
                        }
                    
                        /**
                        * @dev gets the available liquidity in the reserve. The available liquidity is the balance of the core contract
                        * @param _reserve the reserve address
                        * @return the available liquidity
                        **/
                        function getReserveAvailableLiquidity(address _reserve) public view returns (uint256) {
                            uint256 balance = 0;
                    
                            if (_reserve == EthAddressLib.ethAddress()) {
                                balance = address(this).balance;
                            } else {
                                balance = IERC20(_reserve).balanceOf(address(this));
                            }
                            return balance;
                        }
                    
                        /**
                        * @dev gets the total liquidity in the reserve. The total liquidity is the balance of the core contract + total borrows
                        * @param _reserve the reserve address
                        * @return the total liquidity
                        **/
                        function getReserveTotalLiquidity(address _reserve) public view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return getReserveAvailableLiquidity(_reserve).add(reserve.getTotalBorrows());
                        }
                    
                        /**
                        * @dev gets the normalized income of the reserve. a value of 1e27 means there is no income. A value of 2e27 means there
                        * there has been 100% income.
                        * @param _reserve the reserve address
                        * @return the reserve normalized income
                        **/
                        function getReserveNormalizedIncome(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.getNormalizedIncome();
                        }
                    
                        /**
                        * @dev gets the reserve total borrows
                        * @param _reserve the reserve address
                        * @return the total borrows (stable + variable)
                        **/
                        function getReserveTotalBorrows(address _reserve) public view returns (uint256) {
                            return reserves[_reserve].getTotalBorrows();
                        }
                    
                        /**
                        * @dev gets the reserve total borrows stable
                        * @param _reserve the reserve address
                        * @return the total borrows stable
                        **/
                        function getReserveTotalBorrowsStable(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.totalBorrowsStable;
                        }
                    
                        /**
                        * @dev gets the reserve total borrows variable
                        * @param _reserve the reserve address
                        * @return the total borrows variable
                        **/
                    
                        function getReserveTotalBorrowsVariable(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.totalBorrowsVariable;
                        }
                    
                        /**
                        * @dev gets the reserve liquidation threshold
                        * @param _reserve the reserve address
                        * @return the reserve liquidation threshold
                        **/
                    
                        function getReserveLiquidationThreshold(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.liquidationThreshold;
                        }
                    
                        /**
                        * @dev gets the reserve liquidation bonus
                        * @param _reserve the reserve address
                        * @return the reserve liquidation bonus
                        **/
                    
                        function getReserveLiquidationBonus(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.liquidationBonus;
                        }
                    
                        /**
                        * @dev gets the reserve current variable borrow rate. Is the base variable borrow rate if the reserve is empty
                        * @param _reserve the reserve address
                        * @return the reserve current variable borrow rate
                        **/
                    
                        function getReserveCurrentVariableBorrowRate(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                    
                            if (reserve.currentVariableBorrowRate == 0) {
                                return
                                    IReserveInterestRateStrategy(reserve.interestRateStrategyAddress)
                                    .getBaseVariableBorrowRate();
                            }
                            return reserve.currentVariableBorrowRate;
                        }
                    
                        /**
                        * @dev gets the reserve current stable borrow rate. Is the market rate if the reserve is empty
                        * @param _reserve the reserve address
                        * @return the reserve current stable borrow rate
                        **/
                    
                        function getReserveCurrentStableBorrowRate(address _reserve) public view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            ILendingRateOracle oracle = ILendingRateOracle(addressesProvider.getLendingRateOracle());
                    
                            if (reserve.currentStableBorrowRate == 0) {
                                //no stable rate borrows yet
                                return oracle.getMarketBorrowRate(_reserve);
                            }
                    
                            return reserve.currentStableBorrowRate;
                        }
                    
                        /**
                        * @dev gets the reserve average stable borrow rate. The average stable rate is the weighted average
                        * of all the loans taken at stable rate.
                        * @param _reserve the reserve address
                        * @return the reserve current average borrow rate
                        **/
                        function getReserveCurrentAverageStableBorrowRate(address _reserve)
                            external
                            view
                            returns (uint256)
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.currentAverageStableBorrowRate;
                        }
                    
                        /**
                        * @dev gets the reserve liquidity rate
                        * @param _reserve the reserve address
                        * @return the reserve liquidity rate
                        **/
                        function getReserveCurrentLiquidityRate(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.currentLiquidityRate;
                        }
                    
                        /**
                        * @dev gets the reserve liquidity cumulative index
                        * @param _reserve the reserve address
                        * @return the reserve liquidity cumulative index
                        **/
                        function getReserveLiquidityCumulativeIndex(address _reserve) external view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.lastLiquidityCumulativeIndex;
                        }
                    
                        /**
                        * @dev gets the reserve variable borrow index
                        * @param _reserve the reserve address
                        * @return the reserve variable borrow index
                        **/
                        function getReserveVariableBorrowsCumulativeIndex(address _reserve)
                            external
                            view
                            returns (uint256)
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.lastVariableBorrowCumulativeIndex;
                        }
                    
                        /**
                        * @dev this function aggregates the configuration parameters of the reserve.
                        * It's used in the LendingPoolDataProvider specifically to save gas, and avoid
                        * multiple external contract calls to fetch the same data.
                        * @param _reserve the reserve address
                        * @return the reserve decimals
                        * @return the base ltv as collateral
                        * @return the liquidation threshold
                        * @return if the reserve is used as collateral or not
                        **/
                        function getReserveConfiguration(address _reserve)
                            external
                            view
                            returns (uint256, uint256, uint256, bool)
                        {
                            uint256 decimals;
                            uint256 baseLTVasCollateral;
                            uint256 liquidationThreshold;
                            bool usageAsCollateralEnabled;
                    
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            decimals = reserve.decimals;
                            baseLTVasCollateral = reserve.baseLTVasCollateral;
                            liquidationThreshold = reserve.liquidationThreshold;
                            usageAsCollateralEnabled = reserve.usageAsCollateralEnabled;
                    
                            return (decimals, baseLTVasCollateral, liquidationThreshold, usageAsCollateralEnabled);
                        }
                    
                        /**
                        * @dev returns the decimals of the reserve
                        * @param _reserve the reserve address
                        * @return the reserve decimals
                        **/
                        function getReserveDecimals(address _reserve) external view returns (uint256) {
                            return reserves[_reserve].decimals;
                        }
                    
                        /**
                        * @dev returns true if the reserve is enabled for borrowing
                        * @param _reserve the reserve address
                        * @return true if the reserve is enabled for borrowing, false otherwise
                        **/
                    
                        function isReserveBorrowingEnabled(address _reserve) external view returns (bool) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.borrowingEnabled;
                        }
                    
                        /**
                        * @dev returns true if the reserve is enabled as collateral
                        * @param _reserve the reserve address
                        * @return true if the reserve is enabled as collateral, false otherwise
                        **/
                    
                        function isReserveUsageAsCollateralEnabled(address _reserve) external view returns (bool) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.usageAsCollateralEnabled;
                        }
                    
                        /**
                        * @dev returns true if the stable rate is enabled on reserve
                        * @param _reserve the reserve address
                        * @return true if the stable rate is enabled on reserve, false otherwise
                        **/
                        function getReserveIsStableBorrowRateEnabled(address _reserve) external view returns (bool) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.isStableBorrowRateEnabled;
                        }
                    
                        /**
                        * @dev returns true if the reserve is active
                        * @param _reserve the reserve address
                        * @return true if the reserve is active, false otherwise
                        **/
                        function getReserveIsActive(address _reserve) external view returns (bool) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.isActive;
                        }
                    
                        /**
                        * @notice returns if a reserve is freezed
                        * @param _reserve the reserve for which the information is needed
                        * @return true if the reserve is freezed, false otherwise
                        **/
                    
                        function getReserveIsFreezed(address _reserve) external view returns (bool) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            return reserve.isFreezed;
                        }
                    
                        /**
                        * @notice returns the timestamp of the last action on the reserve
                        * @param _reserve the reserve for which the information is needed
                        * @return the last updated timestamp of the reserve
                        **/
                    
                        function getReserveLastUpdate(address _reserve) external view returns (uint40 timestamp) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            timestamp = reserve.lastUpdateTimestamp;
                        }
                    
                        /**
                        * @dev returns the utilization rate U of a specific reserve
                        * @param _reserve the reserve for which the information is needed
                        * @return the utilization rate in ray
                        **/
                    
                        function getReserveUtilizationRate(address _reserve) public view returns (uint256) {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                    
                            uint256 totalBorrows = reserve.getTotalBorrows();
                    
                            if (totalBorrows == 0) {
                                return 0;
                            }
                    
                            uint256 availableLiquidity = getReserveAvailableLiquidity(_reserve);
                    
                            return totalBorrows.rayDiv(availableLiquidity.add(totalBorrows));
                        }
                    
                        /**
                        * @return the array of reserves configured on the core
                        **/
                        function getReserves() external view returns (address[] memory) {
                            return reservesList;
                        }
                    
                        /**
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return true if the user has chosen to use the reserve as collateral, false otherwise
                        **/
                        function isUserUseReserveAsCollateralEnabled(address _reserve, address _user)
                            external
                            view
                            returns (bool)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            return user.useAsCollateral;
                        }
                    
                        /**
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return the origination fee for the user
                        **/
                        function getUserOriginationFee(address _reserve, address _user)
                            external
                            view
                            returns (uint256)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            return user.originationFee;
                        }
                    
                        /**
                        * @dev users with no loans in progress have NONE as borrow rate mode
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return the borrow rate mode for the user,
                        **/
                    
                        function getUserCurrentBorrowRateMode(address _reserve, address _user)
                            public
                            view
                            returns (CoreLibrary.InterestRateMode)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            if (user.principalBorrowBalance == 0) {
                                return CoreLibrary.InterestRateMode.NONE;
                            }
                    
                            return
                                user.stableBorrowRate > 0
                                ? CoreLibrary.InterestRateMode.STABLE
                                : CoreLibrary.InterestRateMode.VARIABLE;
                        }
                    
                        /**
                        * @dev gets the current borrow rate of the user
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return the borrow rate for the user,
                        **/
                        function getUserCurrentBorrowRate(address _reserve, address _user)
                            internal
                            view
                            returns (uint256)
                        {
                            CoreLibrary.InterestRateMode rateMode = getUserCurrentBorrowRateMode(_reserve, _user);
                    
                            if (rateMode == CoreLibrary.InterestRateMode.NONE) {
                                return 0;
                            }
                    
                            return
                                rateMode == CoreLibrary.InterestRateMode.STABLE
                                ? usersReserveData[_user][_reserve].stableBorrowRate
                                : reserves[_reserve].currentVariableBorrowRate;
                        }
                    
                        /**
                        * @dev the stable rate returned is 0 if the user is borrowing at variable or not borrowing at all
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return the user stable rate
                        **/
                        function getUserCurrentStableBorrowRate(address _reserve, address _user)
                            external
                            view
                            returns (uint256)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            return user.stableBorrowRate;
                        }
                    
                        /**
                        * @dev calculates and returns the borrow balances of the user
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        * @return the principal borrow balance, the compounded balance and the balance increase since the last borrow/repay/swap/rebalance
                        **/
                    
                        function getUserBorrowBalances(address _reserve, address _user)
                            public
                            view
                            returns (uint256, uint256, uint256)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            if (user.principalBorrowBalance == 0) {
                                return (0, 0, 0);
                            }
                    
                            uint256 principal = user.principalBorrowBalance;
                            uint256 compoundedBalance = CoreLibrary.getCompoundedBorrowBalance(
                                user,
                                reserves[_reserve]
                            );
                            return (principal, compoundedBalance, compoundedBalance.sub(principal));
                        }
                    
                        /**
                        * @dev the variable borrow index of the user is 0 if the user is not borrowing or borrowing at stable
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return the variable borrow index for the user
                        **/
                    
                        function getUserVariableBorrowCumulativeIndex(address _reserve, address _user)
                            external
                            view
                            returns (uint256)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            return user.lastVariableBorrowCumulativeIndex;
                        }
                    
                        /**
                        * @dev the variable borrow index of the user is 0 if the user is not borrowing or borrowing at stable
                        * @param _reserve the address of the reserve for which the information is needed
                        * @param _user the address of the user for which the information is needed
                        * @return the variable borrow index for the user
                        **/
                    
                        function getUserLastUpdate(address _reserve, address _user)
                            external
                            view
                            returns (uint256 timestamp)
                        {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            timestamp = user.lastUpdateTimestamp;
                        }
                    
                        /**
                        * @dev updates the lending pool core configuration
                        **/
                        function refreshConfiguration() external onlyLendingPoolConfigurator {
                            refreshConfigInternal();
                        }
                    
                        /**
                        * @dev initializes a reserve
                        * @param _reserve the address of the reserve
                        * @param _aTokenAddress the address of the overlying aToken contract
                        * @param _decimals the decimals of the reserve currency
                        * @param _interestRateStrategyAddress the address of the interest rate strategy contract
                        **/
                        function initReserve(
                            address _reserve,
                            address _aTokenAddress,
                            uint256 _decimals,
                            address _interestRateStrategyAddress
                        ) external onlyLendingPoolConfigurator {
                            reserves[_reserve].init(_aTokenAddress, _decimals, _interestRateStrategyAddress);
                            addReserveToListInternal(_reserve);
                    
                        }
                    
                    
                    
                        /**
                        * @dev removes the last added reserve in the reservesList array
                        * @param _reserveToRemove the address of the reserve
                        **/
                        function removeLastAddedReserve(address _reserveToRemove)
                         external onlyLendingPoolConfigurator {
                    
                            address lastReserve = reservesList[reservesList.length-1];
                    
                            require(lastReserve == _reserveToRemove, "Reserve being removed is different than the reserve requested");
                    
                            //as we can't check if totalLiquidity is 0 (since the reserve added might not be an ERC20) we at least check that there is nothing borrowed
                            require(getReserveTotalBorrows(lastReserve) == 0, "Cannot remove a reserve with liquidity deposited");
                    
                            reserves[lastReserve].isActive = false;
                            reserves[lastReserve].aTokenAddress = address(0);
                            reserves[lastReserve].decimals = 0;
                            reserves[lastReserve].lastLiquidityCumulativeIndex = 0;
                            reserves[lastReserve].lastVariableBorrowCumulativeIndex = 0;
                            reserves[lastReserve].borrowingEnabled = false;
                            reserves[lastReserve].usageAsCollateralEnabled = false;
                            reserves[lastReserve].baseLTVasCollateral = 0;
                            reserves[lastReserve].liquidationThreshold = 0;
                            reserves[lastReserve].liquidationBonus = 0;
                            reserves[lastReserve].interestRateStrategyAddress = address(0);
                    
                            reservesList.pop();
                        }
                    
                        /**
                        * @dev updates the address of the interest rate strategy contract
                        * @param _reserve the address of the reserve
                        * @param _rateStrategyAddress the address of the interest rate strategy contract
                        **/
                    
                        function setReserveInterestRateStrategyAddress(address _reserve, address _rateStrategyAddress)
                            external
                            onlyLendingPoolConfigurator
                        {
                            reserves[_reserve].interestRateStrategyAddress = _rateStrategyAddress;
                        }
                    
                        /**
                        * @dev enables borrowing on a reserve. Also sets the stable rate borrowing
                        * @param _reserve the address of the reserve
                        * @param _stableBorrowRateEnabled true if the stable rate needs to be enabled, false otherwise
                        **/
                    
                        function enableBorrowingOnReserve(address _reserve, bool _stableBorrowRateEnabled)
                            external
                            onlyLendingPoolConfigurator
                        {
                            reserves[_reserve].enableBorrowing(_stableBorrowRateEnabled);
                        }
                    
                        /**
                        * @dev disables borrowing on a reserve
                        * @param _reserve the address of the reserve
                        **/
                    
                        function disableBorrowingOnReserve(address _reserve) external onlyLendingPoolConfigurator {
                            reserves[_reserve].disableBorrowing();
                        }
                    
                        /**
                        * @dev enables a reserve to be used as collateral
                        * @param _reserve the address of the reserve
                        **/
                        function enableReserveAsCollateral(
                            address _reserve,
                            uint256 _baseLTVasCollateral,
                            uint256 _liquidationThreshold,
                            uint256 _liquidationBonus
                        ) external onlyLendingPoolConfigurator {
                            reserves[_reserve].enableAsCollateral(
                                _baseLTVasCollateral,
                                _liquidationThreshold,
                                _liquidationBonus
                            );
                        }
                    
                        /**
                        * @dev disables a reserve to be used as collateral
                        * @param _reserve the address of the reserve
                        **/
                        function disableReserveAsCollateral(address _reserve) external onlyLendingPoolConfigurator {
                            reserves[_reserve].disableAsCollateral();
                        }
                    
                        /**
                        * @dev enable the stable borrow rate mode on a reserve
                        * @param _reserve the address of the reserve
                        **/
                        function enableReserveStableBorrowRate(address _reserve) external onlyLendingPoolConfigurator {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.isStableBorrowRateEnabled = true;
                        }
                    
                        /**
                        * @dev disable the stable borrow rate mode on a reserve
                        * @param _reserve the address of the reserve
                        **/
                        function disableReserveStableBorrowRate(address _reserve) external onlyLendingPoolConfigurator {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.isStableBorrowRateEnabled = false;
                        }
                    
                        /**
                        * @dev activates a reserve
                        * @param _reserve the address of the reserve
                        **/
                        function activateReserve(address _reserve) external onlyLendingPoolConfigurator {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                    
                            require(
                                reserve.lastLiquidityCumulativeIndex > 0 &&
                                    reserve.lastVariableBorrowCumulativeIndex > 0,
                                "Reserve has not been initialized yet"
                            );
                            reserve.isActive = true;
                        }
                    
                        /**
                        * @dev deactivates a reserve
                        * @param _reserve the address of the reserve
                        **/
                        function deactivateReserve(address _reserve) external onlyLendingPoolConfigurator {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.isActive = false;
                        }
                    
                        /**
                        * @notice allows the configurator to freeze the reserve.
                        * A freezed reserve does not allow any action apart from repay, redeem, liquidationCall, rebalance.
                        * @param _reserve the address of the reserve
                        **/
                        function freezeReserve(address _reserve) external onlyLendingPoolConfigurator {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.isFreezed = true;
                        }
                    
                        /**
                        * @notice allows the configurator to unfreeze the reserve. A unfreezed reserve allows any action to be executed.
                        * @param _reserve the address of the reserve
                        **/
                        function unfreezeReserve(address _reserve) external onlyLendingPoolConfigurator {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.isFreezed = false;
                        }
                    
                        /**
                        * @notice allows the configurator to update the loan to value of a reserve
                        * @param _reserve the address of the reserve
                        * @param _ltv the new loan to value
                        **/
                        function setReserveBaseLTVasCollateral(address _reserve, uint256 _ltv)
                            external
                            onlyLendingPoolConfigurator
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.baseLTVasCollateral = _ltv;
                        }
                    
                        /**
                        * @notice allows the configurator to update the liquidation threshold of a reserve
                        * @param _reserve the address of the reserve
                        * @param _threshold the new liquidation threshold
                        **/
                        function setReserveLiquidationThreshold(address _reserve, uint256 _threshold)
                            external
                            onlyLendingPoolConfigurator
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.liquidationThreshold = _threshold;
                        }
                    
                        /**
                        * @notice allows the configurator to update the liquidation bonus of a reserve
                        * @param _reserve the address of the reserve
                        * @param _bonus the new liquidation bonus
                        **/
                        function setReserveLiquidationBonus(address _reserve, uint256 _bonus)
                            external
                            onlyLendingPoolConfigurator
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.liquidationBonus = _bonus;
                        }
                    
                        /**
                        * @notice allows the configurator to update the reserve decimals
                        * @param _reserve the address of the reserve
                        * @param _decimals the decimals of the reserve
                        **/
                        function setReserveDecimals(address _reserve, uint256 _decimals)
                            external
                            onlyLendingPoolConfigurator
                        {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            reserve.decimals = _decimals;
                        }
                    
                        /**
                        * @notice internal functions
                        **/
                    
                        /**
                        * @dev updates the state of a reserve as a consequence of a borrow action.
                        * @param _reserve the address of the reserve on which the user is borrowing
                        * @param _user the address of the borrower
                        * @param _principalBorrowBalance the previous borrow balance of the borrower before the action
                        * @param _balanceIncrease the accrued interest of the user on the previous borrowed amount
                        * @param _amountBorrowed the new amount borrowed
                        * @param _rateMode the borrow rate mode (stable, variable)
                        **/
                    
                        function updateReserveStateOnBorrowInternal(
                            address _reserve,
                            address _user,
                            uint256 _principalBorrowBalance,
                            uint256 _balanceIncrease,
                            uint256 _amountBorrowed,
                            CoreLibrary.InterestRateMode _rateMode
                        ) internal {
                            reserves[_reserve].updateCumulativeIndexes();
                    
                            //increasing reserve total borrows to account for the new borrow balance of the user
                            //NOTE: Depending on the previous borrow mode, the borrows might need to be switched from variable to stable or vice versa
                    
                            updateReserveTotalBorrowsByRateModeInternal(
                                _reserve,
                                _user,
                                _principalBorrowBalance,
                                _balanceIncrease,
                                _amountBorrowed,
                                _rateMode
                            );
                        }
                    
                        /**
                        * @dev updates the state of a user as a consequence of a borrow action.
                        * @param _reserve the address of the reserve on which the user is borrowing
                        * @param _user the address of the borrower
                        * @param _amountBorrowed the amount borrowed
                        * @param _balanceIncrease the accrued interest of the user on the previous borrowed amount
                        * @param _rateMode the borrow rate mode (stable, variable)
                        * @return the final borrow rate for the user. Emitted by the borrow() event
                        **/
                    
                        function updateUserStateOnBorrowInternal(
                            address _reserve,
                            address _user,
                            uint256 _amountBorrowed,
                            uint256 _balanceIncrease,
                            uint256 _fee,
                            CoreLibrary.InterestRateMode _rateMode
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            if (_rateMode == CoreLibrary.InterestRateMode.STABLE) {
                                //stable
                                //reset the user variable index, and update the stable rate
                                user.stableBorrowRate = reserve.currentStableBorrowRate;
                                user.lastVariableBorrowCumulativeIndex = 0;
                            } else if (_rateMode == CoreLibrary.InterestRateMode.VARIABLE) {
                                //variable
                                //reset the user stable rate, and store the new borrow index
                                user.stableBorrowRate = 0;
                                user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex;
                            } else {
                                revert("Invalid borrow rate mode");
                            }
                            //increase the principal borrows and the origination fee
                            user.principalBorrowBalance = user.principalBorrowBalance.add(_amountBorrowed).add(
                                _balanceIncrease
                            );
                            user.originationFee = user.originationFee.add(_fee);
                    
                            //solium-disable-next-line
                            user.lastUpdateTimestamp = uint40(block.timestamp);
                    
                        }
                    
                        /**
                        * @dev updates the state of the reserve as a consequence of a repay action.
                        * @param _reserve the address of the reserve on which the user is repaying
                        * @param _user the address of the borrower
                        * @param _paybackAmountMinusFees the amount being paid back minus fees
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        **/
                    
                        function updateReserveStateOnRepayInternal(
                            address _reserve,
                            address _user,
                            uint256 _paybackAmountMinusFees,
                            uint256 _balanceIncrease
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_reserve][_user];
                    
                            CoreLibrary.InterestRateMode borrowRateMode = getUserCurrentBorrowRateMode(_reserve, _user);
                    
                            //update the indexes
                            reserves[_reserve].updateCumulativeIndexes();
                    
                            //compound the cumulated interest to the borrow balance and then subtracting the payback amount
                            if (borrowRateMode == CoreLibrary.InterestRateMode.STABLE) {
                                reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                                    _balanceIncrease,
                                    user.stableBorrowRate
                                );
                                reserve.decreaseTotalBorrowsStableAndUpdateAverageRate(
                                    _paybackAmountMinusFees,
                                    user.stableBorrowRate
                                );
                            } else {
                                reserve.increaseTotalBorrowsVariable(_balanceIncrease);
                                reserve.decreaseTotalBorrowsVariable(_paybackAmountMinusFees);
                            }
                        }
                    
                        /**
                        * @dev updates the state of the user as a consequence of a repay action.
                        * @param _reserve the address of the reserve on which the user is repaying
                        * @param _user the address of the borrower
                        * @param _paybackAmountMinusFees the amount being paid back minus fees
                        * @param _originationFeeRepaid the fee on the amount that is being repaid
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @param _repaidWholeLoan true if the user is repaying the whole loan
                        **/
                        function updateUserStateOnRepayInternal(
                            address _reserve,
                            address _user,
                            uint256 _paybackAmountMinusFees,
                            uint256 _originationFeeRepaid,
                            uint256 _balanceIncrease,
                            bool _repaidWholeLoan
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            //update the user principal borrow balance, adding the cumulated interest and then subtracting the payback amount
                            user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease).sub(
                                _paybackAmountMinusFees
                            );
                            user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex;
                    
                            //if the balance decrease is equal to the previous principal (user is repaying the whole loan)
                            //and the rate mode is stable, we reset the interest rate mode of the user
                            if (_repaidWholeLoan) {
                                user.stableBorrowRate = 0;
                                user.lastVariableBorrowCumulativeIndex = 0;
                            }
                            user.originationFee = user.originationFee.sub(_originationFeeRepaid);
                    
                            //solium-disable-next-line
                            user.lastUpdateTimestamp = uint40(block.timestamp);
                    
                        }
                    
                        /**
                        * @dev updates the state of the user as a consequence of a swap rate action.
                        * @param _reserve the address of the reserve on which the user is performing the rate swap
                        * @param _user the address of the borrower
                        * @param _principalBorrowBalance the the principal amount borrowed by the user
                        * @param _compoundedBorrowBalance the principal amount plus the accrued interest
                        * @param _currentRateMode the rate mode at which the user borrowed
                        **/
                        function updateReserveStateOnSwapRateInternal(
                            address _reserve,
                            address _user,
                            uint256 _principalBorrowBalance,
                            uint256 _compoundedBorrowBalance,
                            CoreLibrary.InterestRateMode _currentRateMode
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            //compounding reserve indexes
                            reserve.updateCumulativeIndexes();
                    
                            if (_currentRateMode == CoreLibrary.InterestRateMode.STABLE) {
                                uint256 userCurrentStableRate = user.stableBorrowRate;
                    
                                //swap to variable
                                reserve.decreaseTotalBorrowsStableAndUpdateAverageRate(
                                    _principalBorrowBalance,
                                    userCurrentStableRate
                                ); //decreasing stable from old principal balance
                                reserve.increaseTotalBorrowsVariable(_compoundedBorrowBalance); //increase variable borrows
                            } else if (_currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) {
                                //swap to stable
                                uint256 currentStableRate = reserve.currentStableBorrowRate;
                                reserve.decreaseTotalBorrowsVariable(_principalBorrowBalance);
                                reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                                    _compoundedBorrowBalance,
                                    currentStableRate
                                );
                    
                            } else {
                                revert("Invalid rate mode received");
                            }
                        }
                    
                        /**
                        * @dev updates the state of the user as a consequence of a swap rate action.
                        * @param _reserve the address of the reserve on which the user is performing the swap
                        * @param _user the address of the borrower
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @param _currentRateMode the current rate mode of the user
                        **/
                    
                        function updateUserStateOnSwapRateInternal(
                            address _reserve,
                            address _user,
                            uint256 _balanceIncrease,
                            CoreLibrary.InterestRateMode _currentRateMode
                        ) internal returns (CoreLibrary.InterestRateMode) {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                    
                            CoreLibrary.InterestRateMode newMode = CoreLibrary.InterestRateMode.NONE;
                    
                            if (_currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) {
                                //switch to stable
                                newMode = CoreLibrary.InterestRateMode.STABLE;
                                user.stableBorrowRate = reserve.currentStableBorrowRate;
                                user.lastVariableBorrowCumulativeIndex = 0;
                            } else if (_currentRateMode == CoreLibrary.InterestRateMode.STABLE) {
                                newMode = CoreLibrary.InterestRateMode.VARIABLE;
                                user.stableBorrowRate = 0;
                                user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex;
                            } else {
                                revert("Invalid interest rate mode received");
                            }
                            //compounding cumulated interest
                            user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease);
                            //solium-disable-next-line
                            user.lastUpdateTimestamp = uint40(block.timestamp);
                    
                            return newMode;
                        }
                    
                        /**
                        * @dev updates the state of the principal reserve as a consequence of a liquidation action.
                        * @param _principalReserve the address of the principal reserve that is being repaid
                        * @param _user the address of the borrower
                        * @param _amountToLiquidate the amount being repaid by the liquidator
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        **/
                    
                        function updatePrincipalReserveStateOnLiquidationInternal(
                            address _principalReserve,
                            address _user,
                            uint256 _amountToLiquidate,
                            uint256 _balanceIncrease
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_principalReserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_principalReserve];
                    
                            //update principal reserve data
                            reserve.updateCumulativeIndexes();
                    
                            CoreLibrary.InterestRateMode borrowRateMode = getUserCurrentBorrowRateMode(
                                _principalReserve,
                                _user
                            );
                    
                            if (borrowRateMode == CoreLibrary.InterestRateMode.STABLE) {
                                //increase the total borrows by the compounded interest
                                reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                                    _balanceIncrease,
                                    user.stableBorrowRate
                                );
                    
                                //decrease by the actual amount to liquidate
                                reserve.decreaseTotalBorrowsStableAndUpdateAverageRate(
                                    _amountToLiquidate,
                                    user.stableBorrowRate
                                );
                    
                            } else {
                                //increase the total borrows by the compounded interest
                                reserve.increaseTotalBorrowsVariable(_balanceIncrease);
                    
                                //decrease by the actual amount to liquidate
                                reserve.decreaseTotalBorrowsVariable(_amountToLiquidate);
                            }
                    
                        }
                    
                        /**
                        * @dev updates the state of the collateral reserve as a consequence of a liquidation action.
                        * @param _collateralReserve the address of the collateral reserve that is being liquidated
                        **/
                        function updateCollateralReserveStateOnLiquidationInternal(
                            address _collateralReserve
                        ) internal {
                            //update collateral reserve
                            reserves[_collateralReserve].updateCumulativeIndexes();
                    
                        }
                    
                        /**
                        * @dev updates the state of the user being liquidated as a consequence of a liquidation action.
                        * @param _reserve the address of the principal reserve that is being repaid
                        * @param _user the address of the borrower
                        * @param _amountToLiquidate the amount being repaid by the liquidator
                        * @param _feeLiquidated the amount of origination fee being liquidated
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        **/
                        function updateUserStateOnLiquidationInternal(
                            address _reserve,
                            address _user,
                            uint256 _amountToLiquidate,
                            uint256 _feeLiquidated,
                            uint256 _balanceIncrease
                        ) internal {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            //first increase by the compounded interest, then decrease by the liquidated amount
                            user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease).sub(
                                _amountToLiquidate
                            );
                    
                            if (
                                getUserCurrentBorrowRateMode(_reserve, _user) == CoreLibrary.InterestRateMode.VARIABLE
                            ) {
                                user.lastVariableBorrowCumulativeIndex = reserve.lastVariableBorrowCumulativeIndex;
                            }
                    
                            if(_feeLiquidated > 0){
                                user.originationFee = user.originationFee.sub(_feeLiquidated);
                            }
                    
                            //solium-disable-next-line
                            user.lastUpdateTimestamp = uint40(block.timestamp);
                        }
                    
                        /**
                        * @dev updates the state of the reserve as a consequence of a stable rate rebalance
                        * @param _reserve the address of the principal reserve where the user borrowed
                        * @param _user the address of the borrower
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        **/
                    
                        function updateReserveStateOnRebalanceInternal(
                            address _reserve,
                            address _user,
                            uint256 _balanceIncrease
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                    
                            reserve.updateCumulativeIndexes();
                    
                            reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                                _balanceIncrease,
                                user.stableBorrowRate
                            );
                    
                        }
                    
                        /**
                        * @dev updates the state of the user as a consequence of a stable rate rebalance
                        * @param _reserve the address of the principal reserve where the user borrowed
                        * @param _user the address of the borrower
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        **/
                    
                        function updateUserStateOnRebalanceInternal(
                            address _reserve,
                            address _user,
                            uint256 _balanceIncrease
                        ) internal {
                            CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                    
                            user.principalBorrowBalance = user.principalBorrowBalance.add(_balanceIncrease);
                            user.stableBorrowRate = reserve.currentStableBorrowRate;
                    
                            //solium-disable-next-line
                            user.lastUpdateTimestamp = uint40(block.timestamp);
                        }
                    
                        /**
                        * @dev updates the state of the user as a consequence of a stable rate rebalance
                        * @param _reserve the address of the principal reserve where the user borrowed
                        * @param _user the address of the borrower
                        * @param _balanceIncrease the accrued interest on the borrowed amount
                        * @param _amountBorrowed the accrued interest on the borrowed amount
                        **/
                        function updateReserveTotalBorrowsByRateModeInternal(
                            address _reserve,
                            address _user,
                            uint256 _principalBalance,
                            uint256 _balanceIncrease,
                            uint256 _amountBorrowed,
                            CoreLibrary.InterestRateMode _newBorrowRateMode
                        ) internal {
                            CoreLibrary.InterestRateMode previousRateMode = getUserCurrentBorrowRateMode(
                                _reserve,
                                _user
                            );
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                    
                            if (previousRateMode == CoreLibrary.InterestRateMode.STABLE) {
                                CoreLibrary.UserReserveData storage user = usersReserveData[_user][_reserve];
                                reserve.decreaseTotalBorrowsStableAndUpdateAverageRate(
                                    _principalBalance,
                                    user.stableBorrowRate
                                );
                            } else if (previousRateMode == CoreLibrary.InterestRateMode.VARIABLE) {
                                reserve.decreaseTotalBorrowsVariable(_principalBalance);
                            }
                    
                            uint256 newPrincipalAmount = _principalBalance.add(_balanceIncrease).add(_amountBorrowed);
                            if (_newBorrowRateMode == CoreLibrary.InterestRateMode.STABLE) {
                                reserve.increaseTotalBorrowsStableAndUpdateAverageRate(
                                    newPrincipalAmount,
                                    reserve.currentStableBorrowRate
                                );
                            } else if (_newBorrowRateMode == CoreLibrary.InterestRateMode.VARIABLE) {
                                reserve.increaseTotalBorrowsVariable(newPrincipalAmount);
                            } else {
                                revert("Invalid new borrow rate mode");
                            }
                        }
                    
                        /**
                        * @dev Updates the reserve current stable borrow rate Rf, the current variable borrow rate Rv and the current liquidity rate Rl.
                        * Also updates the lastUpdateTimestamp value. Please refer to the whitepaper for further information.
                        * @param _reserve the address of the reserve to be updated
                        * @param _liquidityAdded the amount of liquidity added to the protocol (deposit or repay) in the previous action
                        * @param _liquidityTaken the amount of liquidity taken from the protocol (redeem or borrow)
                        **/
                    
                        function updateReserveInterestRatesAndTimestampInternal(
                            address _reserve,
                            uint256 _liquidityAdded,
                            uint256 _liquidityTaken
                        ) internal {
                            CoreLibrary.ReserveData storage reserve = reserves[_reserve];
                            (uint256 newLiquidityRate, uint256 newStableRate, uint256 newVariableRate) = IReserveInterestRateStrategy(
                                reserve
                                    .interestRateStrategyAddress
                            )
                                .calculateInterestRates(
                                _reserve,
                                getReserveAvailableLiquidity(_reserve).add(_liquidityAdded).sub(_liquidityTaken),
                                reserve.totalBorrowsStable,
                                reserve.totalBorrowsVariable,
                                reserve.currentAverageStableBorrowRate
                            );
                    
                            reserve.currentLiquidityRate = newLiquidityRate;
                            reserve.currentStableBorrowRate = newStableRate;
                            reserve.currentVariableBorrowRate = newVariableRate;
                    
                            //solium-disable-next-line
                            reserve.lastUpdateTimestamp = uint40(block.timestamp);
                    
                            emit ReserveUpdated(
                                _reserve,
                                newLiquidityRate,
                                newStableRate,
                                newVariableRate,
                                reserve.lastLiquidityCumulativeIndex,
                                reserve.lastVariableBorrowCumulativeIndex
                            );
                        }
                    
                        /**
                        * @dev transfers to the protocol fees of a flashloan to the fees collection address
                        * @param _token the address of the token being transferred
                        * @param _amount the amount being transferred
                        **/
                    
                        function transferFlashLoanProtocolFeeInternal(address _token, uint256 _amount) internal {
                            address payable receiver = address(uint160(addressesProvider.getTokenDistributor()));
                    
                            if (_token != EthAddressLib.ethAddress()) {
                                ERC20(_token).safeTransfer(receiver, _amount);
                            } else {
                                receiver.transfer(_amount);
                            }
                        }
                    
                        /**
                        * @dev updates the internal configuration of the core
                        **/
                        function refreshConfigInternal() internal {
                            lendingPoolAddress = addressesProvider.getLendingPool();
                        }
                    
                        /**
                        * @dev adds a reserve to the array of the reserves address
                        **/
                        function addReserveToListInternal(address _reserve) internal {
                            bool reserveAlreadyAdded = false;
                            for (uint256 i = 0; i < reservesList.length; i++)
                                if (reservesList[i] == _reserve) {
                                    reserveAlreadyAdded = true;
                                }
                            if (!reserveAlreadyAdded) reservesList.push(_reserve);
                        }
                    
                    }
                    
                    /**
                    * @title LendingPool contract
                    * @notice Implements the actions of the LendingPool, and exposes accessory methods to fetch the users and reserve data
                    * @author Aave
                     **/
                    
                    contract LendingPool is ReentrancyGuard, VersionedInitializable {
                        using SafeMath for uint256;
                        using WadRayMath for uint256;
                        using Address for address;
                    
                        LendingPoolAddressesProvider public addressesProvider;
                        LendingPoolCore public core;
                        LendingPoolDataProvider public dataProvider;
                        LendingPoolParametersProvider public parametersProvider;
                        IFeeProvider feeProvider;
                    
                        /**
                        * @dev emitted on deposit
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        * @param _amount the amount to be deposited
                        * @param _referral the referral number of the action
                        * @param _timestamp the timestamp of the action
                        **/
                        event Deposit(
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _amount,
                            uint16 indexed _referral,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted during a redeem action.
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        * @param _amount the amount to be deposited
                        * @param _timestamp the timestamp of the action
                        **/
                        event RedeemUnderlying(
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _amount,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted on borrow
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        * @param _amount the amount to be deposited
                        * @param _borrowRateMode the rate mode, can be either 1-stable or 2-variable
                        * @param _borrowRate the rate at which the user has borrowed
                        * @param _originationFee the origination fee to be paid by the user
                        * @param _borrowBalanceIncrease the balance increase since the last borrow, 0 if it's the first time borrowing
                        * @param _referral the referral number of the action
                        * @param _timestamp the timestamp of the action
                        **/
                        event Borrow(
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _amount,
                            uint256 _borrowRateMode,
                            uint256 _borrowRate,
                            uint256 _originationFee,
                            uint256 _borrowBalanceIncrease,
                            uint16 indexed _referral,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted on repay
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user for which the repay has been executed
                        * @param _repayer the address of the user that has performed the repay action
                        * @param _amountMinusFees the amount repaid minus fees
                        * @param _fees the fees repaid
                        * @param _borrowBalanceIncrease the balance increase since the last action
                        * @param _timestamp the timestamp of the action
                        **/
                        event Repay(
                            address indexed _reserve,
                            address indexed _user,
                            address indexed _repayer,
                            uint256 _amountMinusFees,
                            uint256 _fees,
                            uint256 _borrowBalanceIncrease,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted when a user performs a rate swap
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user executing the swap
                        * @param _newRateMode the new interest rate mode
                        * @param _newRate the new borrow rate
                        * @param _borrowBalanceIncrease the balance increase since the last action
                        * @param _timestamp the timestamp of the action
                        **/
                        event Swap(
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _newRateMode,
                            uint256 _newRate,
                            uint256 _borrowBalanceIncrease,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted when a user enables a reserve as collateral
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        **/
                        event ReserveUsedAsCollateralEnabled(address indexed _reserve, address indexed _user);
                    
                        /**
                        * @dev emitted when a user disables a reserve as collateral
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user
                        **/
                        event ReserveUsedAsCollateralDisabled(address indexed _reserve, address indexed _user);
                    
                        /**
                        * @dev emitted when the stable rate of a user gets rebalanced
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user for which the rebalance has been executed
                        * @param _newStableRate the new stable borrow rate after the rebalance
                        * @param _borrowBalanceIncrease the balance increase since the last action
                        * @param _timestamp the timestamp of the action
                        **/
                        event RebalanceStableBorrowRate(
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _newStableRate,
                            uint256 _borrowBalanceIncrease,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted when a flashloan is executed
                        * @param _target the address of the flashLoanReceiver
                        * @param _reserve the address of the reserve
                        * @param _amount the amount requested
                        * @param _totalFee the total fee on the amount
                        * @param _protocolFee the part of the fee for the protocol
                        * @param _timestamp the timestamp of the action
                        **/
                        event FlashLoan(
                            address indexed _target,
                            address indexed _reserve,
                            uint256 _amount,
                            uint256 _totalFee,
                            uint256 _protocolFee,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev these events are not emitted directly by the LendingPool
                        * but they are declared here as the LendingPoolLiquidationManager
                        * is executed using a delegateCall().
                        * This allows to have the events in the generated ABI for LendingPool.
                        **/
                    
                        /**
                        * @dev emitted when a borrow fee is liquidated
                        * @param _collateral the address of the collateral being liquidated
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user being liquidated
                        * @param _feeLiquidated the total fee liquidated
                        * @param _liquidatedCollateralForFee the amount of collateral received by the protocol in exchange for the fee
                        * @param _timestamp the timestamp of the action
                        **/
                        event OriginationFeeLiquidated(
                            address indexed _collateral,
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _feeLiquidated,
                            uint256 _liquidatedCollateralForFee,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted when a borrower is liquidated
                        * @param _collateral the address of the collateral being liquidated
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user being liquidated
                        * @param _purchaseAmount the total amount liquidated
                        * @param _liquidatedCollateralAmount the amount of collateral being liquidated
                        * @param _accruedBorrowInterest the amount of interest accrued by the borrower since the last action
                        * @param _liquidator the address of the liquidator
                        * @param _receiveAToken true if the liquidator wants to receive aTokens, false otherwise
                        * @param _timestamp the timestamp of the action
                        **/
                        event LiquidationCall(
                            address indexed _collateral,
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _purchaseAmount,
                            uint256 _liquidatedCollateralAmount,
                            uint256 _accruedBorrowInterest,
                            address _liquidator,
                            bool _receiveAToken,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev functions affected by this modifier can only be invoked by the
                        * aToken.sol contract
                        * @param _reserve the address of the reserve
                        **/
                        modifier onlyOverlyingAToken(address _reserve) {
                            require(
                                msg.sender == core.getReserveATokenAddress(_reserve),
                                "The caller of this function can only be the aToken contract of this reserve"
                            );
                            _;
                        }
                    
                        /**
                        * @dev functions affected by this modifier can only be invoked if the reserve is active
                        * @param _reserve the address of the reserve
                        **/
                        modifier onlyActiveReserve(address _reserve) {
                            requireReserveActiveInternal(_reserve);
                            _;
                        }
                    
                        /**
                        * @dev functions affected by this modifier can only be invoked if the reserve is not freezed.
                        * A freezed reserve only allows redeems, repays, rebalances and liquidations.
                        * @param _reserve the address of the reserve
                        **/
                        modifier onlyUnfreezedReserve(address _reserve) {
                            requireReserveNotFreezedInternal(_reserve);
                            _;
                        }
                    
                        /**
                        * @dev functions affected by this modifier can only be invoked if the provided _amount input parameter
                        * is not zero.
                        * @param _amount the amount provided
                        **/
                        modifier onlyAmountGreaterThanZero(uint256 _amount) {
                            requireAmountGreaterThanZeroInternal(_amount);
                            _;
                        }
                    
                        uint256 public constant UINT_MAX_VALUE = uint256(-1);
                    
                        uint256 public constant LENDINGPOOL_REVISION = 0x2;
                    
                        function getRevision() internal pure returns (uint256) {
                            return LENDINGPOOL_REVISION;
                        }
                    
                        /**
                        * @dev this function is invoked by the proxy contract when the LendingPool contract is added to the
                        * AddressesProvider.
                        * @param _addressesProvider the address of the LendingPoolAddressesProvider registry
                        **/
                        function initialize(LendingPoolAddressesProvider _addressesProvider) public initializer {
                            addressesProvider = _addressesProvider;
                            core = LendingPoolCore(addressesProvider.getLendingPoolCore());
                            dataProvider = LendingPoolDataProvider(addressesProvider.getLendingPoolDataProvider());
                            parametersProvider = LendingPoolParametersProvider(
                                addressesProvider.getLendingPoolParametersProvider()
                            );
                            feeProvider = IFeeProvider(addressesProvider.getFeeProvider());
                        }
                    
                        /**
                        * @dev deposits The underlying asset into the reserve. A corresponding amount of the overlying asset (aTokens)
                        * is minted.
                        * @param _reserve the address of the reserve
                        * @param _amount the amount to be deposited
                        * @param _referralCode integrators are assigned a referral code and can potentially receive rewards.
                        **/
                        function deposit(address _reserve, uint256 _amount, uint16 _referralCode)
                            external
                            payable
                            nonReentrant
                            onlyActiveReserve(_reserve)
                            onlyUnfreezedReserve(_reserve)
                            onlyAmountGreaterThanZero(_amount)
                        {
                            AToken aToken = AToken(core.getReserveATokenAddress(_reserve));
                    
                            bool isFirstDeposit = aToken.balanceOf(msg.sender) == 0;
                    
                            core.updateStateOnDeposit(_reserve, msg.sender, _amount, isFirstDeposit);
                    
                            //minting AToken to user 1:1 with the specific exchange rate
                            aToken.mintOnDeposit(msg.sender, _amount);
                    
                            //transfer to the core contract
                            core.transferToReserve.value(msg.value)(_reserve, msg.sender, _amount);
                    
                            //solium-disable-next-line
                            emit Deposit(_reserve, msg.sender, _amount, _referralCode, block.timestamp);
                    
                        }
                    
                        /**
                        * @dev Redeems the underlying amount of assets requested by _user.
                        * This function is executed by the overlying aToken contract in response to a redeem action.
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user performing the action
                        * @param _amount the underlying amount to be redeemed
                        **/
                        function redeemUnderlying(
                            address _reserve,
                            address payable _user,
                            uint256 _amount,
                            uint256 _aTokenBalanceAfterRedeem
                        )
                            external
                            nonReentrant
                            onlyOverlyingAToken(_reserve)
                            onlyActiveReserve(_reserve)
                            onlyAmountGreaterThanZero(_amount)
                        {
                            uint256 currentAvailableLiquidity = core.getReserveAvailableLiquidity(_reserve);
                            require(
                                currentAvailableLiquidity >= _amount,
                                "There is not enough liquidity available to redeem"
                            );
                    
                            core.updateStateOnRedeem(_reserve, _user, _amount, _aTokenBalanceAfterRedeem == 0);
                    
                            core.transferToUser(_reserve, _user, _amount);
                    
                            //solium-disable-next-line
                            emit RedeemUnderlying(_reserve, _user, _amount, block.timestamp);
                    
                        }
                    
                        /**
                        * @dev data structures for local computations in the borrow() method.
                        */
                    
                        struct BorrowLocalVars {
                            uint256 principalBorrowBalance;
                            uint256 currentLtv;
                            uint256 currentLiquidationThreshold;
                            uint256 borrowFee;
                            uint256 requestedBorrowAmountETH;
                            uint256 amountOfCollateralNeededETH;
                            uint256 userCollateralBalanceETH;
                            uint256 userBorrowBalanceETH;
                            uint256 userTotalFeesETH;
                            uint256 borrowBalanceIncrease;
                            uint256 currentReserveStableRate;
                            uint256 availableLiquidity;
                            uint256 reserveDecimals;
                            uint256 finalUserBorrowRate;
                            CoreLibrary.InterestRateMode rateMode;
                            bool healthFactorBelowThreshold;
                        }
                    
                        /**
                        * @dev Allows users to borrow a specific amount of the reserve currency, provided that the borrower
                        * already deposited enough collateral.
                        * @param _reserve the address of the reserve
                        * @param _amount the amount to be borrowed
                        * @param _interestRateMode the interest rate mode at which the user wants to borrow. Can be 0 (STABLE) or 1 (VARIABLE)
                        **/
                        function borrow(
                            address _reserve,
                            uint256 _amount,
                            uint256 _interestRateMode,
                            uint16 _referralCode
                        )
                            external
                            nonReentrant
                            onlyActiveReserve(_reserve)
                            onlyUnfreezedReserve(_reserve)
                            onlyAmountGreaterThanZero(_amount)
                        {
                            // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
                            BorrowLocalVars memory vars;
                    
                            //check that the reserve is enabled for borrowing
                            require(core.isReserveBorrowingEnabled(_reserve), "Reserve is not enabled for borrowing");
                            //validate interest rate mode
                            require(
                                uint256(CoreLibrary.InterestRateMode.VARIABLE) == _interestRateMode ||
                                    uint256(CoreLibrary.InterestRateMode.STABLE) == _interestRateMode,
                                "Invalid interest rate mode selected"
                            );
                    
                            //cast the rateMode to coreLibrary.interestRateMode
                            vars.rateMode = CoreLibrary.InterestRateMode(_interestRateMode);
                    
                            //check that the amount is available in the reserve
                            vars.availableLiquidity = core.getReserveAvailableLiquidity(_reserve);
                    
                            require(
                                vars.availableLiquidity >= _amount,
                                "There is not enough liquidity available in the reserve"
                            );
                    
                            (
                                ,
                                vars.userCollateralBalanceETH,
                                vars.userBorrowBalanceETH,
                                vars.userTotalFeesETH,
                                vars.currentLtv,
                                vars.currentLiquidationThreshold,
                                ,
                                vars.healthFactorBelowThreshold
                            ) = dataProvider.calculateUserGlobalData(msg.sender);
                    
                            require(vars.userCollateralBalanceETH > 0, "The collateral balance is 0");
                    
                            require(
                                !vars.healthFactorBelowThreshold,
                                "The borrower can already be liquidated so he cannot borrow more"
                            );
                    
                            //calculating fees
                            vars.borrowFee = feeProvider.calculateLoanOriginationFee(msg.sender, _amount);
                    
                            require(vars.borrowFee > 0, "The amount to borrow is too small");
                    
                            vars.amountOfCollateralNeededETH = dataProvider.calculateCollateralNeededInETH(
                                _reserve,
                                _amount,
                                vars.borrowFee,
                                vars.userBorrowBalanceETH,
                                vars.userTotalFeesETH,
                                vars.currentLtv
                            );
                    
                            require(
                                vars.amountOfCollateralNeededETH <= vars.userCollateralBalanceETH,
                                "There is not enough collateral to cover a new borrow"
                            );
                    
                            /**
                            * Following conditions need to be met if the user is borrowing at a stable rate:
                            * 1. Reserve must be enabled for stable rate borrowing
                            * 2. Users cannot borrow from the reserve if their collateral is (mostly) the same currency
                            *    they are borrowing, to prevent abuses.
                            * 3. Users will be able to borrow only a relatively small, configurable amount of the total
                            *    liquidity
                            **/
                    
                            if (vars.rateMode == CoreLibrary.InterestRateMode.STABLE) {
                                //check if the borrow mode is stable and if stable rate borrowing is enabled on this reserve
                                require(
                                    core.isUserAllowedToBorrowAtStable(_reserve, msg.sender, _amount),
                                    "User cannot borrow the selected amount with a stable rate"
                                );
                    
                                //calculate the max available loan size in stable rate mode as a percentage of the
                                //available liquidity
                                uint256 maxLoanPercent = parametersProvider.getMaxStableRateBorrowSizePercent();
                                uint256 maxLoanSizeStable = vars.availableLiquidity.mul(maxLoanPercent).div(100);
                    
                                require(
                                    _amount <= maxLoanSizeStable,
                                    "User is trying to borrow too much liquidity at a stable rate"
                                );
                            }
                    
                            //all conditions passed - borrow is accepted
                            (vars.finalUserBorrowRate, vars.borrowBalanceIncrease) = core.updateStateOnBorrow(
                                _reserve,
                                msg.sender,
                                _amount,
                                vars.borrowFee,
                                vars.rateMode
                            );
                    
                            //if we reached this point, we can transfer
                            core.transferToUser(_reserve, msg.sender, _amount);
                    
                            emit Borrow(
                                _reserve,
                                msg.sender,
                                _amount,
                                _interestRateMode,
                                vars.finalUserBorrowRate,
                                vars.borrowFee,
                                vars.borrowBalanceIncrease,
                                _referralCode,
                                //solium-disable-next-line
                                block.timestamp
                            );
                        }
                    
                        /**
                        * @notice repays a borrow on the specific reserve, for the specified amount (or for the whole amount, if uint256(-1) is specified).
                        * @dev the target user is defined by _onBehalfOf. If there is no repayment on behalf of another account,
                        * _onBehalfOf must be equal to msg.sender.
                        * @param _reserve the address of the reserve on which the user borrowed
                        * @param _amount the amount to repay, or uint256(-1) if the user wants to repay everything
                        * @param _onBehalfOf the address for which msg.sender is repaying.
                        **/
                    
                        struct RepayLocalVars {
                            uint256 principalBorrowBalance;
                            uint256 compoundedBorrowBalance;
                            uint256 borrowBalanceIncrease;
                            bool isETH;
                            uint256 paybackAmount;
                            uint256 paybackAmountMinusFees;
                            uint256 currentStableRate;
                            uint256 originationFee;
                        }
                    
                        function repay(address _reserve, uint256 _amount, address payable _onBehalfOf)
                            external
                            payable
                            nonReentrant
                            onlyActiveReserve(_reserve)
                            onlyAmountGreaterThanZero(_amount)
                        {
                            // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
                            RepayLocalVars memory vars;
                    
                            (
                                vars.principalBorrowBalance,
                                vars.compoundedBorrowBalance,
                                vars.borrowBalanceIncrease
                            ) = core.getUserBorrowBalances(_reserve, _onBehalfOf);
                    
                            vars.originationFee = core.getUserOriginationFee(_reserve, _onBehalfOf);
                            vars.isETH = EthAddressLib.ethAddress() == _reserve;
                    
                            require(vars.compoundedBorrowBalance > 0, "The user does not have any borrow pending");
                    
                            require(
                                _amount != UINT_MAX_VALUE || msg.sender == _onBehalfOf,
                                "To repay on behalf of an user an explicit amount to repay is needed."
                            );
                    
                            //default to max amount
                            vars.paybackAmount = vars.compoundedBorrowBalance.add(vars.originationFee);
                    
                            if (_amount != UINT_MAX_VALUE && _amount < vars.paybackAmount) {
                                vars.paybackAmount = _amount;
                            }
                    
                            require(
                                !vars.isETH || msg.value >= vars.paybackAmount,
                                "Invalid msg.value sent for the repayment"
                            );
                    
                            //if the amount is smaller than the origination fee, just transfer the amount to the fee destination address
                            if (vars.paybackAmount <= vars.originationFee) {
                                core.updateStateOnRepay(
                                    _reserve,
                                    _onBehalfOf,
                                    0,
                                    vars.paybackAmount,
                                    vars.borrowBalanceIncrease,
                                    false
                                );
                    
                                core.transferToFeeCollectionAddress.value(vars.isETH ? vars.paybackAmount : 0)(
                                    _reserve,
                                    _onBehalfOf,
                                    vars.paybackAmount,
                                    addressesProvider.getTokenDistributor()
                                );
                    
                                emit Repay(
                                    _reserve,
                                    _onBehalfOf,
                                    msg.sender,
                                    0,
                                    vars.paybackAmount,
                                    vars.borrowBalanceIncrease,
                                    //solium-disable-next-line
                                    block.timestamp
                                );
                                return;
                            }
                    
                            vars.paybackAmountMinusFees = vars.paybackAmount.sub(vars.originationFee);
                    
                            core.updateStateOnRepay(
                                _reserve,
                                _onBehalfOf,
                                vars.paybackAmountMinusFees,
                                vars.originationFee,
                                vars.borrowBalanceIncrease,
                                vars.compoundedBorrowBalance == vars.paybackAmountMinusFees
                            );
                    
                            //if the user didn't repay the origination fee, transfer the fee to the fee collection address
                            if(vars.originationFee > 0) {
                                core.transferToFeeCollectionAddress.value(vars.isETH ? vars.originationFee : 0)(
                                    _reserve,
                                    _onBehalfOf,
                                    vars.originationFee,
                                    addressesProvider.getTokenDistributor()
                                );
                            }
                    
                            //sending the total msg.value if the transfer is ETH.
                            //the transferToReserve() function will take care of sending the
                            //excess ETH back to the caller
                            core.transferToReserve.value(vars.isETH ? msg.value.sub(vars.originationFee) : 0)(
                                _reserve,
                                msg.sender,
                                vars.paybackAmountMinusFees
                            );
                    
                            emit Repay(
                                _reserve,
                                _onBehalfOf,
                                msg.sender,
                                vars.paybackAmountMinusFees,
                                vars.originationFee,
                                vars.borrowBalanceIncrease,
                                //solium-disable-next-line
                                block.timestamp
                            );
                        }
                    
                        /**
                        * @dev borrowers can user this function to swap between stable and variable borrow rate modes.
                        * @param _reserve the address of the reserve on which the user borrowed
                        **/
                        function swapBorrowRateMode(address _reserve)
                            external
                            nonReentrant
                            onlyActiveReserve(_reserve)
                            onlyUnfreezedReserve(_reserve)
                        {
                            (uint256 principalBorrowBalance, uint256 compoundedBorrowBalance, uint256 borrowBalanceIncrease) = core
                                .getUserBorrowBalances(_reserve, msg.sender);
                    
                            require(
                                compoundedBorrowBalance > 0,
                                "User does not have a borrow in progress on this reserve"
                            );
                    
                            CoreLibrary.InterestRateMode currentRateMode = core.getUserCurrentBorrowRateMode(
                                _reserve,
                                msg.sender
                            );
                    
                            if (currentRateMode == CoreLibrary.InterestRateMode.VARIABLE) {
                                /**
                                * user wants to swap to stable, before swapping we need to ensure that
                                * 1. stable borrow rate is enabled on the reserve
                                * 2. user is not trying to abuse the reserve by depositing
                                * more collateral than he is borrowing, artificially lowering
                                * the interest rate, borrowing at variable, and switching to stable
                                **/
                                require(
                                    core.isUserAllowedToBorrowAtStable(_reserve, msg.sender, compoundedBorrowBalance),
                                    "User cannot borrow the selected amount at stable"
                                );
                            }
                    
                            (CoreLibrary.InterestRateMode newRateMode, uint256 newBorrowRate) = core
                                .updateStateOnSwapRate(
                                _reserve,
                                msg.sender,
                                principalBorrowBalance,
                                compoundedBorrowBalance,
                                borrowBalanceIncrease,
                                currentRateMode
                            );
                    
                            emit Swap(
                                _reserve,
                                msg.sender,
                                uint256(newRateMode),
                                newBorrowRate,
                                borrowBalanceIncrease,
                                //solium-disable-next-line
                                block.timestamp
                            );
                        }
                    
                        /**
                        * @dev rebalances the stable interest rate of a user if current liquidity rate > user stable rate.
                        * this is regulated by Aave to ensure that the protocol is not abused, and the user is paying a fair
                        * rate. Anyone can call this function though.
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user to be rebalanced
                        **/
                        function rebalanceStableBorrowRate(address _reserve, address _user)
                            external
                            nonReentrant
                            onlyActiveReserve(_reserve)
                        {
                            (, uint256 compoundedBalance, uint256 borrowBalanceIncrease) = core.getUserBorrowBalances(
                                _reserve,
                                _user
                            );
                    
                            //step 1: user must be borrowing on _reserve at a stable rate
                            require(compoundedBalance > 0, "User does not have any borrow for this reserve");
                    
                            require(
                                core.getUserCurrentBorrowRateMode(_reserve, _user) ==
                                    CoreLibrary.InterestRateMode.STABLE,
                                "The user borrow is variable and cannot be rebalanced"
                            );
                    
                            uint256 userCurrentStableRate = core.getUserCurrentStableBorrowRate(_reserve, _user);
                            uint256 liquidityRate = core.getReserveCurrentLiquidityRate(_reserve);
                            uint256 reserveCurrentStableRate = core.getReserveCurrentStableBorrowRate(_reserve);
                            uint256 rebalanceDownRateThreshold = reserveCurrentStableRate.rayMul(
                                WadRayMath.ray().add(parametersProvider.getRebalanceDownRateDelta())
                            );
                    
                            //step 2: we have two possible situations to rebalance:
                    
                            //1. user stable borrow rate is below the current liquidity rate. The loan needs to be rebalanced,
                            //as this situation can be abused (user putting back the borrowed liquidity in the same reserve to earn on it)
                            //2. user stable rate is above the market avg borrow rate of a certain delta, and utilization rate is low.
                            //In this case, the user is paying an interest that is too high, and needs to be rescaled down.
                            if (
                                userCurrentStableRate < liquidityRate ||
                                userCurrentStableRate > rebalanceDownRateThreshold
                            ) {
                                uint256 newStableRate = core.updateStateOnRebalance(
                                    _reserve,
                                    _user,
                                    borrowBalanceIncrease
                                );
                    
                                emit RebalanceStableBorrowRate(
                                    _reserve,
                                    _user,
                                    newStableRate,
                                    borrowBalanceIncrease,
                                    //solium-disable-next-line
                                    block.timestamp
                                );
                    
                                return;
                    
                            }
                    
                            revert("Interest rate rebalance conditions were not met");
                        }
                    
                        /**
                        * @dev allows depositors to enable or disable a specific deposit as collateral.
                        * @param _reserve the address of the reserve
                        * @param _useAsCollateral true if the user wants to user the deposit as collateral, false otherwise.
                        **/
                        function setUserUseReserveAsCollateral(address _reserve, bool _useAsCollateral)
                            external
                            nonReentrant
                            onlyActiveReserve(_reserve)
                            onlyUnfreezedReserve(_reserve)
                        {
                            uint256 underlyingBalance = core.getUserUnderlyingAssetBalance(_reserve, msg.sender);
                    
                            require(underlyingBalance > 0, "User does not have any liquidity deposited");
                    
                            require(
                                dataProvider.balanceDecreaseAllowed(_reserve, msg.sender, underlyingBalance),
                                "User deposit is already being used as collateral"
                            );
                    
                            core.setUserUseReserveAsCollateral(_reserve, msg.sender, _useAsCollateral);
                    
                            if (_useAsCollateral) {
                                emit ReserveUsedAsCollateralEnabled(_reserve, msg.sender);
                            } else {
                                emit ReserveUsedAsCollateralDisabled(_reserve, msg.sender);
                            }
                        }
                    
                        /**
                        * @dev users can invoke this function to liquidate an undercollateralized position.
                        * @param _reserve the address of the collateral to liquidated
                        * @param _reserve the address of the principal reserve
                        * @param _user the address of the borrower
                        * @param _purchaseAmount the amount of principal that the liquidator wants to repay
                        * @param _receiveAToken true if the liquidators wants to receive the aTokens, false if
                        * he wants to receive the underlying asset directly
                        **/
                        function liquidationCall(
                            address _collateral,
                            address _reserve,
                            address _user,
                            uint256 _purchaseAmount,
                            bool _receiveAToken
                        ) external payable nonReentrant onlyActiveReserve(_reserve) onlyActiveReserve(_collateral) {
                            address liquidationManager = addressesProvider.getLendingPoolLiquidationManager();
                    
                            //solium-disable-next-line
                            (bool success, bytes memory result) = liquidationManager.delegatecall(
                                abi.encodeWithSignature(
                                    "liquidationCall(address,address,address,uint256,bool)",
                                    _collateral,
                                    _reserve,
                                    _user,
                                    _purchaseAmount,
                                    _receiveAToken
                                )
                            );
                            require(success, "Liquidation call failed");
                    
                            (uint256 returnCode, string memory returnMessage) = abi.decode(result, (uint256, string));
                    
                            if (returnCode != 0) {
                                //error found
                                revert(string(abi.encodePacked("Liquidation failed: ", returnMessage)));
                            }
                        }
                    
                        /**
                        * @dev allows smartcontracts to access the liquidity of the pool within one transaction,
                        * as long as the amount taken plus a fee is returned. NOTE There are security concerns for developers of flashloan receiver contracts
                        * that must be kept into consideration. For further details please visit https://developers.aave.com
                        * @param _receiver The address of the contract receiving the funds. The receiver should implement the IFlashLoanReceiver interface.
                        * @param _reserve the address of the principal reserve
                        * @param _amount the amount requested for this flashloan
                        **/
                        function flashLoan(address _receiver, address _reserve, uint256 _amount, bytes memory _params)
                            public
                            nonReentrant
                            onlyActiveReserve(_reserve)
                            onlyAmountGreaterThanZero(_amount)
                        {
                            //check that the reserve has enough available liquidity
                            //we avoid using the getAvailableLiquidity() function in LendingPoolCore to save gas
                            uint256 availableLiquidityBefore = _reserve == EthAddressLib.ethAddress()
                                ? address(core).balance
                                : IERC20(_reserve).balanceOf(address(core));
                    
                            require(
                                availableLiquidityBefore >= _amount,
                                "There is not enough liquidity available to borrow"
                            );
                    
                            (uint256 totalFeeBips, uint256 protocolFeeBips) = parametersProvider
                                .getFlashLoanFeesInBips();
                            //calculate amount fee
                            uint256 amountFee = _amount.mul(totalFeeBips).div(10000);
                    
                            //protocol fee is the part of the amountFee reserved for the protocol - the rest goes to depositors
                            uint256 protocolFee = amountFee.mul(protocolFeeBips).div(10000);
                            require(
                                amountFee > 0 && protocolFee > 0,
                                "The requested amount is too small for a flashLoan."
                            );
                    
                            //get the FlashLoanReceiver instance
                            IFlashLoanReceiver receiver = IFlashLoanReceiver(_receiver);
                    
                            address payable userPayable = address(uint160(_receiver));
                    
                            //transfer funds to the receiver
                            core.transferToUser(_reserve, userPayable, _amount);
                    
                            //execute action of the receiver
                            receiver.executeOperation(_reserve, _amount, amountFee, _params);
                    
                            //check that the actual balance of the core contract includes the returned amount
                            uint256 availableLiquidityAfter = _reserve == EthAddressLib.ethAddress()
                                ? address(core).balance
                                : IERC20(_reserve).balanceOf(address(core));
                    
                            require(
                                availableLiquidityAfter == availableLiquidityBefore.add(amountFee),
                                "The actual balance of the protocol is inconsistent"
                            );
                    
                            core.updateStateOnFlashLoan(
                                _reserve,
                                availableLiquidityBefore,
                                amountFee.sub(protocolFee),
                                protocolFee
                            );
                    
                            //solium-disable-next-line
                            emit FlashLoan(_receiver, _reserve, _amount, amountFee, protocolFee, block.timestamp);
                        }
                    
                        /**
                        * @dev accessory functions to fetch data from the core contract
                        **/
                    
                        function getReserveConfigurationData(address _reserve)
                            external
                            view
                            returns (
                                uint256 ltv,
                                uint256 liquidationThreshold,
                                uint256 liquidationBonus,
                                address interestRateStrategyAddress,
                                bool usageAsCollateralEnabled,
                                bool borrowingEnabled,
                                bool stableBorrowRateEnabled,
                                bool isActive
                            )
                        {
                            return dataProvider.getReserveConfigurationData(_reserve);
                        }
                    
                        function getReserveData(address _reserve)
                            external
                            view
                            returns (
                                uint256 totalLiquidity,
                                uint256 availableLiquidity,
                                uint256 totalBorrowsStable,
                                uint256 totalBorrowsVariable,
                                uint256 liquidityRate,
                                uint256 variableBorrowRate,
                                uint256 stableBorrowRate,
                                uint256 averageStableBorrowRate,
                                uint256 utilizationRate,
                                uint256 liquidityIndex,
                                uint256 variableBorrowIndex,
                                address aTokenAddress,
                                uint40 lastUpdateTimestamp
                            )
                        {
                            return dataProvider.getReserveData(_reserve);
                        }
                    
                        function getUserAccountData(address _user)
                            external
                            view
                            returns (
                                uint256 totalLiquidityETH,
                                uint256 totalCollateralETH,
                                uint256 totalBorrowsETH,
                                uint256 totalFeesETH,
                                uint256 availableBorrowsETH,
                                uint256 currentLiquidationThreshold,
                                uint256 ltv,
                                uint256 healthFactor
                            )
                        {
                            return dataProvider.getUserAccountData(_user);
                        }
                    
                        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
                            )
                        {
                            return dataProvider.getUserReserveData(_reserve, _user);
                        }
                    
                        function getReserves() external view returns (address[] memory) {
                            return core.getReserves();
                        }
                    
                        /**
                        * @dev internal function to save on code size for the onlyActiveReserve modifier
                        **/
                        function requireReserveActiveInternal(address _reserve) internal view {
                            require(core.getReserveIsActive(_reserve), "Action requires an active reserve");
                        }
                    
                        /**
                        * @notice internal function to save on code size for the onlyUnfreezedReserve modifier
                        **/
                        function requireReserveNotFreezedInternal(address _reserve) internal view {
                            require(!core.getReserveIsFreezed(_reserve), "Action requires an unfreezed reserve");
                        }
                    
                        /**
                        * @notice internal function to save on code size for the onlyAmountGreaterThanZero modifier
                        **/
                        function requireAmountGreaterThanZeroInternal(uint256 _amount) internal pure {
                            require(_amount > 0, "Amount must be greater than 0");
                        }
                    }
                    
                    /**
                    * @title LendingPoolLiquidationManager contract
                    * @author Aave
                    * @notice Implements the liquidation function.
                    **/
                    contract LendingPoolLiquidationManager is ReentrancyGuard, VersionedInitializable {
                        using SafeMath for uint256;
                        using WadRayMath for uint256;
                        using Address for address;
                    
                        LendingPoolAddressesProvider public addressesProvider;
                        LendingPoolCore core;
                        LendingPoolDataProvider dataProvider;
                        LendingPoolParametersProvider parametersProvider;
                        IFeeProvider feeProvider;
                        address ethereumAddress;
                    
                        uint256 constant LIQUIDATION_CLOSE_FACTOR_PERCENT = 50;
                    
                        /**
                        * @dev emitted when a borrow fee is liquidated
                        * @param _collateral the address of the collateral being liquidated
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user being liquidated
                        * @param _feeLiquidated the total fee liquidated
                        * @param _liquidatedCollateralForFee the amount of collateral received by the protocol in exchange for the fee
                        * @param _timestamp the timestamp of the action
                        **/
                        event OriginationFeeLiquidated(
                            address indexed _collateral,
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _feeLiquidated,
                            uint256 _liquidatedCollateralForFee,
                            uint256 _timestamp
                        );
                    
                        /**
                        * @dev emitted when a borrower is liquidated
                        * @param _collateral the address of the collateral being liquidated
                        * @param _reserve the address of the reserve
                        * @param _user the address of the user being liquidated
                        * @param _purchaseAmount the total amount liquidated
                        * @param _liquidatedCollateralAmount the amount of collateral being liquidated
                        * @param _accruedBorrowInterest the amount of interest accrued by the borrower since the last action
                        * @param _liquidator the address of the liquidator
                        * @param _receiveAToken true if the liquidator wants to receive aTokens, false otherwise
                        * @param _timestamp the timestamp of the action
                        **/
                        event LiquidationCall(
                            address indexed _collateral,
                            address indexed _reserve,
                            address indexed _user,
                            uint256 _purchaseAmount,
                            uint256 _liquidatedCollateralAmount,
                            uint256 _accruedBorrowInterest,
                            address _liquidator,
                            bool _receiveAToken,
                            uint256 _timestamp
                        );
                    
                        enum LiquidationErrors {
                            NO_ERROR,
                            NO_COLLATERAL_AVAILABLE,
                            COLLATERAL_CANNOT_BE_LIQUIDATED,
                            CURRRENCY_NOT_BORROWED,
                            HEALTH_FACTOR_ABOVE_THRESHOLD,
                            NOT_ENOUGH_LIQUIDITY
                        }
                    
                        struct LiquidationCallLocalVars {
                            uint256 userCollateralBalance;
                            uint256 userCompoundedBorrowBalance;
                            uint256 borrowBalanceIncrease;
                            uint256 maxPrincipalAmountToLiquidate;
                            uint256 actualAmountToLiquidate;
                            uint256 liquidationRatio;
                            uint256 collateralPrice;
                            uint256 principalCurrencyPrice;
                            uint256 maxAmountCollateralToLiquidate;
                            uint256 originationFee;
                            uint256 feeLiquidated;
                            uint256 liquidatedCollateralForFee;
                            CoreLibrary.InterestRateMode borrowRateMode;
                            uint256 userStableRate;
                            bool isCollateralEnabled;
                            bool healthFactorBelowThreshold;
                        }
                    
                        /**
                        * @dev as the contract extends the VersionedInitializable contract to match the state
                        * of the LendingPool contract, the getRevision() function is needed.
                        */
                        function getRevision() internal pure returns (uint256) {
                            return 0;
                        }
                    
                        /**
                        * @dev users can invoke this function to liquidate an undercollateralized position.
                        * @param _reserve the address of the collateral to liquidated
                        * @param _reserve the address of the principal reserve
                        * @param _user the address of the borrower
                        * @param _purchaseAmount the amount of principal that the liquidator wants to repay
                        * @param _receiveAToken true if the liquidators wants to receive the aTokens, false if
                        * he wants to receive the underlying asset directly
                        **/
                        function liquidationCall(
                            address _collateral,
                            address _reserve,
                            address _user,
                            uint256 _purchaseAmount,
                            bool _receiveAToken
                        ) external payable returns (uint256, string memory) {
                            // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
                            LiquidationCallLocalVars memory vars;
                    
                            (, , , , , , , vars.healthFactorBelowThreshold) = dataProvider.calculateUserGlobalData(
                                _user
                            );
                    
                            if (!vars.healthFactorBelowThreshold) {
                                return (
                                    uint256(LiquidationErrors.HEALTH_FACTOR_ABOVE_THRESHOLD),
                                    "Health factor is not below the threshold"
                                );
                            }
                    
                            vars.userCollateralBalance = core.getUserUnderlyingAssetBalance(_collateral, _user);
                    
                            //if _user hasn't deposited this specific collateral, nothing can be liquidated
                            if (vars.userCollateralBalance == 0) {
                                return (
                                    uint256(LiquidationErrors.NO_COLLATERAL_AVAILABLE),
                                    "Invalid collateral to liquidate"
                                );
                            }
                    
                            vars.isCollateralEnabled =
                                core.isReserveUsageAsCollateralEnabled(_collateral) &&
                                core.isUserUseReserveAsCollateralEnabled(_collateral, _user);
                    
                            //if _collateral isn't enabled as collateral by _user, it cannot be liquidated
                            if (!vars.isCollateralEnabled) {
                                return (
                                    uint256(LiquidationErrors.COLLATERAL_CANNOT_BE_LIQUIDATED),
                                    "The collateral chosen cannot be liquidated"
                                );
                            }
                    
                            //if the user hasn't borrowed the specific currency defined by _reserve, it cannot be liquidated
                            (, vars.userCompoundedBorrowBalance, vars.borrowBalanceIncrease) = core
                                .getUserBorrowBalances(_reserve, _user);
                    
                            if (vars.userCompoundedBorrowBalance == 0) {
                                return (
                                    uint256(LiquidationErrors.CURRRENCY_NOT_BORROWED),
                                    "User did not borrow the specified currency"
                                );
                            }
                    
                            //all clear - calculate the max principal amount that can be liquidated
                            vars.maxPrincipalAmountToLiquidate = vars
                                .userCompoundedBorrowBalance
                                .mul(LIQUIDATION_CLOSE_FACTOR_PERCENT)
                                .div(100);
                    
                            vars.actualAmountToLiquidate = _purchaseAmount > vars.maxPrincipalAmountToLiquidate
                                ? vars.maxPrincipalAmountToLiquidate
                                : _purchaseAmount;
                    
                            (uint256 maxCollateralToLiquidate, uint256 principalAmountNeeded) = calculateAvailableCollateralToLiquidate(
                                _collateral,
                                _reserve,
                                vars.actualAmountToLiquidate,
                                vars.userCollateralBalance
                            );
                    
                            vars.originationFee = core.getUserOriginationFee(_reserve, _user);
                    
                            //if there is a fee to liquidate, calculate the maximum amount of fee that can be liquidated
                            if (vars.originationFee > 0) {
                                (
                                    vars.liquidatedCollateralForFee,
                                    vars.feeLiquidated
                                ) = calculateAvailableCollateralToLiquidate(
                                    _collateral,
                                    _reserve,
                                    vars.originationFee,
                                    vars.userCollateralBalance.sub(maxCollateralToLiquidate)
                                );
                            }
                    
                            //if principalAmountNeeded < vars.ActualAmountToLiquidate, there isn't enough
                            //of _collateral to cover the actual amount that is being liquidated, hence we liquidate
                            //a smaller amount
                    
                            if (principalAmountNeeded < vars.actualAmountToLiquidate) {
                                vars.actualAmountToLiquidate = principalAmountNeeded;
                            }
                    
                            //if liquidator reclaims the underlying asset, we make sure there is enough available collateral in the reserve
                            if (!_receiveAToken) {
                                uint256 currentAvailableCollateral = core.getReserveAvailableLiquidity(_collateral);
                                if (currentAvailableCollateral < maxCollateralToLiquidate) {
                                    return (
                                        uint256(LiquidationErrors.NOT_ENOUGH_LIQUIDITY),
                                        "There isn't enough liquidity available to liquidate"
                                    );
                                }
                            }
                    
                            core.updateStateOnLiquidation(
                                _reserve,
                                _collateral,
                                _user,
                                vars.actualAmountToLiquidate,
                                maxCollateralToLiquidate,
                                vars.feeLiquidated,
                                vars.liquidatedCollateralForFee,
                                vars.borrowBalanceIncrease,
                                _receiveAToken
                            );
                    
                            AToken collateralAtoken = AToken(core.getReserveATokenAddress(_collateral));
                    
                            //if liquidator reclaims the aToken, he receives the equivalent atoken amount
                            if (_receiveAToken) {
                                collateralAtoken.transferOnLiquidation(_user, msg.sender, maxCollateralToLiquidate);
                            } else {
                                //otherwise receives the underlying asset
                                //burn the equivalent amount of atoken
                                collateralAtoken.burnOnLiquidation(_user, maxCollateralToLiquidate);
                                core.transferToUser(_collateral, msg.sender, maxCollateralToLiquidate);
                            }
                    
                            //transfers the principal currency to the pool
                            core.transferToReserve.value(msg.value)(_reserve, msg.sender, vars.actualAmountToLiquidate);
                    
                            if (vars.feeLiquidated > 0) {
                                //if there is enough collateral to liquidate the fee, first transfer burn an equivalent amount of
                                //aTokens of the user
                                collateralAtoken.burnOnLiquidation(_user, vars.liquidatedCollateralForFee);
                    
                                //then liquidate the fee by transferring it to the fee collection address
                                core.liquidateFee(
                                    _collateral,
                                    vars.liquidatedCollateralForFee,
                                    addressesProvider.getTokenDistributor()
                                );
                    
                                emit OriginationFeeLiquidated(
                                    _collateral,
                                    _reserve,
                                    _user,
                                    vars.feeLiquidated,
                                    vars.liquidatedCollateralForFee,
                                    //solium-disable-next-line
                                    block.timestamp
                                );
                    
                            }
                            emit LiquidationCall(
                                _collateral,
                                _reserve,
                                _user,
                                vars.actualAmountToLiquidate,
                                maxCollateralToLiquidate,
                                vars.borrowBalanceIncrease,
                                msg.sender,
                                _receiveAToken,
                                //solium-disable-next-line
                                block.timestamp
                            );
                    
                            return (uint256(LiquidationErrors.NO_ERROR), "No errors");
                        }
                    
                        struct AvailableCollateralToLiquidateLocalVars {
                            uint256 userCompoundedBorrowBalance;
                            uint256 liquidationBonus;
                            uint256 collateralPrice;
                            uint256 principalCurrencyPrice;
                            uint256 maxAmountCollateralToLiquidate;
                        }
                    
                        /**
                        * @dev calculates how much of a specific collateral can be liquidated, given
                        * a certain amount of principal currency. This function needs to be called after
                        * all the checks to validate the liquidation have been performed, otherwise it might fail.
                        * @param _collateral the collateral to be liquidated
                        * @param _principal the principal currency to be liquidated
                        * @param _purchaseAmount the amount of principal being liquidated
                        * @param _userCollateralBalance the collatera balance for the specific _collateral asset of the user being liquidated
                        * @return the maximum amount that is possible to liquidated given all the liquidation constraints (user balance, close factor) and
                        * the purchase amount
                        **/
                        function calculateAvailableCollateralToLiquidate(
                            address _collateral,
                            address _principal,
                            uint256 _purchaseAmount,
                            uint256 _userCollateralBalance
                        ) internal view returns (uint256 collateralAmount, uint256 principalAmountNeeded) {
                            collateralAmount = 0;
                            principalAmountNeeded = 0;
                            IPriceOracleGetter oracle = IPriceOracleGetter(addressesProvider.getPriceOracle());
                    
                            // Usage of a memory struct of vars to avoid "Stack too deep" errors due to local variables
                            AvailableCollateralToLiquidateLocalVars memory vars;
                    
                            vars.collateralPrice = oracle.getAssetPrice(_collateral);
                            vars.principalCurrencyPrice = oracle.getAssetPrice(_principal);
                            vars.liquidationBonus = core.getReserveLiquidationBonus(_collateral);
                    
                            //this is the maximum possible amount of the selected collateral that can be liquidated, given the
                            //max amount of principal currency that is available for liquidation.
                            vars.maxAmountCollateralToLiquidate = vars
                                .principalCurrencyPrice
                                .mul(_purchaseAmount)
                                .div(vars.collateralPrice)
                                .mul(vars.liquidationBonus)
                                .div(100);
                    
                            if (vars.maxAmountCollateralToLiquidate > _userCollateralBalance) {
                                collateralAmount = _userCollateralBalance;
                                principalAmountNeeded = vars
                                    .collateralPrice
                                    .mul(collateralAmount)
                                    .div(vars.principalCurrencyPrice)
                                    .mul(100)
                                    .div(vars.liquidationBonus);
                            } else {
                                collateralAmount = vars.maxAmountCollateralToLiquidate;
                                principalAmountNeeded = _purchaseAmount;
                            }
                    
                            return (collateralAmount, principalAmountNeeded);
                        }
                    }

                    File 11 of 11: IdleAave
                    // File: @openzeppelin/contracts/token/ERC20/IERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
                     * the optional functions; to access them see {ERC20Detailed}.
                     */
                    interface IERC20 {
                        /**
                         * @dev Returns the amount of tokens in existence.
                         */
                        function totalSupply() external view returns (uint256);
                    
                        /**
                         * @dev Returns the amount of tokens owned by `account`.
                         */
                        function balanceOf(address account) external view returns (uint256);
                    
                        /**
                         * @dev Moves `amount` tokens from the caller's account to `recipient`.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transfer(address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Returns the remaining number of tokens that `spender` will be
                         * allowed to spend on behalf of `owner` through {transferFrom}. This is
                         * zero by default.
                         *
                         * This value changes when {approve} or {transferFrom} are called.
                         */
                        function allowance(address owner, address spender) external view returns (uint256);
                    
                        /**
                         * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * IMPORTANT: Beware that changing an allowance with this method brings the risk
                         * that someone may use both the old and the new allowance by unfortunate
                         * transaction ordering. One possible solution to mitigate this race
                         * condition is to first reduce the spender's allowance to 0 and set the
                         * desired value afterwards:
                         * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                         *
                         * Emits an {Approval} event.
                         */
                        function approve(address spender, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Moves `amount` tokens from `sender` to `recipient` using the
                         * allowance mechanism. `amount` is then deducted from the caller's
                         * allowance.
                         *
                         * Returns a boolean value indicating whether the operation succeeded.
                         *
                         * Emits a {Transfer} event.
                         */
                        function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
                    
                        /**
                         * @dev Emitted when `value` tokens are moved from one account (`from`) to
                         * another (`to`).
                         *
                         * Note that `value` may be zero.
                         */
                        event Transfer(address indexed from, address indexed to, uint256 value);
                    
                        /**
                         * @dev Emitted when the allowance of a `spender` for an `owner` is set by
                         * a call to {approve}. `value` is the new allowance.
                         */
                        event Approval(address indexed owner, address indexed spender, uint256 value);
                    }
                    
                    // File: @openzeppelin/contracts/token/ERC20/ERC20Detailed.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    /**
                     * @dev Optional functions from the ERC20 standard.
                     */
                    contract ERC20Detailed is IERC20 {
                        string private _name;
                        string private _symbol;
                        uint8 private _decimals;
                    
                        /**
                         * @dev Sets the values for `name`, `symbol`, and `decimals`. All three of
                         * these values are immutable: they can only be set once during
                         * construction.
                         */
                        constructor (string memory name, string memory symbol, uint8 decimals) public {
                            _name = name;
                            _symbol = symbol;
                            _decimals = decimals;
                        }
                    
                        /**
                         * @dev Returns the name of the token.
                         */
                        function name() public view returns (string memory) {
                            return _name;
                        }
                    
                        /**
                         * @dev Returns the symbol of the token, usually a shorter version of the
                         * name.
                         */
                        function symbol() public view returns (string memory) {
                            return _symbol;
                        }
                    
                        /**
                         * @dev Returns the number of decimals used to get its user representation.
                         * For example, if `decimals` equals `2`, a balance of `505` tokens should
                         * be displayed to a user as `5,05` (`505 / 10 ** 2`).
                         *
                         * Tokens usually opt for a value of 18, imitating the relationship between
                         * Ether and Wei.
                         *
                         * NOTE: This information is only used for _display_ purposes: it in
                         * no way affects any of the arithmetic of the contract, including
                         * {IERC20-balanceOf} and {IERC20-transfer}.
                         */
                        function decimals() public view returns (uint8) {
                            return _decimals;
                        }
                    }
                    
                    // File: @openzeppelin/contracts/math/SafeMath.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Wrappers over Solidity's arithmetic operations with added overflow
                     * checks.
                     *
                     * Arithmetic operations in Solidity wrap on overflow. This can easily result
                     * in bugs, because programmers usually assume that an overflow raises an
                     * error, which is the standard behavior in high level programming languages.
                     * `SafeMath` restores this intuition by reverting the transaction when an
                     * operation overflows.
                     *
                     * Using this library instead of the unchecked operations eliminates an entire
                     * class of bugs, so it's recommended to use it always.
                     */
                    library SafeMath {
                        /**
                         * @dev Returns the addition of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `+` operator.
                         *
                         * Requirements:
                         * - Addition cannot overflow.
                         */
                        function add(uint256 a, uint256 b) internal pure returns (uint256) {
                            uint256 c = a + b;
                            require(c >= a, "SafeMath: addition overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         */
                        function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                            return sub(a, b, "SafeMath: subtraction overflow");
                        }
                    
                        /**
                         * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
                         * overflow (when the result is negative).
                         *
                         * Counterpart to Solidity's `-` operator.
                         *
                         * Requirements:
                         * - Subtraction cannot overflow.
                         *
                         * _Available since v2.4.0._
                         */
                        function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b <= a, errorMessage);
                            uint256 c = a - b;
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the multiplication of two unsigned integers, reverting on
                         * overflow.
                         *
                         * Counterpart to Solidity's `*` operator.
                         *
                         * Requirements:
                         * - Multiplication cannot overflow.
                         */
                        function mul(uint256 a, uint256 b) internal pure returns (uint256) {
                            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
                            // benefit is lost if 'b' is also tested.
                            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
                            if (a == 0) {
                                return 0;
                            }
                    
                            uint256 c = a * b;
                            require(c / a == b, "SafeMath: multiplication overflow");
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function div(uint256 a, uint256 b) internal pure returns (uint256) {
                            return div(a, b, "SafeMath: division by zero");
                        }
                    
                        /**
                         * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
                         * division by zero. The result is rounded towards zero.
                         *
                         * Counterpart to Solidity's `/` operator. Note: this function uses a
                         * `revert` opcode (which leaves remaining gas untouched) while Solidity
                         * uses an invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            // Solidity only automatically asserts when dividing by 0
                            require(b > 0, errorMessage);
                            uint256 c = a / b;
                            // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                    
                            return c;
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         */
                        function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                            return mod(a, b, "SafeMath: modulo by zero");
                        }
                    
                        /**
                         * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
                         * Reverts with custom message when dividing by zero.
                         *
                         * Counterpart to Solidity's `%` operator. This function uses a `revert`
                         * opcode (which leaves remaining gas untouched) while Solidity uses an
                         * invalid opcode to revert (consuming all remaining gas).
                         *
                         * Requirements:
                         * - The divisor cannot be zero.
                         *
                         * _Available since v2.4.0._
                         */
                        function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
                            require(b != 0, errorMessage);
                            return a % b;
                        }
                    }
                    
                    // File: @openzeppelin/contracts/utils/Address.sol
                    
                    pragma solidity ^0.5.5;
                    
                    /**
                     * @dev Collection of functions related to the address type
                     */
                    library Address {
                        /**
                         * @dev Returns true if `account` is a contract.
                         *
                         * This test is non-exhaustive, and there may be false-negatives: during the
                         * execution of a contract's constructor, its address will be reported as
                         * not containing a contract.
                         *
                         * IMPORTANT: It is unsafe to assume that an address for which this
                         * function returns false is an externally-owned account (EOA) and not a
                         * contract.
                         */
                        function isContract(address account) internal view returns (bool) {
                            // This method relies in extcodesize, which returns 0 for contracts in
                            // construction, since the code is only stored at the end of the
                            // constructor execution.
                    
                            // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                            // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                            // for accounts without code, i.e. `keccak256('')`
                            bytes32 codehash;
                            bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                            // solhint-disable-next-line no-inline-assembly
                            assembly { codehash := extcodehash(account) }
                            return (codehash != 0x0 && codehash != accountHash);
                        }
                    
                        /**
                         * @dev Converts an `address` into `address payable`. Note that this is
                         * simply a type cast: the actual underlying value is not changed.
                         *
                         * _Available since v2.4.0._
                         */
                        function toPayable(address account) internal pure returns (address payable) {
                            return address(uint160(account));
                        }
                    
                        /**
                         * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
                         * `recipient`, forwarding all available gas and reverting on errors.
                         *
                         * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
                         * of certain opcodes, possibly making contracts go over the 2300 gas limit
                         * imposed by `transfer`, making them unable to receive funds via
                         * `transfer`. {sendValue} removes this limitation.
                         *
                         * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
                         *
                         * IMPORTANT: because control is transferred to `recipient`, care must be
                         * taken to not create reentrancy vulnerabilities. Consider using
                         * {ReentrancyGuard} or the
                         * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
                         *
                         * _Available since v2.4.0._
                         */
                        function sendValue(address payable recipient, uint256 amount) internal {
                            require(address(this).balance >= amount, "Address: insufficient balance");
                    
                            // solhint-disable-next-line avoid-call-value
                            (bool success, ) = recipient.call.value(amount)("");
                            require(success, "Address: unable to send value, recipient may have reverted");
                        }
                    }
                    
                    // File: @openzeppelin/contracts/token/ERC20/SafeERC20.sol
                    
                    pragma solidity ^0.5.0;
                    
                    
                    
                    
                    /**
                     * @title SafeERC20
                     * @dev Wrappers around ERC20 operations that throw on failure (when the token
                     * contract returns false). Tokens that return no value (and instead revert or
                     * throw on failure) are also supported, non-reverting calls are assumed to be
                     * successful.
                     * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
                     * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
                     */
                    library SafeERC20 {
                        using SafeMath for uint256;
                        using Address for address;
                    
                        function safeTransfer(IERC20 token, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
                        }
                    
                        function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                            callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
                        }
                    
                        function safeApprove(IERC20 token, address spender, uint256 value) internal {
                            // safeApprove should only be called when setting an initial allowance,
                            // or when resetting it to zero. To increase and decrease it, use
                            // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                            // solhint-disable-next-line max-line-length
                            require((value == 0) || (token.allowance(address(this), spender) == 0),
                                "SafeERC20: approve from non-zero to non-zero allowance"
                            );
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
                        }
                    
                        function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).add(value);
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                            uint256 newAllowance = token.allowance(address(this), spender).sub(value, "SafeERC20: decreased allowance below zero");
                            callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                        }
                    
                        /**
                         * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
                         * on the return value: the return value is optional (but if data is returned, it must not be false).
                         * @param token The token targeted by the call.
                         * @param data The call data (encoded using abi.encode or one of its variants).
                         */
                        function callOptionalReturn(IERC20 token, bytes memory data) private {
                            // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                            // we're implementing it ourselves.
                    
                            // A Solidity high level call has three parts:
                            //  1. The target address is checked to verify it contains contract code
                            //  2. The call itself is made, and success asserted
                            //  3. The return value is decoded, which in turn checks the size of the returned data.
                            // solhint-disable-next-line max-line-length
                            require(address(token).isContract(), "SafeERC20: call to non-contract");
                    
                            // solhint-disable-next-line avoid-low-level-calls
                            (bool success, bytes memory returndata) = address(token).call(data);
                            require(success, "SafeERC20: low-level call failed");
                    
                            if (returndata.length > 0) { // Return data is optional
                                // solhint-disable-next-line max-line-length
                                require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                            }
                        }
                    }
                    
                    // File: @openzeppelin/contracts/GSN/Context.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /*
                     * @dev Provides information about the current execution context, including the
                     * sender of the transaction and its data. While these are generally available
                     * via msg.sender and msg.data, they should not be accessed in such a direct
                     * manner, since when dealing with GSN meta-transactions the account sending and
                     * paying for execution may not be the actual sender (as far as an application
                     * is concerned).
                     *
                     * This contract is only required for intermediate, library-like contracts.
                     */
                    contract Context {
                        // Empty internal constructor, to prevent people from mistakenly deploying
                        // an instance of this contract, which should be used via inheritance.
                        constructor () internal { }
                        // solhint-disable-previous-line no-empty-blocks
                    
                        function _msgSender() internal view returns (address payable) {
                            return msg.sender;
                        }
                    
                        function _msgData() internal view returns (bytes memory) {
                            this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
                            return msg.data;
                        }
                    }
                    
                    // File: @openzeppelin/contracts/ownership/Ownable.sol
                    
                    pragma solidity ^0.5.0;
                    
                    /**
                     * @dev Contract module which provides a basic access control mechanism, where
                     * there is an account (an owner) that can be granted exclusive access to
                     * specific functions.
                     *
                     * This module is used through inheritance. It will make available the modifier
                     * `onlyOwner`, which can be applied to your functions to restrict their use to
                     * the owner.
                     */
                    contract Ownable is Context {
                        address private _owner;
                    
                        event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
                    
                        /**
                         * @dev Initializes the contract setting the deployer as the initial owner.
                         */
                        constructor () internal {
                            _owner = _msgSender();
                            emit OwnershipTransferred(address(0), _owner);
                        }
                    
                        /**
                         * @dev Returns the address of the current owner.
                         */
                        function owner() public view returns (address) {
                            return _owner;
                        }
                    
                        /**
                         * @dev Throws if called by any account other than the owner.
                         */
                        modifier onlyOwner() {
                            require(isOwner(), "Ownable: caller is not the owner");
                            _;
                        }
                    
                        /**
                         * @dev Returns true if the caller is the current owner.
                         */
                        function isOwner() public view returns (bool) {
                            return _msgSender() == _owner;
                        }
                    
                        /**
                         * @dev Leaves the contract without owner. It will not be possible to call
                         * `onlyOwner` functions anymore. Can only be called by the current owner.
                         *
                         * NOTE: Renouncing ownership will leave the contract without an owner,
                         * thereby removing any functionality that is only available to the owner.
                         */
                        function renounceOwnership() public onlyOwner {
                            emit OwnershipTransferred(_owner, address(0));
                            _owner = address(0);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         * Can only be called by the current owner.
                         */
                        function transferOwnership(address newOwner) public onlyOwner {
                            _transferOwnership(newOwner);
                        }
                    
                        /**
                         * @dev Transfers ownership of the contract to a new account (`newOwner`).
                         */
                        function _transferOwnership(address newOwner) internal {
                            require(newOwner != address(0), "Ownable: new owner is the zero address");
                            emit OwnershipTransferred(_owner, newOwner);
                            _owner = newOwner;
                        }
                    }
                    
                    // File: contracts/interfaces/ILendingProtocol.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface ILendingProtocol {
                      function mint() external returns (uint256);
                      function redeem(address account) external returns (uint256);
                      function nextSupplyRate(uint256 amount) external view returns (uint256);
                      function nextSupplyRateWithParams(uint256[] calldata params) external view returns (uint256);
                      function getAPR() external view returns (uint256);
                      function getPriceInToken() external view returns (uint256);
                      function token() external view returns (address);
                      function underlying() external view returns (address);
                      function availableLiquidity() external view returns (uint256);
                    }
                    
                    // File: contracts/interfaces/AaveLendingPoolProvider.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface AaveLendingPoolProvider {
                      function getLendingPool() external view returns (address);
                      function getLendingPoolCore() external view returns (address);
                    }
                    
                    // File: contracts/interfaces/AaveLendingPool.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface AaveLendingPool {
                      function deposit(address _reserve, uint256 _amount, uint16 _referralCode) external;
                      function getReserveData(address _reserve)
                        external view returns (
                          uint256 totalLiquidity,
                          uint256 availableLiquidity,
                          uint256 totalBorrowsStable,
                          uint256 totalBorrowsVariable,
                          uint256 liquidityRate,
                          uint256 variableBorrowRate,
                          uint256 stableBorrowRate,
                          uint256 averageStableBorrowRate,
                          uint256 utilizationRate,
                          uint256 liquidityIndex,
                          uint256 variableBorrowIndex,
                          address aTokenAddress,
                          uint40 lastUpdateTimestamp
                        );
                    }
                    
                    // File: contracts/interfaces/AaveLendingPoolCore.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface AaveLendingPoolCore {
                      function getReserveCurrentLiquidityRate(address _reserve) external view returns (uint256);
                      function getReserveInterestRateStrategyAddress(address _reserve) external view returns (address);
                      function getReserveTotalBorrowsStable(address _reserve) external view returns (uint256);
                      function getReserveTotalBorrowsVariable(address _reserve) external view returns (uint256);
                      function getReserveCurrentAverageStableBorrowRate(address _reserve) external view returns (uint256);
                      function getReserveAvailableLiquidity(address _reserve) external view returns (uint256);
                    }
                    
                    // File: contracts/interfaces/AToken.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface AToken {
                      function redeem(uint256 amount) external;
                    }
                    
                    // File: contracts/interfaces/AaveInterestRateStrategy.sol
                    
                    pragma solidity 0.5.16;
                    
                    interface AaveInterestRateStrategy {
                      function getBaseVariableBorrowRate() external view returns (uint256);
                      function calculateInterestRates(
                        address _reserve,
                        uint256 _utilizationRate,
                        uint256 _totalBorrowsStable,
                        uint256 _totalBorrowsVariable,
                        uint256 _averageStableBorrowRate) external view
                      returns (uint256 liquidityRate, uint256 stableBorrowRate, uint256 variableBorrowRate);
                    }
                    
                    // File: contracts/wrappers/IdleAave.sol
                    
                    /**
                     * @title: Aave wrapper
                     * @summary: Used for interacting with Aave. Has
                     *           a common interface with all other protocol wrappers.
                     *           This contract holds assets only during a tx, after tx it should be empty
                     * @author: Idle Labs Inc., idle.finance
                     */
                    pragma solidity 0.5.16;
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    
                    contract IdleAave is ILendingProtocol, Ownable {
                      using SafeERC20 for IERC20;
                      using SafeMath for uint256;
                    
                      // protocol token (aToken) address
                      address public token;
                      // underlying token (token eg DAI) address
                      address public underlying;
                      address public idleToken;
                    
                      address public constant aaveAddressesProvider = address(0x24a42fD28C976A61Df5D00D0599C34c4f90748c8);
                      AaveLendingPoolProvider provider = AaveLendingPoolProvider(aaveAddressesProvider);
                    
                      /**
                       * @param _token : aToken address
                       * @param _underlying : underlying token (eg DAI) address
                       */
                      constructor(address _token, address _underlying) public {
                        require(_token != address(0) && _underlying != address(0), 'AAVE: some addr is 0');
                    
                        token = _token;
                        underlying = _underlying;
                        IERC20(_underlying).safeApprove(
                          provider.getLendingPoolCore(),
                          uint256(-1)
                        );
                      }
                    
                      /**
                       * Throws if called by any account other than IdleToken contract.
                       */
                      modifier onlyIdle() {
                        require(msg.sender == idleToken, "Ownable: caller is not IdleToken");
                        _;
                      }
                    
                      // onlyOwner
                      /**
                       * sets idleToken address
                       * NOTE: can be called only once. It's not on the constructor because we are deploying this contract
                       *       after the IdleToken contract
                       * @param _idleToken : idleToken address
                       */
                      function setIdleToken(address _idleToken)
                        external onlyOwner {
                          require(idleToken == address(0), "idleToken addr already set");
                          require(_idleToken != address(0), "_idleToken addr is 0");
                          idleToken = _idleToken;
                      }
                      // end onlyOwner
                    
                      /**
                       * Calculate next supply rate for Aave, given an `_amount` supplied (last array param)
                       * and all other params supplied.
                       * on calculations.
                       *
                       * @param params : array with all params needed for calculation (see below)
                       * @return : yearly net rate
                       */
                      function nextSupplyRateWithParams(uint256[] calldata params)
                        external view
                        returns (uint256) {
                          AaveLendingPoolCore core = AaveLendingPoolCore(provider.getLendingPoolCore());
                          AaveInterestRateStrategy apr = AaveInterestRateStrategy(core.getReserveInterestRateStrategyAddress(underlying));
                          /*
                            params[0] = core.getReserveAvailableLiquidity(underlying);
                            params[1] = core.getReserveTotalBorrowsStable(underlying);
                            params[2] = core.getReserveTotalBorrowsVariable(underlying);
                            params[3] = core.getReserveCurrentAverageStableBorrowRate(underlying);
                            params[4] = _amount;
                          */
                    
                          (uint256 newLiquidityRate,,) = apr.calculateInterestRates(
                            underlying,
                            params[0].add(params[4]),
                            params[1],
                            params[2],
                            params[3]
                          );
                    
                          // newLiquidityRate is in RAY (ie 1e27)
                          // also newLiquidityRate is in the form 0.03 * 1e27
                          // while we need the result in the form 3 * 1e18
                          return newLiquidityRate.mul(100).div(10**9);
                      }
                    
                      /**
                       * Calculate next supply rate for Aave, given an `_amount` supplied
                       *
                       * @param _amount : new underlying amount supplied (eg DAI)
                       * @return : yearly net rate
                       */
                      function nextSupplyRate(uint256 _amount)
                        external view
                        returns (uint256) {
                          AaveLendingPoolCore core = AaveLendingPoolCore(provider.getLendingPoolCore());
                          AaveInterestRateStrategy apr = AaveInterestRateStrategy(core.getReserveInterestRateStrategyAddress(underlying));
                    
                          (uint256 newLiquidityRate,,) = apr.calculateInterestRates(
                            underlying,
                            core.getReserveAvailableLiquidity(underlying).add(_amount),
                            core.getReserveTotalBorrowsStable(underlying),
                            core.getReserveTotalBorrowsVariable(underlying),
                            core.getReserveCurrentAverageStableBorrowRate(underlying)
                          );
                          return newLiquidityRate.mul(100).div(10**9);
                      }
                    
                      /**
                       * @return current price of aToken in underlying, Aave price is always 1
                       */
                      function getPriceInToken()
                        external view
                        returns (uint256) {
                          return 10**18;
                      }
                    
                      /**
                       * @return apr : current yearly net rate
                       */
                      function getAPR()
                        external view
                        returns (uint256) {
                          AaveLendingPoolCore core = AaveLendingPoolCore(provider.getLendingPoolCore());
                          return core.getReserveCurrentLiquidityRate(underlying).mul(100).div(10**9);
                      }
                    
                      /**
                       * Gets all underlying tokens in this contract and mints aTokens
                       * tokens are then transferred to msg.sender
                       * NOTE: underlying tokens needs to be sent here before calling this
                       *
                       * @return aTokens minted
                       */
                      function mint()
                        external onlyIdle
                        returns (uint256 aTokens) {
                          uint256 balance = IERC20(underlying).balanceOf(address(this));
                          if (balance == 0) {
                            return aTokens;
                          }
                          AaveLendingPool lendingPool = AaveLendingPool(provider.getLendingPool());
                          lendingPool.deposit(underlying, balance, 29); // 29 -> referral
                          aTokens = IERC20(token).balanceOf(address(this));
                          // transfer them to the caller
                          IERC20(token).safeTransfer(msg.sender, aTokens);
                      }
                    
                      /**
                       * Gets all aTokens in this contract and redeems underlying tokens.
                       * underlying tokens are then transferred to `_account`
                       * NOTE: aTokens needs to be sent here before calling this
                       *
                       * @return underlying tokens redeemd
                       */
                      function redeem(address _account)
                        external onlyIdle
                        returns (uint256 tokens) {
                          AToken(token).redeem(IERC20(token).balanceOf(address(this)));
                          IERC20 _underlying = IERC20(underlying);
                    
                          tokens = _underlying.balanceOf(address(this));
                          _underlying.safeTransfer(_account, tokens);
                      }
                    
                      function availableLiquidity() external view returns (uint256) {
                        AaveLendingPoolCore core = AaveLendingPoolCore(provider.getLendingPoolCore());
                        return IERC20(underlying).balanceOf(address(core));
                      }
                    }