library WithdrawalLib {
    using SafeMathLib256 for uint256;
    using BimodalLib for BimodalLib.Ledger;
    // EVENTS
    event WithdrawalRequest(
        address indexed token,
        address indexed requestor,
        uint256 amount
    );

    event WithdrawalConfirmation(
        address indexed token,
        address indexed requestor,
        uint256 amount
    );

    function initWithdrawal(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address holder,
        uint256 eon,
        uint256 withdrawalAmount
    ) private /* onlyWhenContractUnpunished() */
    {
        BimodalLib.Wallet storage entry = ledger.walletBook[token][holder];

        uint256 balance = 0;
        if (token != address(this)) {
            require(ledger.tokenToTrail[token] != 0, "t");
            balance = token.balanceOf(this);
        } else {
            balance = address(this).balance;
        }

        require(
            ledger.getPendingWithdrawalsAtEon(token, eon).add(
                withdrawalAmount
            ) <= balance,
            "b"
        );

        entry.withdrawals.push(BimodalLib.Withdrawal(eon, withdrawalAmount));

        ledger.addToRunningPendingWithdrawals(token, eon, withdrawalAmount);

        ledger.appendOperationToEonAccumulator(
            eon,
            token,
            holder,
            BimodalLib.Operation.WITHDRAWAL,
            withdrawalAmount
        );

        emit WithdrawalRequest(token, holder, withdrawalAmount);
    }

    /**
     * This method can be called freely by a client to initiate a withdrawal in the
     * parent-chain that will take 2 eons to be confirmable only if the client can
     * provide a satisfying proof of exclusive allotment.
     */
    function requestWithdrawal(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        bytes32[2] checksums,
        uint64 trail,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2][2] lrPassiveMark, // Left, Right
        uint256 withdrawalAmount
    ) public /* payable */
    /* onlyWithConstantReimbursement(100100) */
    {
        uint256 available = lrPassiveMark[0][1].sub(lrPassiveMark[0][0]);
        uint256 eon = ledger.currentEon();

        uint256 pending = ledger.getWalletPendingWithdrawalAmountAtEon(
            token,
            msg.sender,
            eon
        );
        require(available >= withdrawalAmount.add(pending), "b");

        require(
            ChallengeLib.verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                token,
                msg.sender,
                checksums,
                trail,
                [eon.sub(1), lrPassiveMark[1][0], lrPassiveMark[1][1]],
                allotmentChain,
                membershipChain,
                values,
                lrPassiveMark[0]
            ),
            "p"
        );

        initWithdrawal(ledger, token, msg.sender, eon, withdrawalAmount);
    }

    /**
     * This method can be called by a client to initiate a withdrawal in the
     * parent-chain that will take 2 eons to be confirmable only if the client
     * provides a signature from the operator authorizing the initialization of this
     * withdrawal.
     */
    function requestAuthorizedWithdrawal(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        uint256 withdrawalAmount,
        uint256 expiry,
        bytes32 r,
        bytes32 s,
        uint8 v
    ) public {
        requestDelegatedWithdrawal(
            ledger,
            token,
            msg.sender,
            withdrawalAmount,
            expiry,
            r,
            s,
            v
        );
    }

    /**
     * This method can be called by the operator to initiate a withdrawal in the
     * parent-chain that will take 2 eons to be confirmable only if the operator
     * provides a signature from the client authorizing the delegation of this
     * withdrawal initialization.
     */
    function requestDelegatedWithdrawal(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address holder,
        uint256 withdrawalAmount,
        uint256 expiry,
        bytes32 r,
        bytes32 s,
        uint8 v
    ) public /* onlyOperator() */
    {
        require(block.number <= expiry);

        uint256 eon = ledger.currentEon();
        uint256 pending = ledger.getWalletPendingWithdrawalAmountAtEon(
            token,
            holder,
            eon
        );

        require(
            ChallengeLib.verifyWithdrawalAuthorization(
                token,
                holder,
                expiry,
                withdrawalAmount.add(pending),
                holder,
                r,
                s,
                v
            )
        );

        initWithdrawal(ledger, token, holder, eon, withdrawalAmount);
    }

    /**
     * This method can be called to confirm the withdrawals of a recipient that have
     * been pending for at least two eons when the operator is not halted.
     */
    function confirmWithdrawal(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address recipient
    )
        public
        returns (
            /* onlyWhenContractUnpunished() */
            uint256 amount
        )
    {
        BimodalLib.Wallet storage entry = ledger.walletBook[token][recipient];
        BimodalLib.Withdrawal[] storage withdrawals = entry.withdrawals;

        uint256 eon = ledger.currentEon();
        amount = 0;

        /**
         * after the loop, i is set to the index of the first pending withdrawals
         * amount is the amount to be sent to the recipient
         */
        uint32 i = 0;
        for (i = 0; i < withdrawals.length; i++) {
            BimodalLib.Withdrawal storage withdrawal = withdrawals[i];
            if (withdrawal.eon.add(1) >= eon) {
                break;
            } else if (
                withdrawal.eon.add(2) == eon &&
                ledger.currentEra() < ledger.EXTENDED_BLOCKS_PER_EPOCH
            ) {
                break;
            }

            amount = amount.add(withdrawal.amount);
        }

        // set withdrawals to contain only pending withdrawal requests
        for (uint32 j = 0; j < i && i < withdrawals.length; j++) {
            withdrawals[j] = withdrawals[i];
            i++;
        }
        withdrawals.length = withdrawals.length.sub(j);

        ledger.deductFromRunningPendingWithdrawals(token, eon, eon, amount);

        BimodalLib.AmountAggregate storage eonWithdrawals = ledger
            .confirmedWithdrawals[token][eon.mod(ledger.EONS_KEPT)];
        BimodalLib.addToAggregate(eonWithdrawals, eon, amount);

        emit WithdrawalConfirmation(token, recipient, amount);

        // if token is not chain native asset
        if (token != address(this)) {
            require(ledger.tokenToTrail[token] != 0);
            require(token.transfer(recipient, amount));
        } else {
            recipient.transfer(amount);
        }
    }
}
/*
 This library contains the implementations of the first NOCUST withdrawal
 procedures.
 */
