contract GoaldIndirect is ERC721 {
    /** @dev WETH is used as an intermediary within Uniswap for greater liquidity. */
    address private constant WETH_CONTRACT = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;

    /** @dev The collateral token stored (e.g., WBTC). */
    address private _collateralAddress;

    /** @dev The payment token taken (e.g., WETH). */
    address private _paymentAddress;

    /** @dev The address of the latest goald proxy. */
    address private  _proxyAddress;

    /** @dev The path between the payment token and the collateral token so the Uniswap router can do swaps. */
    address[] private _uniPath;

    /**
     * @dev Packed values (all uints):
     *  bits   offset   variable name   description
     *    80     0   fee                  The fee per interval of the goald.
     *     8    80   feeIntervalDays      How many days until the next fee is due; 14 to 60.
     *    16    88   totalIntervals       The total number of intervals this goald will have when it is finalized.
     *    16   104   remainingIntervals   How many fee intervals remain before the goald is finalized.
     *    96   120   currentFeeBalance    The current fee balance; this will only be more than the fee while processing a payment.
     *     1   216   finalized            If the goald has been finalized.
     *  ----
     *   217
     */
    uint256 private _values;

    // The masks and shift distances for each value from the packed state variable. This works for both arithmatic and logical shifts,
    // though currrently shifts are logical. This is for big endian values.
    // READ:
    //      1) AND the MASK with the packed values to expose the slot
    //      2) RIGHT SHIFT to get the raw value
    //
    //      value = (_values & MASK) >> SHIFT
    //
    //      MASK    = 0b000111000
    //      SHIFT   = 3
    //      _values = 0b100101101
    //      1)     -> 0b000101000
    //      2)     -> 0b000000101
    //      
    // WRITE:
    //      1) LEFT SHIFT to position with the slot
    //      2) AND the MASK with the shifted value, to get rid of dirty bits
    //      3) AND the NEGATIVE MASK with the packed values to wipe the slot
    //      4) OR the shifted value to update
    //
    //      _values = ((value << SHIFT) & MASK) | (_values & ~MASK)
    //
    //      MASK    = 0b000111000
    //      SHIFT   = 3
    //      _values = 0b100101101
    //      value   = 0b001100011
    //      1)     -> 0b100011000
    //      2)     -> 0b000011000
    //      3)     -> 0b100000101
    //      4)     -> 0b100011101
    uint256 private constant FEE_MASK                  = 0xFFFFFFFFFFFFFFFFFFFF;
    uint256 private constant FEE_SHIFT                 = 0;

    uint256 private constant FEE_INTERVAL_DAYS_MASK    = 0xFF00000000000000000000;
    uint256 private constant FEE_INTERVAL_DAYS_SHIFT   = 80;

    uint256 private constant TOTAL_INTERVALS_MASK      = 0xFFFF0000000000000000000000;
    uint256 private constant TOTAL_INTERVALS_SHIFT     = 88;

    uint256 private constant REMAINING_INTERVALS_MASK  = 0xFFFF00000000000000000000000000;
    uint256 private constant REMAINING_INTERVALS_SHIFT = 104;

    uint256 private constant CURRENT_FEE_BALANCE_MASK  = 0xFFFFFFFFFFFFFFFFFFFFFFFF000000000000000000000000000000;
    uint256 private constant CURRENT_FEE_BALANCE_SHIFT = 120;

    uint256 private constant FINALIZED_MASK            = 0x1000000000000000000000000000000000000000000000000000000;
    uint256 private constant FINALIZED_SHIFT           = 216;

    /** @dev The deadline for the fee to be paid before the goald locks. Any payment made after the deadline will have a minor fee. */
    uint256 private _nextFeeTimestamp;

    /** @dev If a payment is sent after the next pay period, a small fine is taken. This is currently 0.25%. */
    uint256 private constant LATE_PAYMENT_FINE_DIVISOR = 400;

    // We include this here instead of the `nonReentrant` modifier to reduce gas costs. We also might change the reentrancy state
    // multiple times within a single function depending on needs.
    // See OpenZeppelin - ReentrancyGuard for more.
    // Reentrancy reversions are the only calls to revert (in this contract) that do not have reasons.
    uint256 private constant RE_NOT_ENTERED = 1;
    uint256 private constant RE_ENTERED     = 2;
    uint256 private _status;

    /** @dev Emitted whenever a fee is paid. */
    event FeePaid(uint256 feePaid, uint256 feeTokensSpent, uint256 collateralTokensReceived);

    /** @dev Emitted when the collateral pool has been drained. */
    event GoaldFinalized();

    /**
     * @param proxyAddress The address of the proxy.
     * @param owner Who owns the goald and is required to pay the fees to maintain it (though anyone can pay the fees).
     * @param collateralToken Which token will be used as collateral in the collateral pool (e.g., WBTC).
     * @param paymentToken Which token will be used to pay for the collateral token (e.g., WETH).
     * @param fee The periodic fee required to be paid to maintain the goald; minimum 1. Anyone can pay the fee.
     * @param feeIntervalDays How many days between each fee payment; minimum 1.
     * @param finalizationIntervals How many fee intervals must pass before the goald finalizes; minimum 1.
     */
    constructor(
        address proxyAddress,
        address owner,
        address collateralToken,
        address paymentToken,
        uint96  fee,
        uint8   feeIntervalDays,
        uint16  finalizationIntervals
    ) ERC721("", "") public {
        // Validate the addresses.
        require(proxyAddress    != address(0),      "PRX: Zero address not allowed");
        require(proxyAddress    != address(this),   "PRX: Goald address not allowed");
        require(owner           != address(0),      "OWN: Zero address not allowed");
        require(owner           != address(this),   "OWN: Goald address not allowed");
        require(owner           != collateralToken, "OWN: COL address not allowed");
        require(owner           != paymentToken,    "OWN: PAY address not allowed");
        require(collateralToken != address(0),      "COL: Zero address not allowed");
        require(collateralToken != address(this),   "COL: Goald address not allowed");
        require(collateralToken != paymentToken,    "COL: PAY address not allowed");
        require(collateralToken != WETH_CONTRACT,   "COL: WETH not allowd");
        require(paymentToken    != address(0),      "PAY: Zero address not allowed");
        require(paymentToken    != address(this),   "PAY: Goald address not allowed");
        require(paymentToken    != WETH_CONTRACT,   "PAY: WETH not allowd");

        // Set the proxy address.
        _proxyAddress = proxyAddress;

        // The tokens must be contracts.
        uint256 codeSize;
        assembly { codeSize := extcodesize(collateralToken) }
        require(codeSize > 0, "COL: not a contract");
        assembly { codeSize := extcodesize(paymentToken) }
        require(codeSize > 0, "PAY: not a contract");

        // Validate the fee.
        require(fee > 0,                   "FEE: Must be positive");
        require(feeIntervalDays > 0,       "FEE_INT: Must be nonzero");
        require(finalizationIntervals > 0, "EXP_INT: Must be nonzero");

        // Set up the tokens and uniswap paths. There must be a direct path between the two and enough liquidity in uniswap for this
        // to function. We use WETH as an intermediary to improve liquidity.
        _collateralAddress = collateralToken;
        _paymentAddress    = paymentToken;
        _uniPath           = [paymentToken, WETH_CONTRACT, collateralToken];

        // Mint the goald NFT. This determines ownership of the goald, and who can withdraw from either pool. This is never burned
        // and can only be transferred if the goald has not been finalized. The goald becomes valueless once it has been finalized.
        _mint(owner, 1);

        // Initialize the packed values. We mask because it's possible the calldata has dirty bits.
        _values = 0
            | ((uint256(fee)                   << FEE_SHIFT)                 & FEE_MASK)
            | ((uint256(feeIntervalDays)       << FEE_INTERVAL_DAYS_SHIFT)   & FEE_INTERVAL_DAYS_MASK)
            | ((uint256(finalizationIntervals) << TOTAL_INTERVALS_SHIFT)     & TOTAL_INTERVALS_MASK)
            | ((uint256(finalizationIntervals) << REMAINING_INTERVALS_SHIFT) & REMAINING_INTERVALS_MASK);

        // Set us up for reentrancy guarding.
        _status = RE_NOT_ENTERED;

        // Set the balance targets.
        _nextFeeTimestamp = block.timestamp + (feeIntervalDays * 1 days);

        // Validate the timestamp.
        require(_nextFeeTimestamp > block.timestamp);
    }

    /// Views ///

    /**
     * Single view over all internal state variables.
     *
     * Returns:
     *     Proxy contract address
     *     Collateral token address
     *     Payment token address (or 0 for ETH)
     *     Uniswap router address
     *     Next fee timestamp
     *     Packed state values
     */
    function getDetails() external view returns (uint256[10] memory details) {
        // Important Addresses.
        details[0] = uint256(_proxyAddress);
        details[1] = uint256(_collateralAddress);
        details[2] = uint256(_paymentAddress);

        // Next fee timestamp.
        details[3] = _nextFeeTimestamp;

        // Packed values.
        uint256 values = _values;
        details[4] = (values & FEE_MASK)                 >> FEE_SHIFT;
        details[5] = (values & FEE_INTERVAL_DAYS_MASK)   >> FEE_INTERVAL_DAYS_SHIFT;
        details[6] = (values & TOTAL_INTERVALS_MASK)     >> TOTAL_INTERVALS_SHIFT;
        details[7] = (values & REMAINING_INTERVALS_MASK) >> REMAINING_INTERVALS_SHIFT;
        details[8] = (values & CURRENT_FEE_BALANCE_MASK) >> CURRENT_FEE_BALANCE_SHIFT;
        details[9] = (values & FINALIZED_MASK)           >> FINALIZED_SHIFT;
    }

    /// External Functions ///

    /**
     * Pay out the stored collateral. This can only be paid out to the owner, not a third party, in case there are any unknown
     * vulnerabilities. If this is called before all fees have been made, then there is a penalty on how much can be withdrawn
     * from the collateral pool.
     */
    function finalizeGoald() external {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;

        // Only the owner can finalize the goald.
        address owner = ownerOf(1);
        require(msg.sender == owner, "Caller not owner");

        // Can't be finalized.
        uint256 values = _values;
        require((values & FINALIZED_MASK) >> FINALIZED_SHIFT == 0, "Already finalized");

        // Make sure we have something to withdraw.
        address collateralAddress = _collateralAddress;
        uint256 collateralAmount  = IERC20(collateralAddress).balanceOf(address(this));
        require(collateralAmount > 0, "No collateral");

        // There is an early withdrawal penalty of 25%, decreasing linearly depending on how many intervals have been paid. We use
        // basis points since we can't use floats. In the event of an overflow, the penalty will be undervalued; this will only
        // happen if the balance was increased externally as the maximum value this contract could set it to is 2^96 (max fee *
        // max intervals). In all practicality, this will never happen.
        uint256 penaltyAmount =
            /* 25% in basis points */ ( 2500
            /* remaining intervals */     * (values & REMAINING_INTERVALS_MASK) >> REMAINING_INTERVALS_SHIFT
            /* total intervals */         / (values & TOTAL_INTERVALS_MASK)     >> TOTAL_INTERVALS_SHIFT
                                          * collateralAmount
            /* revert basis points */ ) / 10000;

        // Likewise, this will never underflow. Remaining intervals would have to be greater than total intervals, which can't happen.
        collateralAmount -= penaltyAmount;

        // Transfer the collateral. Only transfer the penalty if there is one.
        IERC20 collateralContract = IERC20(_collateralAddress);
        require(collateralContract.transfer(owner, collateralAmount), "RCVR: transfer fail");
        if (penaltyAmount > 0) {
            require(collateralContract.transfer(IGoaldProxy(_proxyAddress).getProxyAddress(), penaltyAmount), "PRXY: transfer fail");
        }

        // Set as finalized. Even though values didn't change, we don't use `|=` as that adds an unnecessary SLOAD.
        _values = values | FINALIZED_MASK;

        // Hello world!
        emit GoaldFinalized();

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /** An endpoint for an exteral autopay service. This can only be called if the goald is past due. */
    function payFeeLateOnly(uint256 deadline) external {
        require(block.timestamp >= _nextFeeTimestamp, "Not late");

        // Update the proxy address.
        address proxyAddress = IGoaldProxy(_proxyAddress).getProxyAddress();
        if (_proxyAddress != proxyAddress) {
            _proxyAddress = proxyAddress;
        }

        _payFee((_values & FEE_MASK) >> FEE_SHIFT, deadline);
    }

    /** Pay the goald fee. Overpayments extend the next fee deadline, but do not change the finalization date. */
    function payFee(uint96 amount, uint256 deadline) external {
        // No reentrancy guard needed since `_payFee` manages that.

        // Update the proxy address.
        address proxyAddress = IGoaldProxy(_proxyAddress).getProxyAddress();
        if (_proxyAddress != proxyAddress) {
            _proxyAddress = proxyAddress;
        }

        // We use the mask because it's possible the calldata has dirty bits.
        _payFee(amount & (FEE_MASK >> FEE_SHIFT), deadline);
    }

    /// Internal Functions ///

    /** Pay the goald fee. Overpayments extend the next fee deadline, but do not change the finalization date. */
    function _payFee(uint256 amount, uint256 deadline) private {
        // Reentrancy guard.
        require(_status == RE_NOT_ENTERED);
        _status = RE_ENTERED;

        // So we can extract the packed values without unnecessary SLOADs.
        uint256 values = _values;

        // No more payments necessary.
        uint256 remainingIntervals = (values & REMAINING_INTERVALS_MASK) >> REMAINING_INTERVALS_SHIFT;
        require(remainingIntervals > 0 && /* not finalized */ ((values & FINALIZED_MASK) >> FINALIZED_SHIFT) == 0, "All payments made or finalized");

        // There is a small penalty if this is a late payment. This is per-payment, and is 0.25% of the fee, not the amount paid. This
        // allows for multiple late payments in a single transaction with only a single fee while encouraging good standing.
        uint256 fee = (values & FEE_MASK) >> FEE_SHIFT;
        uint256 fine;
        if (block.timestamp > _nextFeeTimestamp) {
            // We're taking it out of the amount since we can't guarantee the owner has sent enough to cover their intention and the
            // fine. It is their responsiblity to understand the requirements of the goald to maintain good standing.
            fine = fee / LATE_PAYMENT_FINE_DIVISOR;
            amount -= fine;

            // We take the double hit here in terms of deployment cost, but it saves on execution.
            require(amount > 0, "AMNT: Can't pay fine");
        } else {
            // Validate the fee.
            require(amount > 0, "AMNT: Can't be zero");
        }

        uint256[] memory amounts;
        { // Scoped to prevent stack too deep error.
            // Transfer from the user.
            IERC20 paymentContract = IERC20(_paymentAddress);
            paymentContract.transferFrom(msg.sender, address(this), amount);
            if (fine > 0) {
                paymentContract.transferFrom(msg.sender, _proxyAddress, fine);
            }

            // We'll be verifying uniswap did what it said it did.
            IERC20 collateralContract = IERC20(_collateralAddress);
            uint256 currentCollateralBalance = collateralContract.balanceOf(address(this));

            // Set the router's allowance to cover the trade. We are only authorizing enough for the collateral amount.
            address uniswapRouterAddress = IGoaldProxy(_proxyAddress).getUniswapRouterAddress();
            paymentContract.approve(uniswapRouterAddress, amount);
            
            // Try and transfer from the user to the goald. We use Uniswap to get the collateral token. We don't care how much of the
            // collateral was returned, so long as it is greater than zero.
            amounts = IUniswapV2Router02(uniswapRouterAddress).swapExactTokensForTokens(amount, 1, _uniPath, address(this), deadline);

            // Double check the balance. Amounts is the same length as `_uniPath`: 3.
            require(currentCollateralBalance + amounts[2] > currentCollateralBalance, "UNI: Overflow error");
            require(collateralContract.balanceOf(address(this)) == currentCollateralBalance + amounts[1], "UNI: Wrong balance");

            // Reset the router's allowance to zero. This will also refund some gas. This is more expensive than keeping it nonzero,
            // but we cannot allow unauthorized transfers. Even though one token can be considered miniscule (e.g., for WETH, which has 18
            // decimal places), we still must be diligent.
            paymentContract.approve(uniswapRouterAddress, 0);

            // Return any remainder to the sender. There is a possible vulnerability here if someone other than the sender transfers
            // tokens externally; in that case whoever is next to call `payFee()` will get the full sum of tokens. If we didn't do this,
            // then we'd have to make a determination of where those extra tokens should go. The owner of the goald has no claim to them,
            // unless they were the ones who transferred them (in which case, they are likely to be the ones to receive them here), and we
            // won't know who transferred them in the first place. We also can't hold onto them, since that permanently loses them. This
            // will incentivize proper use of the goald contract. Rule #1: know who you're sending things to.
            uint256 remainder = paymentContract.balanceOf(address(this));
            if (remainder > 0) {
                paymentContract.transfer(msg.sender, remainder);
            }
        }

        { // Scoped to prevent stack too deep error.
            // Update their balance.
            uint256 currentFeeBalance = (values & CURRENT_FEE_BALANCE_MASK) >> CURRENT_FEE_BALANCE_SHIFT;
            require(currentFeeBalance + amount > currentFeeBalance, "CFB: Overflow error");
            currentFeeBalance += amount;

            // Check to see if we've paid enough to cover at least one interval period.
            uint256 feeIntervalDays = (values & FEE_INTERVAL_DAYS_MASK) >> FEE_INTERVAL_DAYS_SHIFT;
            if (currentFeeBalance >= fee) {
                // This will floor to at last 1 period so we also don't need to worry about underflowing.
                uint256 periods = currentFeeBalance / fee;
                currentFeeBalance -= (periods * fee);

                // Make sure we don't underflow the remaining intervals.
                if (periods > remainingIntervals) {
                    periods = remainingIntervals;
                }
                remainingIntervals -= periods;

                // The goald still needs more payments. We allow for early payments, so we extend the deadline by the amount of periods
                // paid for.
                if (remainingIntervals > 0) {
                    require(_nextFeeTimestamp + (periods * feeIntervalDays * 1 days) > _nextFeeTimestamp, "NFT: Overflow error");
                    _nextFeeTimestamp += periods * feeIntervalDays * 1 days;
                }
            }

            // Update the packed values.
            _values =
                // Preserve everything except for the values we're replacing.
                (values & ~(REMAINING_INTERVALS_MASK | CURRENT_FEE_BALANCE_MASK))

                // Slot in the new values. We need to mask them since we're reducing to something smaller than uint256.
                | ((remainingIntervals << REMAINING_INTERVALS_SHIFT) & REMAINING_INTERVALS_MASK)
                | ((currentFeeBalance  << CURRENT_FEE_BALANCE_SHIFT) & CURRENT_FEE_BALANCE_MASK);
        }

        // Hello world!
        emit FeePaid(amount, amounts[0], amounts[2]);

        // By storing the original amount once again, a refund is triggered (see https://eips.ethereum.org/EIPS/eip-2200).
        _status = RE_NOT_ENTERED;
    }

    /// Overridden Functions ///

    /**
     * @dev See {IERC721-safeTransferFrom}. We override so we can add a check to make sure it hasn't been finalized.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override {
        require((_values & FINALIZED_MASK) >> FINALIZED_SHIFT == 0, "Already finalized");
        super.safeTransferFrom(from, to, tokenId, _data);
    }

    /**
     * @dev See {IERC721-transferFrom}. We override so we can add a check to make sure it hasn't been finalized.
     */
    function transferFrom(address from, address to, uint256 tokenId) public override {
        require((_values & FINALIZED_MASK) >> FINALIZED_SHIFT == 0, "Already finalized");
        super.transferFrom(from, to, tokenId);
    }
}
function payFeeLateOnly(uint256 deadline) external {
    require(block.timestamp >= _nextFeeTimestamp, "Not late");

    // Update the proxy address.
    address proxyAddress = IGoaldProxy(_proxyAddress).getProxyAddress();
    if (_proxyAddress != proxyAddress) {
        _proxyAddress = proxyAddress;
    }

    _payFee((_values & FEE_MASK) >> FEE_SHIFT, deadline);
}

/** An endpoint for an exteral autopay service. This can only be called if the goald is past due. */