contract StableCreditProtocol is ERC20, ERC20Detailed {
    using SafeERC20 for IERC20;
    using Address for address;
    using SafeMath for uint;

    // Oracle used for price debt data (external to the AMM balance to avoid internal manipulation)
    Oracle public constant LINK = Oracle(0x271bf4568fb737cc2e6277e9B1EE0034098cDA2a);
    // Uniswap v2
    UniswapRouter public constant UNI = UniswapRouter(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);
    
    // Maximum credit issued off of deposits (to avoid infinite leverage)
    uint public constant MAX = 7500;
    uint public constant BASE = 10000;
    
    // user => token => credit
    mapping (address => mapping(address => uint)) public credit;
    // user => token => balance
    mapping (address => mapping(address => uint)) public balances;
    
    // How much system liquidity is provided by this asset
    function utilization(address token) public view returns (uint) {
        address pair = UniswapFactory(UNI.factory()).getPair(token, address(this));
        uint _ratio = BASE.sub(BASE.mul(balanceOf(pair)).div(totalSupply()));
        if (_ratio == 0) {
            return MAX;
        }
        return  _ratio > MAX ? MAX : _ratio;
    }
    
    constructor () public ERC20Detailed("StableCredit", "USD", 8) {}
    
    function depositAll(address token) external {
        deposit(token, IERC20(token).balanceOf(msg.sender));
    }
    
    function deposit(address token, uint amount) public {
        _deposit(token, amount);
    }
    
    // UNSAFE: No slippage protection, should not be called directly
    function _deposit(address token, uint amount) internal {
        uint value = LINK.getPriceUSD(token).mul(amount).div(uint256(10)**ERC20Detailed(token).decimals());
        require(value > 0, "!value");
        
        address pair = UniswapFactory(UNI.factory()).getPair(token, address(this));
        if (pair == address(0)) {
            pair = UniswapFactory(UNI.factory()).createPair(token, address(this));
        }
        
        IERC20(token).safeTransferFrom(msg.sender, pair, amount);
        _mint(pair, value); // Amount of aUSD to mint
        
        uint _before = IERC20(pair).balanceOf(address(this));
        UniswapPair(pair).mint(address(this));
        uint _after = IERC20(pair).balanceOf(address(this));
        

        // Assign LP tokens to user, token <> pair is deterministic thanks to CREATE2
        balances[msg.sender][token] = balances[msg.sender][token].add(_after.sub(_before));
        
        // Calculate utilization ratio of the asset. The more an asset contributes to the system, the less credit issued
        // This mechanism avoids large influx of deposits to overpower the system
        // Calculated after deposit to see impact of current deposit (prevents front-running credit)
        uint _credit = value.mul(utilization(token)).div(BASE);
        credit[msg.sender][token] = credit[msg.sender][token].add(_credit);
        _mint(msg.sender, _credit);
    }
    
    function withdrawAll(address token) external {
        _withdraw(token, IERC20(this).balanceOf(msg.sender));
    }
    
    function withdraw(address token, uint amount) external {
        _withdraw(token, amount);
    }

    // UNSAFE: No slippage protection, should not be called directly
    function _withdraw(address token, uint amount) internal {
        
        uint _credit = credit[msg.sender][token];
        uint _uni = balances[msg.sender][token];
        
        if (_credit < amount) {
            amount = _credit;
        }
        
        _burn(msg.sender, amount);
        credit[msg.sender][token] = credit[msg.sender][token].sub(amount);
        
        // Calculate % of collateral to release
        _uni = _uni.mul(amount).div(_credit);
        
        address pair = UniswapFactory(UNI.factory()).getPair(token, address(this));
        
        IERC20(pair).safeApprove(address(UNI), 0);
        IERC20(pair).safeApprove(address(UNI), _uni);
        
        UNI.removeLiquidity(
          token,
          address(this),
          _uni,
          0,
          0,
          address(this),
          now.add(1800)
        );
        
        uint amountA = IERC20(token).balanceOf(address(this));
        uint amountB = balanceOf(address(this));
        
        uint valueA = LINK.getPriceUSD(token).mul(amountA).div(uint256(10)**ERC20Detailed(token).decimals());
        require(valueA > 0, "!value");
        
        // Collateral increased in value, but we max at amount B withdrawn
        if (valueA > amountB) {
            valueA = amountB;
        }
        
        _burn(address(this), valueA); // Amount of aUSD to burn (value of A leaving the system)
        
        IERC20(token).safeTransfer(msg.sender, amountA);
        if (amountB > valueA) { // Asset A appreciated in value, receive credit diff
            IERC20(this).safeTransfer(msg.sender, balanceOf(address(this)));
        }
    }
}
function _withdraw(address token, uint amount) internal {
        
    uint _credit = credit[msg.sender][token];
    uint _uni = balances[msg.sender][token];
        
    if (_credit < amount) {
        amount = _credit;
    }
        
    _burn(msg.sender, amount);
    credit[msg.sender][token] = credit[msg.sender][token].sub(amount);
        
    // Calculate % of collateral to release
    _uni = _uni.mul(amount).div(_credit);
        
    address pair = UniswapFactory(UNI.factory()).getPair(token, address(this));
        
    IERC20(pair).safeApprove(address(UNI), 0);
    IERC20(pair).safeApprove(address(UNI), _uni);
        
    UNI.removeLiquidity(
      token,
      address(this),
      _uni,
      0,
      0,
      address(this),
      now.add(1800)
    );
        
    uint amountA = IERC20(token).balanceOf(address(this));
    uint amountB = balanceOf(address(this));
        
    uint valueA = LINK.getPriceUSD(token).mul(amountA).div(uint256(10)**ERC20Detailed(token).decimals());
    require(valueA > 0, "!value");
        
    // Collateral increased in value, but we max at amount B withdrawn
    if (valueA > amountB) {
        valueA = amountB;
    }
        
    _burn(address(this), valueA); // Amount of aUSD to burn (value of A leaving the system)
        
    IERC20(token).safeTransfer(msg.sender, amountA);
    if (amountB > valueA) { // Asset A appreciated in value, receive credit diff
        IERC20(this).safeTransfer(msg.sender, balanceOf(address(this)));
    }
}

// UNSAFE: No slippage protection, should not be called directly