function confirmWithdrawal(
    BimodalLib.Ledger storage ledger,
    ERC20 token,
    address recipient
)
    public
    returns (
        /* onlyWhenContractUnpunished() */
        uint256 amount
    )
{
    BimodalLib.Wallet storage entry = ledger.walletBook[token][recipient];
    BimodalLib.Withdrawal[] storage withdrawals = entry.withdrawals;

    uint256 eon = ledger.currentEon();
    amount = 0;

    /**
     * after the loop, i is set to the index of the first pending withdrawals
     * amount is the amount to be sent to the recipient
     */
    uint32 i = 0;
    for (i = 0; i < withdrawals.length; i++) {
        BimodalLib.Withdrawal storage withdrawal = withdrawals[i];
        if (withdrawal.eon.add(1) >= eon) {
            break;
        } else if (
            withdrawal.eon.add(2) == eon &&
            ledger.currentEra() < ledger.EXTENDED_BLOCKS_PER_EPOCH
        ) {
            break;
        }

        amount = amount.add(withdrawal.amount);
    }

    // set withdrawals to contain only pending withdrawal requests
    for (uint32 j = 0; j < i && i < withdrawals.length; j++) {
        withdrawals[j] = withdrawals[i];
        i++;
    }
    withdrawals.length = withdrawals.length.sub(j);

    ledger.deductFromRunningPendingWithdrawals(token, eon, eon, amount);

    BimodalLib.AmountAggregate storage eonWithdrawals = ledger
        .confirmedWithdrawals[token][eon.mod(ledger.EONS_KEPT)];
    BimodalLib.addToAggregate(eonWithdrawals, eon, amount);

    emit WithdrawalConfirmation(token, recipient, amount);

    // if token is not chain native asset
    if (token != address(this)) {
        require(ledger.tokenToTrail[token] != 0);
        require(token.transfer(recipient, amount));
    } else {
        recipient.transfer(amount);
    }
}

/**
 * This method can be called to confirm the withdrawals of a recipient that have
 * been pending for at least two eons when the operator is not halted.
 */