"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.waitForEvent = exports.waitForIncludedEvent = exports.waitForFinalizedEvent = exports.bumpFeesForBtcTx = exports.RbfEstimateMode = exports.calculateBtcTxVsize = exports.getExchangeRateValueToSetForTesting = exports.vaultStatusToLabel = exports.runWhileMiningBTCBlocks = exports.makeRandomPolkadotKeyPair = exports.makeRandomBitcoinAddress = exports.sudo = exports.callWith = exports.callWithExchangeRate = exports.wait_success = exports.sleep = exports.BITCOIN_BLOCK_TIME_IN_MS = exports.SLEEP_TIME_MS = void 0;
const api_1 = require("@polkadot/api");
const util_crypto_1 = require("@polkadot/util-crypto");
const big_js_1 = __importDefault(require("big.js"));
const bitcoinjs = __importStar(require("bitcoinjs-lib"));
const src_1 = require("../../src");
const config_1 = require("../config");
exports.SLEEP_TIME_MS = 1000;
// On Bitcoin mainnet, block time is ~10 mins. Speed it up to 10s during the tests.
exports.BITCOIN_BLOCK_TIME_IN_MS = 10 * 1000;
function sleep(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
}
exports.sleep = sleep;
function wait_success(call) {
    return __awaiter(this, void 0, void 0, function* () {
        for (;;) {
            try {
                const res = yield call();
                return res;
            }
            catch (_) {
                yield sleep(exports.SLEEP_TIME_MS);
            }
        }
    });
}
exports.wait_success = wait_success;
function callWithExchangeRate(oracleAPI, exchangeRate, fn) {
    return __awaiter(this, void 0, void 0, function* () {
        const initialExchangeRate = yield oracleAPI.getExchangeRate(exchangeRate.counter);
        yield oracleAPI.setExchangeRate(exchangeRate);
        yield oracleAPI.waitForExchangeRateUpdate(exchangeRate);
        let result;
        try {
            yield fn();
            result = Promise.resolve();
        }
        catch (error) {
            console.log(`Error: ${error.toString()}`);
            result = Promise.reject(error);
        }
        finally {
            yield oracleAPI.setExchangeRate(initialExchangeRate);
            yield oracleAPI.waitForExchangeRateUpdate(initialExchangeRate);
        }
        return result;
    });
}
exports.callWithExchangeRate = callWithExchangeRate;
/*
    Assumption: the `call` argument uses one of the APIs in `InterBtcApi`.
    Since `InterBtcApi` is passed by reference, modifying the account before
    the call means `call` uses that account.
*/
function callWith(InterBtcApi, key, call) {
    return __awaiter(this, void 0, void 0, function* () {
        const prevKey = InterBtcApi.account;
        InterBtcApi.setAccount(key);
        let result;
        try {
            result = yield call();
        }
        finally {
            if (prevKey)
                InterBtcApi.setAccount(prevKey);
        }
        return result;
    });
}
exports.callWith = callWith;
function sudo(InterBtcApi, call) {
    const keyring = new api_1.Keyring({ type: "sr25519" });
    const rootKey = keyring.addFromUri(config_1.SUDO_URI);
    return callWith(InterBtcApi, rootKey, call);
}
exports.sudo = sudo;
function makeRandomBitcoinAddress() {
    const pair = bitcoinjs.ECPair.makeRandom();
    const p2pkh = bitcoinjs.payments.p2pkh({ pubkey: pair.publicKey, network: bitcoinjs.networks.regtest });
    if (!p2pkh.address) {
        throw new Error("Failed to make random bitcoin address");
    }
    return p2pkh.address;
}
exports.makeRandomBitcoinAddress = makeRandomBitcoinAddress;
function makeRandomPolkadotKeyPair(keyring) {
    const mnemonic = util_crypto_1.mnemonicGenerate(12);
    return keyring.addFromUri(mnemonic);
}
exports.makeRandomPolkadotKeyPair = makeRandomPolkadotKeyPair;
function runWhileMiningBTCBlocks(bitcoinCoreClient, fn) {
    return __awaiter(this, void 0, void 0, function* () {
        function generateBlocks() {
            bitcoinCoreClient.mineBlocks(1);
        }
        const intervalId = setInterval(generateBlocks, exports.BITCOIN_BLOCK_TIME_IN_MS);
        try {
            yield fn();
        }
        finally {
            clearInterval(intervalId);
        }
    });
}
exports.runWhileMiningBTCBlocks = runWhileMiningBTCBlocks;
const vaultStatusToLabel = (status) => {
    switch (status) {
        case src_1.VaultStatusExt.Active:
            return "Active";
        case src_1.VaultStatusExt.Inactive:
            return "Inactive";
        case src_1.VaultStatusExt.CommittedTheft:
            return "Commited theft";
        case src_1.VaultStatusExt.Liquidated:
            return "Liquidated";
    }
};
exports.vaultStatusToLabel = vaultStatusToLabel;
// use the same exchange rate as the running oracles use
// to avoid flaky tests due to updated prices from the oracle client(s)
// note: currently the same for all collateral currencies - might change in future
const getExchangeRateValueToSetForTesting = (_collateralCurrency) => new big_js_1.default("230.0");
exports.getExchangeRateValueToSetForTesting = getExchangeRateValueToSetForTesting;
/**
 * Returns the vsize (virtual size) of the given transaction.
 *
 * The vsize is calculated by dividing the weight by 4 and rounding up to the next integer.
 * See also https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#Transaction_size_calculations
 * @param transaction the transaction to calculate the vsize of
 * @returns the vsize of the transaction
 */
