contract StableYieldCredit {
    using SafeERC20 for IERC20;

    /// @notice EIP-20 token name for this token
    string public constant name = "Stable Yield Credit";

    /// @notice EIP-20 token symbol for this token
    string public constant symbol = "scUSD";

    /// @notice EIP-20 token decimals for this token
    uint8 public constant decimals = 8;

    /// @notice Total number of tokens in circulation
    uint public totalSupply = 0; // Initial 0

    mapping(address => mapping (address => uint)) internal allowances;
    mapping(address => uint) internal balances;

    /// @notice The EIP-712 typehash for the contract's domain
    bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint chainId,address verifyingContract)");
    bytes32 public immutable DOMAINSEPARATOR;

    /// @notice The EIP-712 typehash for the permit struct used by the contract
    bytes32 public constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint value,uint nonce,uint deadline)");

    /// @notice A record of states for signing / validating signatures
    mapping (address => uint) public nonces;

    function safe32(uint n, string memory errorMessage) internal pure returns (uint32) {
        require(n < 2**32, errorMessage);
        return uint32(n);
    }

    /// @notice The standard EIP-20 transfer event
    event Transfer(address indexed from, address indexed to, uint amount);

    /// @notice The standard EIP-20 approval event
    event Approval(address indexed owner, address indexed spender, uint amount);

    // Oracle used for price debt data (external to the AMM balance to avoid internal manipulation)
    Oracle public constant LINK = Oracle(0x271bf4568fb737cc2e6277e9B1EE0034098cDA2a);
    ISushiswapV2Factory public constant FACTORY = ISushiswapV2Factory(0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac);
    
    // user => token => collateral
    mapping (address => mapping(address => uint)) public collateral;
    // user => token => credit
    mapping (address => mapping(address => uint)) public collateralCredit;
    
    address[] private _markets;
    
    event Deposit(address indexed creditor, address indexed collateral, uint creditOut, uint amountIn, uint creditMinted);
    event Withdraw(address indexed creditor, address indexed collateral, uint creditIn, uint creditOut, uint amountOut);
    
    constructor () {
        DOMAINSEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), _getChainId(), address(this)));
    }
    
    uint public FEE = 50;
    uint public BASE = 10000;
    
    uint public fees = 0;
    uint public index = 0;
    uint public distributed = 0;
    
    mapping(address => uint) public supplyIndex;
    
    function update() external {
        _update();
    }
    
    function _update() internal {
        if (totalSupply > 0) {
            if (fees > distributed) {
                uint256 _diff = fees - distributed;
                if (_diff > 0) {
                    uint256 _ratio = _diff * 1e8 / totalSupply;
                    if (_ratio > 0) {
                      index += _ratio;
                      distributed = fees;
                    }
                }
            }
        }
    }
    
    function updateFor(address dst) public {
        _update();
        uint256 _supplied = balances[dst];
        if (_supplied > 0) {
            uint256 _supplyIndex = supplyIndex[dst];
            supplyIndex[dst] = index;
            uint256 _delta = index - _supplyIndex;
            if (_delta > 0) {
              uint256 _share = _supplied * _delta / 1e8;
              claimable[dst] += _share;
            }
        } else {
            supplyIndex[dst] = index;
        }
    }
    
    mapping(address => uint) public claimable;
    
    function claim() external {
        _claimFor(msg.sender);
    }
    
    function claimFor(address dst) external {
        _claimFor(dst);
    }
    
    function _claimFor(address dst) internal {
        updateFor(dst);
        fees -= claimable[dst];
        
        // mint the amount
        totalSupply += claimable[dst];
        // transfer the amount to the recipient
        balances[dst] += claimable[dst];
        emit Transfer(address(0), dst, claimable[dst]);
        
        claimable[dst] = 0;
        distributed = fees;
    }
    
    function markets() external view returns (address[] memory) {
        return _markets;
    }
    
    function _mint(address dst, uint amount) internal {
        updateFor(dst);
        // mint the amount
        totalSupply += amount;
        // transfer the amount to the recipient
        balances[dst] += amount;
        emit Transfer(address(0), dst, amount);
    }
    
    function _burn(address dst, uint amount) internal {
        updateFor(dst);
        // mint the amount
        totalSupply -= amount;
        // transfer the amount to the recipient
        balances[dst] -= amount;
        emit Transfer(dst, address(0), amount);
    }
    
    function depositAll(IERC20 token) external {
        _deposit(token, token.balanceOf(msg.sender));
    }
    
    function deposit(IERC20 token, uint amount) external {
        _deposit(token, amount);
    }
    
    function _addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired
    ) internal virtual returns (address pair, uint amountA, uint amountB) {
        // create the pair if it doesn't exist yet
        pair = FACTORY.getPair(tokenA, tokenB);
        if (pair == address(0)) {
            pair = FACTORY.createPair(tokenA, tokenB);
            _markets.push(tokenA);
        }
        (uint reserveA, uint reserveB) = SushiswapV2Library.getReserves(address(FACTORY), tokenA, tokenB);
        if (reserveA == 0 && reserveB == 0) {
            (amountA, amountB) = (amountADesired, amountBDesired);
        } else {
            uint amountBOptimal = SushiswapV2Library.quote(amountADesired, reserveA, reserveB);
            if (amountBOptimal <= amountBDesired) {
                (amountA, amountB) = (amountADesired, amountBOptimal);
            } else {
                uint amountAOptimal = SushiswapV2Library.quote(amountBDesired, reserveB, reserveA);
                assert(amountAOptimal <= amountADesired);
                (amountA, amountB) = (amountAOptimal, amountBDesired);
            }
        }
    }
    
    function _deposit(IERC20 token, uint amount) internal {
        uint _value = LINK.getPriceUSD(address(token)) * amount / uint256(10)**token.decimals();
        require(_value > 0, "!value");
        
        (address _pair, uint amountA,) = _addLiquidity(address(token), address(this), amount, _value);
        
        token.safeTransferFrom(msg.sender, _pair, amountA);
        _mint(_pair, _value); // Amount of scUSD to mint
        
        uint _liquidity = ISushiswapV2Pair(_pair).mint(address(this));
        collateral[msg.sender][address(token)] += _liquidity;
        
        collateralCredit[msg.sender][address(token)] += _value;
        uint _fees = _value * FEE / BASE;
        _mint(msg.sender, _value - _fees);
        fees += _fees;
        
        emit Deposit(msg.sender, address(token), _value, amount, _value);
    }
    
    function withdrawAll(IERC20 token) external {
        _withdraw(token, IERC20(address(this)).balanceOf(msg.sender));
    }
    
    function withdraw(IERC20 token, uint amount) external {
        _withdraw(token, amount);
    }
    
    function _withdraw(IERC20 token, uint amount) internal {
        uint _credit = collateralCredit[msg.sender][address(token)];
        uint _collateral = collateral[msg.sender][address(token)];
        
        if (_credit < amount) {
            amount = _credit;
        }
        
        // Calculate % of collateral to release
        uint _burned = _collateral * amount / _credit;
        address _pair = FACTORY.getPair(address(token), address(this));
        
        IERC20(_pair).safeTransfer(_pair, _burned); // send liquidity to pair
        (uint _amount0, uint _amount1) = ISushiswapV2Pair(_pair).burn(msg.sender);
        (address _token0,) = SushiswapV2Library.sortTokens(address(token), address(this));
        (uint _amountA, uint _amountB) = address(token) == _token0 ? (_amount0, _amount1) : (_amount1, _amount0);
        
        collateralCredit[msg.sender][address(token)] -= amount;
        collateral[msg.sender][address(token)] -= _burned;
        _burn(msg.sender, _amountB+amount); // Amount of scUSD to burn (value of A leaving the system)
        
        emit Withdraw(msg.sender, address(token), amount, _amountB, _amountA);
    }

    /**
     * @notice Get the number of tokens `spender` is approved to spend on behalf of `account`
     * @param account The address of the account holding the funds
     * @param spender The address of the account spending the funds
     * @return The number of tokens approved
     */
    function allowance(address account, address spender) external view returns (uint) {
        return allowances[account][spender];
    }

    /**
     * @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 (2^256-1 means infinite)
     * @return Whether or not the approval succeeded
     */
    function approve(address spender, uint amount) external returns (bool) {
        allowances[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);
        return true;
    }

    /**
     * @notice Triggers an approval from owner to spends
     * @param owner The address to approve from
     * @param spender The address to be approved
     * @param amount The number of tokens that are approved (2^256-1 means infinite)
     * @param deadline The time at which to expire the signature
     * @param v The recovery byte of the signature
     * @param r Half of the ECDSA signature pair
     * @param s Half of the ECDSA signature pair
     */
    function permit(address owner, address spender, uint amount, uint deadline, uint8 v, bytes32 r, bytes32 s) external {
        bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, amount, nonces[owner]++, deadline));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAINSEPARATOR, structHash));
        address signatory = ecrecover(digest, v, r, s);
        require(signatory != address(0), "permit: signature");
        require(signatory == owner, "permit: unauthorized");
        require(block.timestamp <= deadline, "permit: expired");

        allowances[owner][spender] = amount;

        emit Approval(owner, spender, amount);
    }

    /**
     * @notice Get the number of tokens held by the `account`
     * @param account The address of the account to get the balance of
     * @return The number of tokens held
     */
    function balanceOf(address account) external view returns (uint) {
        return balances[account];
    }

    /**
     * @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) {
        _transferTokens(msg.sender, dst, amount);
        return true;
    }

    /**
     * @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, uint amount) external returns (bool) {
        address spender = msg.sender;
        uint spenderAllowance = allowances[src][spender];

        if (spender != src && spenderAllowance != type(uint).max) {
            uint newAllowance = spenderAllowance - amount;
            allowances[src][spender] = newAllowance;

            emit Approval(src, spender, newAllowance);
        }

        _transferTokens(src, dst, amount);
        return true;
    }

    function _transferTokens(address src, address dst, uint amount) internal {
        require(src != address(0), "_transferTokens: zero address");
        require(dst != address(0), "_transferTokens: zero address");
        
        updateFor(src);
        updateFor(dst);

        balances[src] -= amount;
        balances[dst] += amount;
        emit Transfer(src, dst, amount);
    }

    function _getChainId() internal view returns (uint) {
        uint chainId;
        assembly { chainId := chainid() }
        return chainId;
    }
}
function approve(address spender, uint amount) external returns (bool) {
    allowances[msg.sender][spender] = amount;

    emit Approval(msg.sender, spender, amount);
    return true;
}

/**
 * @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 (2^256-1 means infinite)
 * @return Whether or not the approval succeeded
 */