"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.TransactionValidator = exports.ValidationAttributes = void 0;
const neon_core_1 = require("@cityofzion/neon-core");
const api_1 = require("../api");
var ValidationAttributes;
(function (ValidationAttributes) {
    ValidationAttributes[ValidationAttributes["None"] = 0] = "None";
    ValidationAttributes[ValidationAttributes["ValidUntilBlock"] = 1] = "ValidUntilBlock";
    ValidationAttributes[ValidationAttributes["SystemFee"] = 2] = "SystemFee";
    ValidationAttributes[ValidationAttributes["NetworkFee"] = 4] = "NetworkFee";
    ValidationAttributes[ValidationAttributes["Script"] = 8] = "Script";
    ValidationAttributes[ValidationAttributes["All"] = 15] = "All";
})(ValidationAttributes = exports.ValidationAttributes || (exports.ValidationAttributes = {}));
/**
 * A class with functions to validate transaction
 */
class TransactionValidator {
    constructor(rpc, transaction) {
        this.rpcClient = rpc;
        this.transaction = transaction;
    }
    /**
     * validate validUntilBlock.
     * @param autoFix - autofix when number is below current height.
     */
    async validateValidUntilBlock(autoFix = false) {
        const { validUntilBlock: prev } = this.transaction;
        const height = await this.rpcClient.getBlockCount();
        // Suggest a lifespan of approx. 1hr based on 15s blocks
        const suggestion = TransactionValidator.TX_LIFESPAN_SUGGESTION + height - 1;
        if (prev <= height ||
            prev >= height + neon_core_1.tx.Transaction.MAX_TRANSACTION_LIFESPAN) {
            if (autoFix) {
                this.transaction.validUntilBlock = suggestion;
                return fixed(prev, suggestion);
            }
            return invalid(prev, suggestion, "Your transaction lifespan was out of range.");
        }
        if (prev - height <= 20) {
            return suggest(prev, suggestion, "Your transaction has a very limited lifespan. Consider increasing it.");
        }
        return valid();
    }
    /**
     * Validate intents
     */
    async validateScript() {
        const { state } = await this.rpcClient.invokeScript(this.transaction.script, this.transaction.signers);
        if (state !== "HALT") {
            return err("Encountered FAULT when validating script.");
        }
        return valid();
    }
    /**
     * validate systemFee
     * @param autoFix - autofix when fee is too low.
     */
    async validateSystemFee(autoFix = false) {
        const { script, signers, systemFee: prev } = this.transaction;
        const invokeResponse = await this.rpcClient.invokeScript(script, signers);
        if (invokeResponse.state === "FAULT") {
            return err("Cannot get precise systemFee as script execution on node reports FAULT.");
        }
        const gasConsumed = invokeResponse.gasconsumed;
        const suggestion = neon_core_1.u.BigInteger.fromDecimal(gasConsumed, 0);
        const compareResult = suggestion.compare(prev);
        if (compareResult > 0) {
            // Did not hit the minimum fees to run the script.
            if (autoFix) {
                this.transaction.systemFee = suggestion;
                return fixed(prev, suggestion);
            }
            return invalid(prev, suggestion, "Insufficient fees attached to run the script.");
        }
        else if (compareResult < 0) {
            // Overpaying for the script.
            return suggest(prev, suggestion, "Overpaying for running the script.");
        }
        return valid();
    }
    /**
     * Validate NetworkFee
     * @param autoFix - autofix when fee is too low.
     */
    async validateNetworkFee(autoFix = false) {
        const { networkFee: prev } = this.transaction;
        const { feePerByte, executionFeeFactor } = await (0, api_1.getFeeInformation)(this.rpcClient);
        const suggestion = (0, api_1.calculateNetworkFee)(this.transaction, feePerByte, executionFeeFactor);
        const compareResult = suggestion.compare(prev);
        if (compareResult > 0) {
            // Underpaying
            if (autoFix) {
                this.transaction.networkFee = suggestion;
                return fixed(prev, suggestion);
            }
            return invalid(prev, suggestion, "Insufficient network fees.");
        }
        else if (compareResult < 0) {
            // Overpaying
            return suggest(prev, suggestion, "Overpaying network fee.");
        }
        return valid();
    }
    async validate(attrs, autoFix = ValidationAttributes.None) {
        const validationTasks = [];
        const output = {
            valid: true,
            result: {},
        };
        if (attrs & ValidationAttributes.ValidUntilBlock) {
            validationTasks.push(this.validateValidUntilBlock((autoFix & ValidationAttributes.ValidUntilBlock) ===
                ValidationAttributes.ValidUntilBlock).then((s) => (output.result.validUntilBlock = s)));
        }
        if (attrs & ValidationAttributes.SystemFee) {
            validationTasks.push(this.validateSystemFee((autoFix & ValidationAttributes.SystemFee) ===
                ValidationAttributes.SystemFee).then((s) => (output.result.systemFee = s)));
        }
        if (attrs & ValidationAttributes.NetworkFee) {
            validationTasks.push(this.validateNetworkFee((autoFix & ValidationAttributes.NetworkFee) ===
                ValidationAttributes.NetworkFee).then((s) => (output.result.networkFee = s)));
        }
        if (attrs & ValidationAttributes.Script) {
            validationTasks.push(this.validateScript().then((s) => (output.result.script = s)));
        }
        await Promise.all(validationTasks);
        output.valid = Object.values(output.result)
            .map((r) => (r ? r.valid : true))
            .reduce((a, b) => a && b);
        return output;
    }
}
exports.TransactionValidator = TransactionValidator;
TransactionValidator.TX_LIFESPAN_SUGGESTION = 240;
function valid() {
    return { valid: true, fixed: false };
}
function fixed(prev, suggestion, message) {
    return {
        valid: true,
        fixed: true,
        prev,
        suggestion,
        message,
    };
}
function err(message) {
    return {
        valid: false,
        fixed: false,
        message,
    };
}
function suggest(prev, suggestion, message) {
    return {
        valid: true,
        fixed: false,
        prev,
        suggestion,
        message,
    };
}
function invalid(prev, suggestion, message) {
    return {
        valid: false,
        fixed: false,
        prev,
        suggestion,
        message,
    };
}
//# sourceMappingURL=validator.js.map