"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.signSingleOrder = signSingleOrder;
exports.getOrderJudgement = getOrderJudgement;
exports.getMessagePayloadHash = getMessagePayloadHash;
exports.getMessageHash = getMessageHash;
exports.hashRemoteOrderPayload = hashRemoteOrderPayload;
exports.hashCommitOrRevertOrderPayload = hashCommitOrRevertOrderPayload;
exports.getAttestationMessagePayloadCommit = getAttestationMessagePayloadCommit;
exports.getAttestationMessagePayloadRevert = getAttestationMessagePayloadRevert;
exports.constructMultiMerkleProof = constructMultiMerkleProof;
exports.constructMultiMerkleProofVerified = constructMultiMerkleProofVerified;
const ethers_1 = require("ethers");
const logger_1 = require("../utils/logger");
const ethereumjs_util_1 = require("ethereumjs-util");
const merkle_tree_1 = require("@openzeppelin/merkle-tree");
async function signSingleOrder(order, settledAmount, bidWinner, config, wallet) {
    let GMPByte = config.tokens.commitGmpByte;
    let maybeBidAmountDifferentThanMaxReward = null; // This is only used for commitOpenMarketGmpByte
    if (GMPByte === 'commit' && settledAmount.lt(order.maxReward) && settledAmount.gt(0)) {
        GMPByte = config.tokens.commitOpenMarketGmpByte;
        maybeBidAmountDifferentThanMaxReward = settledAmount;
    }
    const messagePayload = getAttestationMessagePayloadCommit(GMPByte, order, bidWinner, maybeBidAmountDifferentThanMaxReward);
    const messagePayloadHash = getMessagePayloadHash(messagePayload);
    const signature = await wallet.signMessage((0, ethereumjs_util_1.toBuffer)(messagePayloadHash));
    return [signature, messagePayloadHash, messagePayload];
}
function getOrderJudgement(order, receivedAmount, bidAmount, receivedInAsset, receivedForTarget, bidWinner, config, orderTimeout, orderTimestamp, supportRevertCommitments) {
    let GMPByte = config.tokens.commitGmpByte;
    if (orderTimestamp + orderTimeout < Date.now() / 1000) {
        if (supportRevertCommitments) {
            GMPByte = config.tokens.revertGmpByte;
        }
        else {
            logger_1.logger.warn({ id: order.id, orderTimestamp, orderTimeout, now: Date.now() / 1000, sourceNetwork: order.source }, `💂🚨 Commitments to reverted orders is currently not supported. Will deliver as Commit with the risk of Executor that user has claimed before.`);
        }
    }
    let maybeBidAmountDifferentThanMaxReward = null; // This is only used for commitOpenMarketGmpByte
    if (GMPByte === 'commit' && bidAmount.lt(order.maxReward)) {
        GMPByte = config.tokens.commitOpenMarketGmpByte;
        maybeBidAmountDifferentThanMaxReward = bidAmount;
    }
    const messagePayload = getAttestationMessagePayloadCommit(GMPByte, order, bidWinner, maybeBidAmountDifferentThanMaxReward);
    const messagePayloadHash = getMessagePayloadHash(messagePayload);
    const judgement = {
        correct: true,
        reason: `ok`,
        GMPByte,
        id: order.id,
        executor: bidWinner,
        messagePayloadHash,
        messagePayload,
        orderTimeout: orderTimeout,
        orderTimestamp: orderTimestamp,
    };
    if (!order.amount.eq(receivedAmount)) {
        judgement.correct = false;
        judgement.reason = `Amount mismatch. Expected ${order.amount.toString()}, got ${receivedAmount.toString()}`;
    }
    else if (order.destinationAsset !== receivedInAsset) {
        judgement.correct = false;
        judgement.reason = `Asset mismatch. Expected asset: ${order.destinationAssetCode}, ${order.destinationAsset} got ${receivedInAsset}`;
    }
    else if (order.targetAccount !== receivedForTarget) {
        judgement.correct = false;
        judgement.reason = `Target mismatch. Expected ${order.targetAccount}, got ${receivedForTarget}`;
    }
    return judgement;
}
function getMessagePayloadHash(attestationMessagePayload) {
    const messagePayloadBytes = ethers_1.ethers.utils.solidityPack(['bytes'], [attestationMessagePayload]);
    const messagePayloadHash = ethers_1.ethers.utils.keccak256((0, ethereumjs_util_1.toBuffer)(messagePayloadBytes));
    return messagePayloadHash;
}
function getMessageHash(messagePayloadHash, nonceOnSource, orderId, sourceNetwork, destinationNetwork, logger) {
    let messageHash;
    // TODO: shouldn't this be from `sourceNetwork` ?
    const source4Bytes = '0x' + Buffer.from(destinationNetwork, 'utf-8').toString('hex');
    const sourceHeight = nonceOnSource;
    try {
        messageHash = ethers_1.ethers.utils.keccak256(ethers_1.ethers.utils.solidityPack(['bytes32', 'bytes4', 'uint32'], [messagePayloadHash, source4Bytes, sourceHeight]));
    }
    catch (error) {
        logger.error({
            id: orderId,
            sourceNetwork,
            destinationNetwork,
            data: [messagePayloadHash, source4Bytes, sourceHeight],
            error: error.message,
        }, `📝 Will not be able to produce attestation - bad ABI parsing`);
        return null;
    }
    logger.info({
        id: orderId,
        sourceNetwork,
        destinationNetwork,
        data: [messagePayloadHash, source4Bytes, sourceHeight],
    }, `📝 Attesting to order`);
    return { messageHash, source4Bytes };
}
function hashRemoteOrderPayload(order) {
    const maxRewardFormatted = order.maxReward.toString();
    const encodedPayload = ethers_1.ethers.utils.defaultAbiCoder.encode(['address', 'uint256'], [order.sourceAsset, maxRewardFormatted]);
    return ethers_1.ethers.utils.keccak256(encodedPayload);
}
function hashCommitOrRevertOrderPayload(currentHash, address, addressZero, GMPByte) {
    const addressValue = GMPByte === 'revert' ? addressZero : address;
    const encodedPayload = ethers_1.ethers.utils.defaultAbiCoder.encode(['bytes32', 'address'], [currentHash, addressValue]);
    return ethers_1.ethers.utils.keccak256(encodedPayload);
}
function getAttestationMessagePayloadCommit(GMPByte, order, executor, maybeBidAmountDifferentThanMaxReward) {
    // If Reverse Commitment, we don't need to include the executor
    if (GMPByte == '0x01') {
        return `${GMPByte}${order.id.slice(2)}`;
    }
    let attestationMessagePayload = `${GMPByte}${order.id.slice(2)}${executor.slice(2)}`;
    if (maybeBidAmountDifferentThanMaxReward) {
        const bidAmountU256Encoded = ethers_1.ethers.utils.defaultAbiCoder.encode(['uint256'], [maybeBidAmountDifferentThanMaxReward]);
        attestationMessagePayload += bidAmountU256Encoded.slice(2);
        attestationMessagePayload += order.sourceAccount.slice(2);
        attestationMessagePayload += order.sourceAsset.slice(2);
        const maxRewardU256Encoded = ethers_1.ethers.utils.defaultAbiCoder.encode(['uint256'], [order.maxReward]);
        attestationMessagePayload += maxRewardU256Encoded.slice(2);
        const amountU256Encoded = ethers_1.ethers.utils.defaultAbiCoder.encode(['uint256'], [order.amount]);
        attestationMessagePayload += amountU256Encoded.slice(2);
        const assetIdU32Encoded = ethers_1.ethers.utils.solidityPack(['uint32'], [order.destinationAssetCode]);
        attestationMessagePayload += assetIdU32Encoded.slice(2);
        const rewardAssetIdU32Encoded = ethers_1.ethers.utils.solidityPack(['uint32'], [order.sourceAssetCode]);
        attestationMessagePayload += rewardAssetIdU32Encoded.slice(2);
        const nonceU32Encoded = ethers_1.ethers.utils.solidityPack(['uint32'], [order.nonce]);
        attestationMessagePayload += nonceU32Encoded.slice(2);
    }
    return attestationMessagePayload;
}
function getAttestationMessagePayloadRevert(GMPByte, orderId) {
    const attestationMessagePayload = `${GMPByte}${orderId.slice(2)}`;
    return attestationMessagePayload;
}
function constructMultiMerkleProof(signers) {
    // Wrap each signer in an array
    const signersArrays = signers.map((signer) => [signer]);
    // Maker sure no duplicate signers
    const uniqueSignersSet = new Set(signersArrays.map((signer) => signer[0]));
    const uniqueSigners = [];
    for (const signer of uniqueSignersSet) {
        uniqueSigners.push([signer]);
    }
    const tree = merkle_tree_1.StandardMerkleTree.of(uniqueSigners, ['address'], {
        sortLeaves: false,
    });
    // Create a Merkle tree from the leaves.
    const proofs = [];
    const leafHashes = [];
    for (const [i, v] of tree.entries()) {
        const proof = tree.getProof(i);
        leafHashes.push(tree.leafHash(v));
        proofs.push(proof);
    }
    // Create a multi proof from the leaves.
    const multiProof = tree.getMultiProof(uniqueSigners);
    const verified = tree.verifyMultiProof(multiProof);
    if (!verified) {
        logger_1.logger.warn({
            uniqueSigners: uniqueSigners.map((signer) => signer[0]),
        }, '🤮 Multi proof is not verified');
        throw new Error('Multi proof is not verified');
    }
    return {
        root: tree.root,
        proof: multiProof.proof,
        flags: multiProof.proofFlags,
        leaves: leafHashes,
    };
}
function constructMultiMerkleProofVerified(signers, impliedRoot, allParticipants) {
    // Wrap each signer in an array
    const signersArrays = allParticipants.map((signer) => [signer]);
    const activeSigners = signers.map((signer) => [signer]);
    const tree = merkle_tree_1.StandardMerkleTree.of(signersArrays, ['address'], {
        sortLeaves: false,
    });
    // Create a Merkle tree from the leaves.
    const proofs = [];
    const leafHashes = [];
    for (const [i, v] of tree.entries()) {
        const proof = tree.getProof(i);
        leafHashes.push(tree.leafHash(v));
        proofs.push(proof);
    }
    // Create a multi proof from the leaves.
    const multiProof = tree.getMultiProof(activeSigners);
    const verified = merkle_tree_1.StandardMerkleTree.verifyMultiProof(impliedRoot, ['address'], multiProof);
    const reconstructedRoot = tree.root;
    if (!verified) {
        logger_1.logger.warn({
            uniqueSigners: activeSigners.map((signer) => signer[0]),
            allParticipants,
            impliedRoot,
            reconstructedRoot,
        }, '🤮 Multi proof is not verified');
        // throw new Error('Multi proof is not verified')
    }
    return {
        root: tree.root,
        proof: multiProof.proof,
        flags: multiProof.proofFlags,
        leaves: leafHashes,
    };
}
//# sourceMappingURL=attestation.js.map