library ChallengeLib {
    using SafeMathLib256 for uint256;
    using SafeMathLib32 for uint32;
    using BimodalLib for BimodalLib.Ledger;
    // EVENTS
    event ChallengeIssued(
        address indexed token,
        address indexed recipient,
        address indexed sender
    );

    event StateUpdate(
        address indexed token,
        address indexed account,
        uint256 indexed eon,
        uint64 trail,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2][3] lrDeltasPassiveMark,
        bytes32 activeStateChecksum,
        bytes32 passiveChecksum,
        bytes32 r,
        bytes32 s,
        uint8 v
    );

    // Validation
    function verifyProofOfExclusiveAccountBalanceAllotment(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address holder,
        bytes32[2] activeStateChecksum_passiveTransfersRoot, // solhint-disable func-param-name-mixedcase
        uint64 trail,
        uint256[3] eonPassiveMark,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2] LR // solhint-disable func-param-name-mixedcase
    ) public view returns (bool) {
        BimodalLib.Checkpoint memory checkpoint = ledger
            .checkpoints[eonPassiveMark[0].mod(ledger.EONS_KEPT)];
        require(eonPassiveMark[0] == checkpoint.eonNumber, "r");

        // activeStateChecksum is set to the account node.
        activeStateChecksum_passiveTransfersRoot[0] = keccak256(
            abi.encodePacked(
                keccak256(abi.encodePacked(address(this))),
                keccak256(abi.encodePacked(token)),
                keccak256(abi.encodePacked(holder)),
                keccak256(
                    abi.encodePacked(
                        activeStateChecksum_passiveTransfersRoot[1], // passiveTransfersRoot
                        eonPassiveMark[1],
                        eonPassiveMark[2]
                    )
                ),
                activeStateChecksum_passiveTransfersRoot[0] // activeStateChecksum
            )
        );
        // the interval allotment is set to form the leaf
        activeStateChecksum_passiveTransfersRoot[0] = keccak256(
            abi.encodePacked(
                LR[0],
                activeStateChecksum_passiveTransfersRoot[0],
                LR[1]
            )
        );

        // This calls the merkle verification procedure, which returns the
        // checkpoint allotment size
        uint64 tokenTrail = ledger.tokenToTrail[token];
        LR[0] = MerkleVerifier.verifyProofOfExclusiveBalanceAllotment(
            trail,
            tokenTrail,
            activeStateChecksum_passiveTransfersRoot[0],
            checkpoint.merkleRoot,
            allotmentChain,
            membershipChain,
            values,
            LR
        );

        // The previous allotment size of the target eon is reconstructed from the
        // deposits and withdrawals performed so far and the current balance.
        LR[1] = address(this).balance;

        if (token != address(this)) {
            require(tokenTrail != 0, "t");
            LR[1] = token.balanceOf(this);
        }

        // Credit back confirmed withdrawals that were performed since target eon
        for (tokenTrail = 0; tokenTrail < ledger.EONS_KEPT; tokenTrail++) {
            if (
                ledger.confirmedWithdrawals[token][tokenTrail].eon >=
                eonPassiveMark[0]
            ) {
                LR[1] = LR[1].add(
                    ledger.confirmedWithdrawals[token][tokenTrail].amount
                );
            }
        }
        // Debit deposits performed since target eon
        for (tokenTrail = 0; tokenTrail < ledger.EONS_KEPT; tokenTrail++) {
            if (ledger.deposits[token][tokenTrail].eon >= eonPassiveMark[0]) {
                LR[1] = LR[1].sub(ledger.deposits[token][tokenTrail].amount);
            }
        }
        // Debit withdrawals pending since prior eon
        LR[1] = LR[1].sub(
            ledger.getPendingWithdrawalsAtEon(token, eonPassiveMark[0].sub(1))
        );
        // Require that the reconstructed allotment matches the proof allotment
        require(LR[0] <= LR[1], "b");

        return true;
    }

    function verifyProofOfActiveStateUpdateAgreement(
        ERC20 token,
        address holder,
        uint64 trail,
        uint256 eon,
        bytes32 txSetRoot,
        uint256[2] deltas,
        address attester,
        bytes32 r,
        bytes32 s,
        uint8 v
    ) public view returns (bytes32 checksum) {
        checksum = MerkleVerifier.activeStateUpdateChecksum(
            token,
            holder,
            trail,
            eon,
            txSetRoot,
            deltas
        );
        require(
            attester == BimodalLib.signedMessageECRECOVER(checksum, r, s, v),
            "A"
        );
    }

    function verifyWithdrawalAuthorization(
        ERC20 token,
        address holder,
        uint256 expiry,
        uint256 amount,
        address attester,
        bytes32 r,
        bytes32 s,
        uint8 v
    ) public view returns (bool) {
        bytes32 checksum = keccak256(
            abi.encodePacked(
                keccak256(abi.encodePacked(address(this))),
                keccak256(abi.encodePacked(token)),
                keccak256(abi.encodePacked(holder)),
                expiry,
                amount
            )
        );
        require(
            attester == BimodalLib.signedMessageECRECOVER(checksum, r, s, v),
            "a"
        );
        return true;
    }

    // Challenge Lifecycle Methods
    /**
     * This method increments the live challenge counter and emits and event
     * containing the challenge index.
     */
    function markChallengeLive(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address recipient,
        address sender
    ) private {
        require(ledger.currentEra() > ledger.BLOCKS_PER_EPOCH);

        uint256 eon = ledger.currentEon();
        BimodalLib.Checkpoint storage checkpoint = ledger.getOrCreateCheckpoint(
            eon,
            eon
        );
        checkpoint.liveChallenges = checkpoint.liveChallenges.add(1);
        emit ChallengeIssued(token, recipient, sender);
    }

    /**
     * This method clears all the data in a Challenge structure and decrements the
     * live challenge counter.
     */
    function clearChallenge(
        BimodalLib.Ledger storage ledger,
        BimodalLib.Challenge storage challenge
    ) private {
        BimodalLib.Checkpoint storage checkpoint = ledger.getOrCreateCheckpoint(
            challenge.initialStateEon.add(1),
            ledger.currentEon()
        );
        checkpoint.liveChallenges = checkpoint.liveChallenges.sub(1);

        challenge.challengeType = BimodalLib.ChallengeType.NONE;
        challenge.block = 0;
        // challenge.initialStateEon = 0;
        challenge.initialStateBalance = 0;
        challenge.deltaHighestSpendings = 0;
        challenge.deltaHighestGains = 0;
        challenge.finalStateBalance = 0;
        challenge.deliveredTxNonce = 0;
        challenge.trailIdentifier = 0;
    }

    /**
     * This method marks a challenge as having been successfully answered only if
     * the response was provided in time.
     */
    function markChallengeAnswered(
        BimodalLib.Ledger storage ledger,
        BimodalLib.Challenge storage challenge
    ) private {
        uint256 eon = ledger.currentEon();

        require(
            challenge.challengeType != BimodalLib.ChallengeType.NONE &&
                block.number.sub(challenge.block) < ledger.BLOCKS_PER_EPOCH &&
                (challenge.initialStateEon == eon.sub(1) ||
                    (challenge.initialStateEon == eon.sub(2) &&
                        ledger.currentEra() < ledger.BLOCKS_PER_EPOCH))
        );

        clearChallenge(ledger, challenge);
    }

    // ========================================================================
    // ========================================================================
    // ========================================================================
    // ====================================  STATE UPDATE Challenge
    // ========================================================================
    // ========================================================================
    // ========================================================================
    /**
     * This method initiates the fields of the Challenge struct to hold a state
     * update challenge.
     */
    function initStateUpdateChallenge(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        uint256 owed,
        uint256[2] spentGained,
        uint64 trail
    ) private {
        BimodalLib.Challenge storage challengeEntry = ledger
            .challengeBook[token][msg.sender][msg.sender];
        require(challengeEntry.challengeType == BimodalLib.ChallengeType.NONE);
        require(challengeEntry.initialStateEon < ledger.currentEon().sub(1));

        challengeEntry.initialStateEon = ledger.currentEon().sub(1);
        challengeEntry.initialStateBalance = owed;
        challengeEntry.deltaHighestSpendings = spentGained[0];
        challengeEntry.deltaHighestGains = spentGained[1];
        challengeEntry.trailIdentifier = trail;

        challengeEntry.challengeType = BimodalLib.ChallengeType.STATE_UPDATE;
        challengeEntry.block = block.number;

        markChallengeLive(ledger, token, msg.sender, msg.sender);
    }

    /**
     * This method checks that the updated balance is at least as much as the
     * expected balance.
     */
    function checkStateUpdateBalance(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address issuer,
        BimodalLib.Challenge storage challenge,
        uint256[2] LR, // solhint-disable func-param-name-mixedcase
        uint256[2] spentGained,
        uint256 passivelyReceived
    ) private view {
        (uint256 deposits, uint256 withdrawals) = ledger
            .getCurrentEonDepositsWithdrawals(token, issuer);
        uint256 incoming = spentGained[1] // actively received in commit chain
            .add(deposits)
            .add(passivelyReceived);
        uint256 outgoing = spentGained[0] // actively spent in commit chain
            .add(withdrawals);
        // This verification is modified to permit underflow of expected balance
        // since a client can choose to zero the `challenge.initialStateBalance`
        require(
            challenge.initialStateBalance.add(incoming) <=
                LR[1]
                    .sub(LR[0]) // final balance allotment
                    .add(outgoing),
            "B"
        );
    }

    function challengeStateUpdateWithProofOfExclusiveBalanceAllotment(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        bytes32[2] checksums,
        uint64 trail,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] value,
        uint256[2][3] lrDeltasPassiveMark,
        bytes32[3] rsTxSetRoot,
        uint8 v
    ) public /* payable */
    /* onlyWithFairReimbursement(ledger) */
    {
        uint256 previousEon = ledger.currentEon().sub(1);
        address operator = ledger.operator;

        // The hub must have committed to this state update
        if (lrDeltasPassiveMark[1][0] != 0 || lrDeltasPassiveMark[1][1] != 0) {
            verifyProofOfActiveStateUpdateAgreement(
                token,
                msg.sender,
                trail,
                previousEon,
                rsTxSetRoot[2],
                lrDeltasPassiveMark[1],
                operator,
                rsTxSetRoot[0],
                rsTxSetRoot[1],
                v
            );
        }

        initStateUpdateChallenge(
            ledger,
            token,
            lrDeltasPassiveMark[0][1].sub(lrDeltasPassiveMark[0][0]),
            lrDeltasPassiveMark[1],
            trail
        );

        // The initial state must have been ratified in the commitment
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                token,
                msg.sender,
                checksums,
                trail,
                [
                    previousEon,
                    lrDeltasPassiveMark[2][0],
                    lrDeltasPassiveMark[2][1]
                ],
                allotmentChain,
                membershipChain,
                value,
                lrDeltasPassiveMark[0]
            )
        );
    }

    function challengeStateUpdateWithProofOfActiveStateUpdateAgreement(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        bytes32 txSetRoot,
        uint64 trail,
        uint256[2] deltas,
        bytes32 r,
        bytes32 s,
        uint8 v
    ) public /* payable */
    /* TODO calculate exact addition */
    /* onlyWithSkewedReimbursement(ledger, 25) */
    {
        // The hub must have committed to this transition
        verifyProofOfActiveStateUpdateAgreement(
            token,
            msg.sender,
            trail,
            ledger.currentEon().sub(1),
            txSetRoot,
            deltas,
            ledger.operator,
            r,
            s,
            v
        );

        initStateUpdateChallenge(ledger, token, 0, deltas, trail);
    }

    function answerStateUpdateChallenge(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address issuer,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2][3] lrDeltasPassiveMark, // [ [L, R], Deltas ]
        bytes32[6] rSrStxSetRootChecksum,
        uint8[2] v
    ) public {
        BimodalLib.Challenge storage challenge = ledger
            .challengeBook[token][issuer][issuer];
        require(
            challenge.challengeType == BimodalLib.ChallengeType.STATE_UPDATE
        );

        // Transition must have been approved by issuer
        if (lrDeltasPassiveMark[1][0] != 0 || lrDeltasPassiveMark[1][1] != 0) {
            rSrStxSetRootChecksum[0] = verifyProofOfActiveStateUpdateAgreement(
                token,
                issuer,
                challenge.trailIdentifier,
                challenge.initialStateEon,
                rSrStxSetRootChecksum[4], // txSetRoot
                lrDeltasPassiveMark[1], // deltas
                issuer,
                rSrStxSetRootChecksum[0], // R[0]
                rSrStxSetRootChecksum[1], // S[0]
                v[0]
            );
            address operator = ledger.operator;
            rSrStxSetRootChecksum[1] = verifyProofOfActiveStateUpdateAgreement(
                token,
                issuer,
                challenge.trailIdentifier,
                challenge.initialStateEon,
                rSrStxSetRootChecksum[4], // txSetRoot
                lrDeltasPassiveMark[1], // deltas
                operator,
                rSrStxSetRootChecksum[2], // R[1]
                rSrStxSetRootChecksum[3], // S[1]
                v[1]
            );
            require(rSrStxSetRootChecksum[0] == rSrStxSetRootChecksum[1], "u");
        } else {
            rSrStxSetRootChecksum[0] = bytes32(0);
        }

        // Transition has to be at least as recent as submitted one
        require(
            lrDeltasPassiveMark[1][0] >= challenge.deltaHighestSpendings &&
                lrDeltasPassiveMark[1][1] >= challenge.deltaHighestGains,
            "x"
        );

        // Transition has to have been properly applied
        checkStateUpdateBalance(
            ledger,
            token,
            issuer,
            challenge,
            lrDeltasPassiveMark[0], // LR
            lrDeltasPassiveMark[1], // deltas
            lrDeltasPassiveMark[2][0]
        ); // passive amount

        // Truffle crashes when trying to interpret this event in some cases.
        emit StateUpdate(
            token,
            issuer,
            challenge.initialStateEon.add(1),
            challenge.trailIdentifier,
            allotmentChain,
            membershipChain,
            values,
            lrDeltasPassiveMark,
            rSrStxSetRootChecksum[0], // activeStateChecksum
            rSrStxSetRootChecksum[5], // passiveAcceptChecksum
            rSrStxSetRootChecksum[2], // R[1]
            rSrStxSetRootChecksum[3], // S[1]
            v[1]
        );

        // Proof of stake must be ratified in the checkpoint
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                token,
                issuer,
                [rSrStxSetRootChecksum[0], rSrStxSetRootChecksum[5]], // activeStateChecksum, passiveAcceptChecksum
                challenge.trailIdentifier,
                [
                    challenge.initialStateEon.add(1), // eonNumber
                    lrDeltasPassiveMark[2][0], // passiveAmount
                    lrDeltasPassiveMark[2][1]
                ],
                allotmentChain,
                membershipChain,
                values,
                lrDeltasPassiveMark[0]
            ), // LR
            "c"
        );

        markChallengeAnswered(ledger, challenge);
    }

    // ========================================================================
    // ========================================================================
    // ========================================================================
    // ====================================  ACTIVE DELIVERY Challenge
    // ========================================================================
    // ========================================================================
    // ========================================================================
    function initTransferDeliveryChallenge(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address sender,
        address recipient,
        uint256 amount,
        uint256 txNonce,
        uint64 trail
    ) private {
        BimodalLib.Challenge storage challenge = ledger
            .challengeBook[token][recipient][sender];
        require(challenge.challengeType == BimodalLib.ChallengeType.NONE);
        require(challenge.initialStateEon < ledger.currentEon().sub(1));

        challenge.challengeType = BimodalLib.ChallengeType.TRANSFER_DELIVERY;
        challenge.initialStateEon = ledger.currentEon().sub(1);
        challenge.deliveredTxNonce = txNonce;
        challenge.block = block.number;
        challenge.trailIdentifier = trail;
        challenge.finalStateBalance = amount;

        markChallengeLive(ledger, token, recipient, sender);
    }

    function challengeTransferDeliveryWithProofOfActiveStateUpdateAgreement(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address[2] SR, // solhint-disable func-param-name-mixedcase
        uint256[2] nonceAmount,
        uint64[3] trails,
        bytes32[] chain,
        uint256[2] deltas,
        bytes32[3] rsTxSetRoot,
        uint8 v
    ) public /* payable */
    /* onlyWithFairReimbursement() */
    {
        require(msg.sender == SR[0] || msg.sender == SR[1], "d");

        // Require hub to have committed to transition
        verifyProofOfActiveStateUpdateAgreement(
            token,
            SR[0],
            trails[0],
            ledger.currentEon().sub(1),
            rsTxSetRoot[2],
            deltas,
            ledger.operator,
            rsTxSetRoot[0],
            rsTxSetRoot[1],
            v
        );

        rsTxSetRoot[0] = MerkleVerifier.transferChecksum(
            SR[1],
            nonceAmount[1], // amount
            trails[2],
            nonceAmount[0]
        ); // nonce

        // Require tx to exist in transition
        require(
            MerkleVerifier.verifyProofOfMembership(
                trails[1],
                chain,
                rsTxSetRoot[0], // transferChecksum
                rsTxSetRoot[2]
            ), // txSetRoot
            "e"
        );

        initTransferDeliveryChallenge(
            ledger,
            token,
            SR[0], // senderAddress
            SR[1], // recipientAddress
            nonceAmount[1], // amount
            nonceAmount[0], // nonce
            trails[2]
        ); // recipientTrail
    }

    function answerTransferDeliveryChallengeWithProofOfActiveStateUpdateAgreement(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address[2] SR, // solhint-disable func-param-name-mixedcase
        uint64 transferMembershipTrail,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2][3] lrDeltasPassiveMark,
        bytes32[2] txSetRootChecksum,
        bytes32[] txChain
    ) public {
        BimodalLib.Challenge storage challenge = ledger
            .challengeBook[token][SR[1]][SR[0]];
        require(
            challenge.challengeType ==
                BimodalLib.ChallengeType.TRANSFER_DELIVERY
        );

        // Assert that the challenged transaction belongs to the transfer set
        require(
            MerkleVerifier.verifyProofOfMembership(
                transferMembershipTrail,
                txChain,
                MerkleVerifier.transferChecksum(
                    SR[0],
                    challenge.finalStateBalance, // amount
                    challenge.trailIdentifier, // recipient trail
                    challenge.deliveredTxNonce
                ),
                txSetRootChecksum[0]
            )
        ); // txSetRoot

        // Require committed transition to include transfer
        txSetRootChecksum[0] = MerkleVerifier.activeStateUpdateChecksum(
            token,
            SR[1],
            challenge.trailIdentifier,
            challenge.initialStateEon,
            txSetRootChecksum[0], // txSetRoot
            lrDeltasPassiveMark[1]
        ); // Deltas

        // Assert that this transition was used to update the recipient's stake
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                token,
                SR[1], // recipient
                txSetRootChecksum, // [activeStateChecksum, passiveChecksum]
                challenge.trailIdentifier,
                [
                    challenge.initialStateEon.add(1), // eonNumber
                    lrDeltasPassiveMark[2][0], // passiveAmount
                    lrDeltasPassiveMark[2][1] // passiveMark
                ],
                allotmentChain,
                membershipChain,
                values,
                lrDeltasPassiveMark[0]
            )
        ); // LR

        markChallengeAnswered(ledger, challenge);
    }

    // ========================================================================
    // ========================================================================
    // ========================================================================
    // ====================================  PASSIVE DELIVERY Challenge
    // ========================================================================
    // ========================================================================
    // ========================================================================
    function challengeTransferDeliveryWithProofOfPassiveStateUpdate(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address[2] SR, // solhint-disable func-param-name-mixedcase
        bytes32[2] txSetRootChecksum,
        uint64[3] senderTransferRecipientTrails,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2][4] lrDeltasPassiveMarkDummyAmount,
        bytes32[] transferMembershipChain
    ) public /* payable */
    /* onlyWithFairReimbursement() */
    {
        require(msg.sender == SR[0] || msg.sender == SR[1], "d");
        lrDeltasPassiveMarkDummyAmount[3][0] = ledger.currentEon().sub(1); // previousEon

        // Assert that the challenged transaction ends the transfer set
        require(
            MerkleVerifier.verifyProofOfMembership(
                senderTransferRecipientTrails[1], // transferMembershipTrail
                transferMembershipChain,
                MerkleVerifier.transferChecksum(
                    SR[1], // recipientAddress
                    lrDeltasPassiveMarkDummyAmount[3][1], // amount
                    senderTransferRecipientTrails[2], // recipientTrail
                    2**256 - 1
                ), // nonce
                txSetRootChecksum[0]
            ), // txSetRoot
            "e"
        );

        // Require committed transition to include transfer
        txSetRootChecksum[0] = MerkleVerifier.activeStateUpdateChecksum(
            token,
            SR[0], // senderAddress
            senderTransferRecipientTrails[0], // senderTrail
            lrDeltasPassiveMarkDummyAmount[3][0], // previousEon
            txSetRootChecksum[0], // txSetRoot
            lrDeltasPassiveMarkDummyAmount[1]
        ); // Deltas

        // Assert that this transition was used to update the sender's stake
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                token,
                SR[0], // senderAddress
                txSetRootChecksum, // [activeStateChecksum, passiveChecksum]
                senderTransferRecipientTrails[0], // senderTrail
                [
                    lrDeltasPassiveMarkDummyAmount[3][0].add(1), // eonNumber
                    lrDeltasPassiveMarkDummyAmount[2][0], // passiveAmount
                    lrDeltasPassiveMarkDummyAmount[2][1] // passiveMark
                ],
                allotmentChain,
                membershipChain,
                values,
                lrDeltasPassiveMarkDummyAmount[0]
            )
        ); // LR

        initTransferDeliveryChallenge(
            ledger,
            token,
            SR[0], // sender
            SR[1], // recipient
            lrDeltasPassiveMarkDummyAmount[3][1], // amount
            uint256(
                keccak256(
                    abi.encodePacked(
                        lrDeltasPassiveMarkDummyAmount[2][1],
                        uint256(2**256 - 1)
                    )
                )
            ), // mark (nonce)
            senderTransferRecipientTrails[2]
        ); // recipientTrail
    }

    function answerTransferDeliveryChallengeWithProofOfPassiveStateUpdate(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address[2] SR, // solhint-disable func-param-name-mixedcase
        uint64 transferMembershipTrail,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        uint256[] values,
        uint256[2][3] lrPassiveMarkPositionNonce,
        bytes32[2] checksums,
        bytes32[] txChainValues
    ) public {
        BimodalLib.Challenge storage challenge = ledger
            .challengeBook[token][SR[1]][SR[0]];
        require(
            challenge.challengeType ==
                BimodalLib.ChallengeType.TRANSFER_DELIVERY
        );
        require(
            challenge.deliveredTxNonce ==
                uint256(
                    keccak256(
                        abi.encodePacked(
                            lrPassiveMarkPositionNonce[2][0],
                            lrPassiveMarkPositionNonce[2][1]
                        )
                    )
                )
        );

        // Assert that the challenged transaction belongs to the passively delivered set
        require(
            MerkleVerifier.verifyProofOfPassiveDelivery(
                transferMembershipTrail,
                MerkleVerifier.transferChecksum( // node
                    SR[0], // sender
                    challenge.finalStateBalance, // amount
                    challenge.trailIdentifier, // recipient trail
                    challenge.deliveredTxNonce
                ),
                checksums[1], // passiveChecksum
                txChainValues,
                [
                    lrPassiveMarkPositionNonce[2][0],
                    lrPassiveMarkPositionNonce[2][0].add(
                        challenge.finalStateBalance
                    )
                ]
            ) <= lrPassiveMarkPositionNonce[1][0]
        );

        // Assert that this transition was used to update the recipient's stake
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                token,
                SR[1], // recipient
                checksums, // [activeStateChecksum, passiveChecksum]
                challenge.trailIdentifier, // recipientTrail
                [
                    challenge.initialStateEon.add(1), // eonNumber
                    lrPassiveMarkPositionNonce[1][0], // passiveAmount
                    lrPassiveMarkPositionNonce[1][1] // passiveMark
                ],
                allotmentChain,
                membershipChain,
                values,
                lrPassiveMarkPositionNonce[0]
            )
        ); // LR

        markChallengeAnswered(ledger, challenge);
    }

    // ========================================================================
    // ========================================================================
    // ========================================================================
    // ====================================  SWAP Challenge
    // ========================================================================
    // ========================================================================
    // ========================================================================
    function initSwapEnactmentChallenge(
        BimodalLib.Ledger storage ledger,
        ERC20[2] tokens,
        uint256[4] updatedSpentGainedPassive,
        uint256[4] sellBuyBalanceNonce,
        uint64 recipientTrail
    ) private {
        ERC20 conduit = ERC20(
            address(keccak256(abi.encodePacked(tokens[0], tokens[1])))
        );
        BimodalLib.Challenge storage challenge = ledger
            .challengeBook[conduit][msg.sender][msg.sender];
        require(challenge.challengeType == BimodalLib.ChallengeType.NONE);
        require(challenge.initialStateEon < ledger.currentEon().sub(1));

        challenge.initialStateEon = ledger.currentEon().sub(1);
        challenge.deliveredTxNonce = sellBuyBalanceNonce[3];
        challenge.challengeType = BimodalLib.ChallengeType.SWAP_ENACTMENT;
        challenge.block = block.number;
        challenge.trailIdentifier = recipientTrail;
        challenge.deltaHighestSpendings = sellBuyBalanceNonce[0];
        challenge.deltaHighestGains = sellBuyBalanceNonce[1];

        (uint256 deposits, uint256 withdrawals) = ledger
            .getCurrentEonDepositsWithdrawals(tokens[0], msg.sender);

        challenge.initialStateBalance = sellBuyBalanceNonce[2] // allotment from eon e - 1
            .add(updatedSpentGainedPassive[2]) // gained
            .add(updatedSpentGainedPassive[3]) // passively delivered
            .add(deposits)
            .sub(updatedSpentGainedPassive[1]) // spent
            .sub(withdrawals);
        challenge.finalStateBalance = updatedSpentGainedPassive[0];

        require(
            challenge.finalStateBalance >= challenge.initialStateBalance,
            "d"
        );

        markChallengeLive(ledger, conduit, msg.sender, msg.sender);
    }

    function challengeSwapEnactmentWithProofOfActiveStateUpdateAgreement(
        BimodalLib.Ledger storage ledger,
        ERC20[2] tokens,
        uint64[3] senderTransferRecipientTrails, // senderTransferRecipientTrails
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        bytes32[] txChain,
        uint256[] values,
        uint256[2][3] lrDeltasPassiveMark,
        uint256[4] sellBuyBalanceNonce,
        bytes32[3] txSetRootChecksumDummy
    ) public /* payable */
    /* onlyWithFairReimbursement() */
    {
        // Require swap to exist in transition
        txSetRootChecksumDummy[2] = MerkleVerifier.swapOrderChecksum(
            tokens,
            senderTransferRecipientTrails[2],
            sellBuyBalanceNonce[0], // sell
            sellBuyBalanceNonce[1], // buy
            sellBuyBalanceNonce[2], // balance
            sellBuyBalanceNonce[3]
        ); // nonce

        require(
            MerkleVerifier.verifyProofOfMembership(
                senderTransferRecipientTrails[1],
                txChain,
                txSetRootChecksumDummy[2], // swapOrderChecksum
                txSetRootChecksumDummy[0]
            ), // txSetRoot
            "e"
        );

        uint256 previousEon = ledger.currentEon().sub(1);

        // Require committed transition to include swap
        txSetRootChecksumDummy[2] = MerkleVerifier.activeStateUpdateChecksum(
            tokens[0],
            msg.sender,
            senderTransferRecipientTrails[0],
            previousEon,
            txSetRootChecksumDummy[0],
            lrDeltasPassiveMark[1]
        ); // deltas

        uint256 updatedBalance = lrDeltasPassiveMark[0][1].sub(
            lrDeltasPassiveMark[0][0]
        );
        // The state must have been ratified in the commitment
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                tokens[0],
                msg.sender,
                [txSetRootChecksumDummy[2], txSetRootChecksumDummy[1]], // [activeStateChecksum, passiveChecksum]
                senderTransferRecipientTrails[0],
                [
                    previousEon.add(1), // eonNumber
                    lrDeltasPassiveMark[2][0], // passiveAmount
                    lrDeltasPassiveMark[2][1] // passiveMark
                ],
                allotmentChain,
                membershipChain,
                values,
                lrDeltasPassiveMark[0]
            )
        ); // LR

        initSwapEnactmentChallenge(
            ledger,
            tokens,
            [
                updatedBalance, // updated
                lrDeltasPassiveMark[1][0], // spent
                lrDeltasPassiveMark[1][1], // gained
                lrDeltasPassiveMark[2][0]
            ], // passiveAmount
            sellBuyBalanceNonce,
            senderTransferRecipientTrails[2]
        );
    }

    /**
     * This method just calculates the total expected balance.
     */
    function calculateSwapConsistencyBalance(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        uint256[2] deltas,
        uint256 passiveAmount,
        uint256 balance
    ) private view returns (uint256) {
        (uint256 deposits, uint256 withdrawals) = ledger
            .getCurrentEonDepositsWithdrawals(token, msg.sender);

        return
            balance
                .add(deltas[1]) // gained
                .add(passiveAmount) // passively delivered
                .add(deposits)
                .sub(withdrawals)
                .sub(deltas[0]); // spent
    }

    /**
     * This method calculates the balance expected to be credited in return for that
     * debited in another token according to the swapping price and is adjusted to
     * ignore numerical errors up to 2 decimal places.
     */
    function verifySwapConsistency(
        BimodalLib.Ledger storage ledger,
        ERC20[2] tokens,
        BimodalLib.Challenge challenge,
        uint256[2] LR, // solhint-disable func-param-name-mixedcase
        uint256[2] deltas,
        uint256 passiveAmount,
        uint256 balance
    ) private view returns (bool) {
        balance = calculateSwapConsistencyBalance(
            ledger,
            tokens[1],
            deltas,
            passiveAmount,
            balance
        );

        require(LR[1].sub(LR[0]) >= balance);

        uint256 taken = challenge
            .deltaHighestSpendings // sell amount
            .sub(
            challenge.finalStateBalance.sub(challenge.initialStateBalance)
        ); // refund
        uint256 given = LR[1]
            .sub(LR[0]) // recipient allotment
            .sub(balance); // authorized allotment

        return
            taken.mul(challenge.deltaHighestGains).div(100) >=
            challenge.deltaHighestSpendings.mul(given).div(100);
    }

    function answerSwapChallengeWithProofOfExclusiveBalanceAllotment(
        BimodalLib.Ledger storage ledger,
        ERC20[2] tokens,
        address issuer,
        uint64 transferMembershipTrail,
        bytes32[] allotmentChain,
        bytes32[] membershipChain,
        bytes32[] txChain,
        uint256[] values,
        uint256[2][3] lrDeltasPassiveMark,
        uint256 balance,
        bytes32[3] txSetRootChecksumDummy
    ) public {
        ERC20 conduit = ERC20(
            address(keccak256(abi.encodePacked(tokens[0], tokens[1])))
        );
        BimodalLib.Challenge storage challenge = ledger
            .challengeBook[conduit][issuer][issuer];
        require(
            challenge.challengeType == BimodalLib.ChallengeType.SWAP_ENACTMENT
        );

        // Assert that the challenged swap belongs to the transition
        txSetRootChecksumDummy[2] = MerkleVerifier.swapOrderChecksum(
            tokens,
            challenge.trailIdentifier, // recipient trail
            challenge.deltaHighestSpendings, // sell amount
            challenge.deltaHighestGains, // buy amount
            balance, // starting balance
            challenge.deliveredTxNonce
        );

        require(
            MerkleVerifier.verifyProofOfMembership(
                transferMembershipTrail,
                txChain,
                txSetRootChecksumDummy[2], // order checksum
                txSetRootChecksumDummy[0]
            ),
            "M"
        ); // txSetRoot

        // Require committed transition to include swap
        txSetRootChecksumDummy[2] = MerkleVerifier.activeStateUpdateChecksum(
            tokens[1],
            issuer,
            challenge.trailIdentifier,
            challenge.initialStateEon,
            txSetRootChecksumDummy[0], // txSetRoot
            lrDeltasPassiveMark[1]
        ); // deltas

        if (balance != 2**256 - 1) {
            require(
                verifySwapConsistency(
                    ledger,
                    tokens,
                    challenge,
                    lrDeltasPassiveMark[0],
                    lrDeltasPassiveMark[1],
                    lrDeltasPassiveMark[2][0],
                    balance
                ),
                "v"
            );
        }

        // Assert that this transition was used to update the recipient's stake
        require(
            verifyProofOfExclusiveAccountBalanceAllotment(
                ledger,
                tokens[1],
                issuer,
                [txSetRootChecksumDummy[2], txSetRootChecksumDummy[1]], // activeStateChecksum, passiveChecksum
                challenge.trailIdentifier,
                [
                    challenge.initialStateEon.add(1),
                    lrDeltasPassiveMark[2][0],
                    lrDeltasPassiveMark[2][1]
                ],
                allotmentChain,
                membershipChain,
                values,
                lrDeltasPassiveMark[0]
            ), // LR
            "s"
        );

        markChallengeAnswered(ledger, challenge);
    }

    // ========================================================================
    // ========================================================================
    // ========================================================================
    // ====================================  WITHDRAWAL Challenge
    // ========================================================================
    // ========================================================================
    // ========================================================================
    function slashWithdrawalWithProofOfMinimumAvailableBalance(
        BimodalLib.Ledger storage ledger,
        ERC20 token,
        address withdrawer,
        uint256[2] markerEonAvailable,
        bytes32[2] rs,
        uint8 v
    ) public returns (uint256[2] amounts) {
        uint256 latestEon = ledger.currentEon();
        require(latestEon < markerEonAvailable[0].add(3), "m");

        bytes32 checksum = keccak256(
            abi.encodePacked(
                keccak256(abi.encodePacked(address(this))),
                keccak256(abi.encodePacked(token)),
                keccak256(abi.encodePacked(withdrawer)),
                markerEonAvailable[0],
                markerEonAvailable[1]
            )
        );

        require(
            withdrawer ==
                BimodalLib.signedMessageECRECOVER(checksum, rs[0], rs[1], v)
        );

        BimodalLib.Wallet storage entry = ledger.walletBook[token][withdrawer];
        BimodalLib.Withdrawal[] storage withdrawals = entry.withdrawals;

        for (uint32 i = 1; i <= withdrawals.length; i++) {
            BimodalLib.Withdrawal storage withdrawal = withdrawals[withdrawals
                .length
                .sub(i)];

            if (withdrawal.eon.add(1) < latestEon) {
                break;
            } else if (withdrawal.eon == latestEon.sub(1)) {
                amounts[0] = amounts[0].add(withdrawal.amount);
            } else if (withdrawal.eon == latestEon) {
                amounts[1] = amounts[1].add(withdrawal.amount);
            }
        }

        require(amounts[0].add(amounts[1]) > markerEonAvailable[1]);

        withdrawals.length = withdrawals.length.sub(i.sub(1)); // i >= 1

        if (amounts[1] > 0) {
            ledger.deductFromRunningPendingWithdrawals(
                token,
                latestEon,
                latestEon,
                amounts[1]
            );
            ledger.appendOperationToEonAccumulator(
                latestEon,
                token,
                withdrawer,
                BimodalLib.Operation.CANCELLATION,
                amounts[1]
            );
        }

        if (amounts[0] > 0) {
            ledger.deductFromRunningPendingWithdrawals(
                token,
                latestEon.sub(1),
                latestEon,
                amounts[0]
            );
            ledger.appendOperationToEonAccumulator(
                latestEon.sub(1),
                token,
                withdrawer,
                BimodalLib.Operation.CANCELLATION,
                amounts[0]
            );
        }
    }
}
/**
 * This library contains the challenge-response implementations of NOCUST.
 */
function markChallengeAnswered(
    BimodalLib.Ledger storage ledger,
    BimodalLib.Challenge storage challenge
) private {
    uint256 eon = ledger.currentEon();

    require(
        challenge.challengeType != BimodalLib.ChallengeType.NONE &&
            block.number.sub(challenge.block) < ledger.BLOCKS_PER_EPOCH &&
            (challenge.initialStateEon == eon.sub(1) ||
                (challenge.initialStateEon == eon.sub(2) &&
                    ledger.currentEra() < ledger.BLOCKS_PER_EPOCH))
    );

    clearChallenge(ledger, challenge);
}

/**
 * This method marks a challenge as having been successfully answered only if
 * the response was provided in time.
 */