library LoopringOrderDecoder {

    function decodeLoopringOrders(bytes memory ringData, uint[] memory indices) internal pure returns (Data.Order[] memory orders) {
        uint numOrders = bytesToUint16(ringData, 2);
        uint numRings = bytesToUint16(ringData, 4);
        uint numSpendables = bytesToUint16(ringData, 6);

        uint dataPtr;
        assembly {dataPtr := ringData}
        uint orderDataPtr = (dataPtr + 8) + 3 * 2;
        uint ringDataPtr = orderDataPtr + (32 * numOrders) * 2;
        uint dataBlobPtr = ringDataPtr + (numRings * 9) + 32;

        // orders = new LoopringOrder[](indices.length);
        orders = new Data.Order[](indices.length);

        for (uint i = 0; i < indices.length; i++) {
            orders[i] = _decodeLoopringOrderAtIndex(dataBlobPtr, orderDataPtr + 2, numSpendables, indices[i]);
        }
    }

    // ----------------------------

    function _decodeLoopringOrderAtIndex(uint data, uint tablesPtr, uint numSpendables, uint orderIndex) private pure returns (Data.Order memory order) {
        tablesPtr += 64 * orderIndex;

        uint offset;
        bytes memory emptyBytes = new bytes(0);
        address lrcTokenAddress = address(0);
        Data.Spendable[] memory spendableList = new Data.Spendable[](numSpendables);
        uint orderStructSize = 40 * 32;

        assembly {
            order := mload(0x40)
            mstore(0x40, add(order, orderStructSize)) // Reserve memory for the order struct

        // order.version
            offset := and(mload(add(tablesPtr, 0)), 0xFFFF)
            mstore(
            add(order, 0),
            offset
            )

        // order.owner
            offset := mul(and(mload(add(tablesPtr, 2)), 0xFFFF), 4)
            mstore(
            add(order, 32),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.tokenS
            offset := mul(and(mload(add(tablesPtr, 4)), 0xFFFF), 4)
            mstore(
            add(order, 64),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.tokenB
            offset := mul(and(mload(add(tablesPtr, 6)), 0xFFFF), 4)
            mstore(
            add(order, 96),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.amountS
            offset := mul(and(mload(add(tablesPtr, 8)), 0xFFFF), 4)
            mstore(
            add(order, 128),
            mload(add(add(data, 32), offset))
            )

        // order.amountB
            offset := mul(and(mload(add(tablesPtr, 10)), 0xFFFF), 4)
            mstore(
            add(order, 160),
            mload(add(add(data, 32), offset))
            )

        // order.validSince
            offset := mul(and(mload(add(tablesPtr, 12)), 0xFFFF), 4)
            mstore(
            add(order, 192),
            and(mload(add(add(data, 4), offset)), 0xFFFFFFFF)
            )

        // order.tokenSpendableS
            offset := and(mload(add(tablesPtr, 14)), 0xFFFF)
        // Force the spendable index to 0 if it's invalid
            offset := mul(offset, lt(offset, numSpendables))
            mstore(
            add(order, 224),
            mload(add(spendableList, mul(add(offset, 1), 32)))
            )

        // order.tokenSpendableFee
            offset := and(mload(add(tablesPtr, 16)), 0xFFFF)
        // Force the spendable index to 0 if it's invalid
            offset := mul(offset, lt(offset, numSpendables))
            mstore(
            add(order, 256),
            mload(add(spendableList, mul(add(offset, 1), 32)))
            )

        // order.dualAuthAddr
            offset := mul(and(mload(add(tablesPtr, 18)), 0xFFFF), 4)
            mstore(
            add(order, 288),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.broker
            offset := mul(and(mload(add(tablesPtr, 20)), 0xFFFF), 4)
            mstore(
            add(order, 320),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.orderInterceptor
            offset := mul(and(mload(add(tablesPtr, 22)), 0xFFFF), 4)
            mstore(
            add(order, 416),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.wallet
            offset := mul(and(mload(add(tablesPtr, 24)), 0xFFFF), 4)
            mstore(
            add(order, 448),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // order.validUntil
            offset := mul(and(mload(add(tablesPtr, 26)), 0xFFFF), 4)
            mstore(
            add(order, 480),
            and(mload(add(add(data, 4), offset)), 0xFFFFFFFF)
            )

        // Default to empty bytes array for value sig and dualAuthSig
            mstore(add(data, 32), emptyBytes)

        // order.sig
            offset := mul(and(mload(add(tablesPtr, 28)), 0xFFFF), 4)
            mstore(
            add(order, 512),
            add(data, add(offset, 32))
            )

        // order.dualAuthSig
            offset := mul(and(mload(add(tablesPtr, 30)), 0xFFFF), 4)
            mstore(
            add(order, 544),
            add(data, add(offset, 32))
            )

        // Restore default to 0
            mstore(add(data, 32), 0)

        // order.allOrNone
            offset := and(mload(add(tablesPtr, 32)), 0xFFFF)
            mstore(
            add(order, 576),
            gt(offset, 0)
            )

        // lrcTokenAddress is the default value for feeToken
            mstore(add(data, 20), lrcTokenAddress)

        // order.feeToken
            offset := mul(and(mload(add(tablesPtr, 34)), 0xFFFF), 4)
            mstore(
            add(order, 608),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // Restore default to 0
            mstore(add(data, 20), 0)

        // order.feeAmount
            offset := mul(and(mload(add(tablesPtr, 36)), 0xFFFF), 4)
            mstore(
            add(order, 640),
            mload(add(add(data, 32), offset))
            )

        // order.waiveFeePercentage
            offset := and(mload(add(tablesPtr, 38)), 0xFFFF)
            mstore(
            add(order, 672),
            offset
            )

        // order.tokenSFeePercentage
            offset := and(mload(add(tablesPtr, 40)), 0xFFFF)
            mstore(
            add(order, 704),
            offset
            )

        // order.tokenBFeePercentage
            offset := and(mload(add(tablesPtr, 42)), 0xFFFF)
            mstore(
            add(order, 736),
            offset
            )

        // The owner is the default value of tokenRecipient
        // order.owner
            mstore(add(data, 20), mload(add(order, 32)))

        // order.tokenRecipient
            offset := mul(and(mload(add(tablesPtr, 44)), 0xFFFF), 4)
            mstore(
            add(order, 768),
            and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
            )

        // Restore default to 0
            mstore(add(data, 20), 0)

        // order.walletSplitPercentage
            offset := and(mload(add(tablesPtr, 46)), 0xFFFF)
            mstore(
            add(order, 800),
            offset
            )

        // order.tokenTypeS
            offset := and(mload(add(tablesPtr, 48)), 0xFFFF)
            mstore(
            add(order, 1024),
            offset
            )

        // order.tokenTypeB
            offset := and(mload(add(tablesPtr, 50)), 0xFFFF)
            mstore(
            add(order, 1056),
            offset
            )

        // order.tokenTypeFee
            offset := and(mload(add(tablesPtr, 52)), 0xFFFF)
            mstore(
            add(order, 1088),
            offset
            )

        // order.trancheS
            offset := mul(and(mload(add(tablesPtr, 54)), 0xFFFF), 4)
            mstore(
            add(order, 1120),
            mload(add(add(data, 32), offset))
            )

        // order.trancheB
            offset := mul(and(mload(add(tablesPtr, 56)), 0xFFFF), 4)
            mstore(
            add(order, 1152),
            mload(add(add(data, 32), offset))
            )

        // Restore default to 0
            mstore(add(data, 20), 0)

        // order.maxPrimaryFillAmount
            offset := mul(and(mload(add(tablesPtr, 58)), 0xFFFF), 4)
            mstore(
            add(order, 1184),
            mload(add(add(data, 32), offset))
            )

        // order.allOrNone
            offset := and(mload(add(tablesPtr, 60)), 0xFFFF)
            mstore(
            add(order, 1216),
            gt(offset, 0)
            )

        // Default to empty bytes array for transferDataS
            mstore(add(data, 32), emptyBytes)

        // order.transferDataS
            offset := mul(and(mload(add(tablesPtr, 62)), 0xFFFF), 4)
            mstore(
            add(order, 1248),
            add(data, add(offset, 32))
            )

        // Restore default to 0
            mstore(add(data, 32), 0)

        // Set default  values
            mstore(add(order, 832), 0)         // order.P2P
            mstore(add(order, 864), 0)         // order.hash
            mstore(add(order, 896), 0)         // order.brokerInterceptor
            mstore(add(order, 928), 0)         // order.filledAmountS
            mstore(add(order, 960), 0)         // order.initialFilledAmountS
            mstore(add(order, 992), 1)         // order.valid

        }
    }

    function bytesToUintX(bytes memory b, uint offset, uint numBytes) private pure returns (uint data) {
        require(b.length >= offset + numBytes, "INVALID_SIZE");
        assembly {data := mload(add(add(b, numBytes), offset))}
    }

    function bytesToUint16(bytes memory b, uint offset) private pure returns (uint16) {
        return uint16(bytesToUintX(b, offset, 2) & 0xFFFF);
    }
}
function _decodeLoopringOrderAtIndex(uint data, uint tablesPtr, uint numSpendables, uint orderIndex) private pure returns (Data.Order memory order) {
    tablesPtr += 64 * orderIndex;

    uint offset;
    bytes memory emptyBytes = new bytes(0);
    address lrcTokenAddress = address(0);
    Data.Spendable[] memory spendableList = new Data.Spendable[](numSpendables);
    uint orderStructSize = 40 * 32;

    assembly {
        order := mload(0x40)
        mstore(0x40, add(order, orderStructSize)) // Reserve memory for the order struct

    // order.version
        offset := and(mload(add(tablesPtr, 0)), 0xFFFF)
        mstore(
        add(order, 0),
        offset
        )

    // order.owner
        offset := mul(and(mload(add(tablesPtr, 2)), 0xFFFF), 4)
        mstore(
        add(order, 32),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.tokenS
        offset := mul(and(mload(add(tablesPtr, 4)), 0xFFFF), 4)
        mstore(
        add(order, 64),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.tokenB
        offset := mul(and(mload(add(tablesPtr, 6)), 0xFFFF), 4)
        mstore(
        add(order, 96),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.amountS
        offset := mul(and(mload(add(tablesPtr, 8)), 0xFFFF), 4)
        mstore(
        add(order, 128),
        mload(add(add(data, 32), offset))
        )

    // order.amountB
        offset := mul(and(mload(add(tablesPtr, 10)), 0xFFFF), 4)
        mstore(
        add(order, 160),
        mload(add(add(data, 32), offset))
        )

    // order.validSince
        offset := mul(and(mload(add(tablesPtr, 12)), 0xFFFF), 4)
        mstore(
        add(order, 192),
        and(mload(add(add(data, 4), offset)), 0xFFFFFFFF)
        )

    // order.tokenSpendableS
        offset := and(mload(add(tablesPtr, 14)), 0xFFFF)
    // Force the spendable index to 0 if it's invalid
        offset := mul(offset, lt(offset, numSpendables))
        mstore(
        add(order, 224),
        mload(add(spendableList, mul(add(offset, 1), 32)))
        )

    // order.tokenSpendableFee
        offset := and(mload(add(tablesPtr, 16)), 0xFFFF)
    // Force the spendable index to 0 if it's invalid
        offset := mul(offset, lt(offset, numSpendables))
        mstore(
        add(order, 256),
        mload(add(spendableList, mul(add(offset, 1), 32)))
        )

    // order.dualAuthAddr
        offset := mul(and(mload(add(tablesPtr, 18)), 0xFFFF), 4)
        mstore(
        add(order, 288),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.broker
        offset := mul(and(mload(add(tablesPtr, 20)), 0xFFFF), 4)
        mstore(
        add(order, 320),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.orderInterceptor
        offset := mul(and(mload(add(tablesPtr, 22)), 0xFFFF), 4)
        mstore(
        add(order, 416),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.wallet
        offset := mul(and(mload(add(tablesPtr, 24)), 0xFFFF), 4)
        mstore(
        add(order, 448),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // order.validUntil
        offset := mul(and(mload(add(tablesPtr, 26)), 0xFFFF), 4)
        mstore(
        add(order, 480),
        and(mload(add(add(data, 4), offset)), 0xFFFFFFFF)
        )

    // Default to empty bytes array for value sig and dualAuthSig
        mstore(add(data, 32), emptyBytes)

    // order.sig
        offset := mul(and(mload(add(tablesPtr, 28)), 0xFFFF), 4)
        mstore(
        add(order, 512),
        add(data, add(offset, 32))
        )

    // order.dualAuthSig
        offset := mul(and(mload(add(tablesPtr, 30)), 0xFFFF), 4)
        mstore(
        add(order, 544),
        add(data, add(offset, 32))
        )

    // Restore default to 0
        mstore(add(data, 32), 0)

    // order.allOrNone
        offset := and(mload(add(tablesPtr, 32)), 0xFFFF)
        mstore(
        add(order, 576),
        gt(offset, 0)
        )

    // lrcTokenAddress is the default value for feeToken
        mstore(add(data, 20), lrcTokenAddress)

    // order.feeToken
        offset := mul(and(mload(add(tablesPtr, 34)), 0xFFFF), 4)
        mstore(
        add(order, 608),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // Restore default to 0
        mstore(add(data, 20), 0)

    // order.feeAmount
        offset := mul(and(mload(add(tablesPtr, 36)), 0xFFFF), 4)
        mstore(
        add(order, 640),
        mload(add(add(data, 32), offset))
        )

    // order.waiveFeePercentage
        offset := and(mload(add(tablesPtr, 38)), 0xFFFF)
        mstore(
        add(order, 672),
        offset
        )

    // order.tokenSFeePercentage
        offset := and(mload(add(tablesPtr, 40)), 0xFFFF)
        mstore(
        add(order, 704),
        offset
        )

    // order.tokenBFeePercentage
        offset := and(mload(add(tablesPtr, 42)), 0xFFFF)
        mstore(
        add(order, 736),
        offset
        )

    // The owner is the default value of tokenRecipient
    // order.owner
        mstore(add(data, 20), mload(add(order, 32)))

    // order.tokenRecipient
        offset := mul(and(mload(add(tablesPtr, 44)), 0xFFFF), 4)
        mstore(
        add(order, 768),
        and(mload(add(add(data, 20), offset)), 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)
        )

    // Restore default to 0
        mstore(add(data, 20), 0)

    // order.walletSplitPercentage
        offset := and(mload(add(tablesPtr, 46)), 0xFFFF)
        mstore(
        add(order, 800),
        offset
        )

    // order.tokenTypeS
        offset := and(mload(add(tablesPtr, 48)), 0xFFFF)
        mstore(
        add(order, 1024),
        offset
        )

    // order.tokenTypeB
        offset := and(mload(add(tablesPtr, 50)), 0xFFFF)
        mstore(
        add(order, 1056),
        offset
        )

    // order.tokenTypeFee
        offset := and(mload(add(tablesPtr, 52)), 0xFFFF)
        mstore(
        add(order, 1088),
        offset
        )

    // order.trancheS
        offset := mul(and(mload(add(tablesPtr, 54)), 0xFFFF), 4)
        mstore(
        add(order, 1120),
        mload(add(add(data, 32), offset))
        )

    // order.trancheB
        offset := mul(and(mload(add(tablesPtr, 56)), 0xFFFF), 4)
        mstore(
        add(order, 1152),
        mload(add(add(data, 32), offset))
        )

    // Restore default to 0
        mstore(add(data, 20), 0)

    // order.maxPrimaryFillAmount
        offset := mul(and(mload(add(tablesPtr, 58)), 0xFFFF), 4)
        mstore(
        add(order, 1184),
        mload(add(add(data, 32), offset))
        )

    // order.allOrNone
        offset := and(mload(add(tablesPtr, 60)), 0xFFFF)
        mstore(
        add(order, 1216),
        gt(offset, 0)
        )

    // Default to empty bytes array for transferDataS
        mstore(add(data, 32), emptyBytes)

    // order.transferDataS
        offset := mul(and(mload(add(tablesPtr, 62)), 0xFFFF), 4)
        mstore(
        add(order, 1248),
        add(data, add(offset, 32))
        )

    // Restore default to 0
        mstore(add(data, 32), 0)

    // Set default  values
        mstore(add(order, 832), 0)         // order.P2P
        mstore(add(order, 864), 0)         // order.hash
        mstore(add(order, 896), 0)         // order.brokerInterceptor
        mstore(add(order, 928), 0)         // order.filledAmountS
        mstore(add(order, 960), 0)         // order.initialFilledAmountS
        mstore(add(order, 992), 1)         // order.valid

    }
}

// ----------------------------