const calculateBtcTxVsize = (transaction) => {
    const txWeight = new big_js_1.default(transaction.weight || 0);
    return txWeight.div(4).round(0, 3 /* RoundUp */);
};
exports.calculateBtcTxVsize = calculateBtcTxVsize;
// types/interface for RBF bumpfee request
// see: https://developer.bitcoin.org/reference/rpc/bumpfee.html
var RbfEstimateMode;
(function (RbfEstimateMode) {
    RbfEstimateMode["unset"] = "unset";
    RbfEstimateMode["economical"] = "economical";
    RbfEstimateMode["conservative"] = "conservative";
})(RbfEstimateMode = exports.RbfEstimateMode || (exports.RbfEstimateMode = {}));
/**
 * Tries to bump fees (replace by fee) on a given bitcoin transaction, using the "bumpfee" RPC call.
 * See also: https://developer.bitcoin.org/reference/rpc/bumpfee.html .
 *
 * Default options are set to calculate a replacement fee aiming for 72 blocks (~12 hours), using and economical estimate mode.
 *
 * @param bitcoinCoreClient the bitcoin core client to make the call with
 * @param txId the transaction id
 * @param options (optional) custom options to pass with "bumpfee" command, default: {conf_target: 72, estimate_mode: "economical"}
 */
const bumpFeesForBtcTx = (bitcoinCoreClient, txId, options = { conf_target: 72, estimate_mode: RbfEstimateMode.economical }) => __awaiter(void 0, void 0, void 0, function* () { return bitcoinCoreClient.client.command("bumpfee", txId, options); });
exports.bumpFeesForBtcTx = bumpFeesForBtcTx;
/**
 * Wait for an event to show up as finalized.
 *
 * This method will only subscribe to new blocks once called and then look for a specific event.
 * Contrary to @see {@link DefaultTransactionAPI.waitForEvent} which looks at all past events
 * and may return due to older events using the same name.
 *
 * Note: This method checks the parent blocks in case the event we are looking for
 * was finalized in a block just before this method was called.
 *
 * @param interBtcApi the api to use for querying
 * @param event the event to wait for, eg. `api.events.assetRegistry.RegisteredAsset`
 */
const waitForFinalizedEvent = (interBtcApi, event) => __awaiter(void 0, void 0, void 0, function* () {
    return new Promise((resolve, _) => interBtcApi.system.subscribeToFinalizedBlockHeads((header) => __awaiter(void 0, void 0, void 0, function* () {
        const events = yield interBtcApi.api.query.system.events.at(header.parentHash);
        if (eventRecordVectorContains(events, event)) {
            resolve();
        }
    })));
});
exports.waitForFinalizedEvent = waitForFinalizedEvent;
/**
 * Wait for an event to show up on chain (unfinalized).
 *
 * This method will only subscribe to new blocks once called and then look for a specific event.
 * Contrary to @see {@link DefaultTransactionAPI.waitForEvent} which looks at all past events
 * and may return due to older events using the same name.
 *
 * Note: This method checks the parent blocks in case the event we are looking for
 * was finalized in a block just before this method was called.
 *
 * @param interBtcApi the api to use for querying
 * @param event the event to wait for, eg. `api.events.assetRegistry.RegisteredAsset`
 */
const waitForIncludedEvent = (interBtcApi, event) => __awaiter(void 0, void 0, void 0, function* () {
    return new Promise((resolve, _) => interBtcApi.system.subscribeToCurrentBlockHeads((header) => __awaiter(void 0, void 0, void 0, function* () {
        const events = yield interBtcApi.api.query.system.events.at(header.parentHash);
        if (eventRecordVectorContains(events, event)) {
            resolve();
        }
    })));
});
exports.waitForIncludedEvent = waitForIncludedEvent;
const eventRecordVectorContains = (eventRecords, event) => {
    return eventRecords.find((record) => event.is(record.event)) !== undefined;
};
/**
 * Wait for an event to show up on chain.
 *
 * This method will only subscribe to new blocks once called and then look for a specific event.
 *
 * Note: This method checks the parent blocks in case the event we are looking for
 * was finalized/included in a block just before this method was called.
 * @param interBtcApi the api to use for querying
 * @param event the event to wait for, eg. `api.events.assetRegistry.RegisteredAsset`
 * @param finalized [optional] true to wait for finalized event, false (default) to only wait for included event
 * @param timeoutMs [optional] a timeout in milliseconds to wait for the event. Rejects if not found before timeout. Default: no timeout
 * @returns a promise that resolves when the event is found, and if a timeout is provided, rejects if not found before timeout.
 */
const waitForEvent = (interBtcApi, event, finalized = false, timeoutMs) => __awaiter(void 0, void 0, void 0, function* () {
    let timeoutHandle;
    const timeoutPromise = timeoutMs !== undefined
        ? new Promise((_, reject) => {
            timeoutHandle = setTimeout(() => reject(), timeoutMs);
        })
        : Promise.resolve();
    const waitForEventPromise = finalized
        ? exports.waitForFinalizedEvent(interBtcApi, event)
        : exports.waitForIncludedEvent(interBtcApi, event);
    yield Promise.race([waitForEventPromise, timeoutPromise]).then((_) => {
        clearTimeout(timeoutHandle);
    });
    return true;
});
exports.waitForEvent = waitForEvent;
//# sourceMappingURL=helpers.js.map