"use strict";
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 });
const api_1 = require("@polkadot/api");
const monetary_js_1 = require("@interlay/monetary-js");
const big_js_1 = __importDefault(require("big.js"));
const index_1 = require("../../../../../src/index");
const factory_1 = require("../../../../../src/factory");
const chai_1 = require("../../../../chai");
const config_1 = require("../../../../config");
const src_1 = require("../../../../../src");
const utils_1 = require("../../../../../src/utils");
const helpers_1 = require("../../../../utils/helpers");
const sinon_1 = __importDefault(require("sinon"));
describe("vaultsAPI", () => {
    let oracleAccount;
    let vault_to_liquidate;
    let vault_to_ban;
    let vault_1;
    let vault_1_ids;
    let vault_2;
    let vault_3;
    let vault_3_ids;
    let api;
    let bitcoinCoreClient;
    let wrappedCurrency;
    let collateralCurrencies;
    let governanceCurrency;
    let interBtcAPI;
    let assetRegistry;
    before(() => __awaiter(void 0, void 0, void 0, function* () {
        api = yield factory_1.createSubstrateAPI(config_1.PARACHAIN_ENDPOINT);
        const keyring = new api_1.Keyring({ type: "sr25519" });
        oracleAccount = keyring.addFromUri(config_1.ORACLE_URI);
        assetRegistry = new index_1.DefaultAssetRegistryAPI(api);
        interBtcAPI = new index_1.DefaultInterBtcApi(api, "regtest", undefined, config_1.ESPLORA_BASE_PATH);
        wrappedCurrency = interBtcAPI.getWrappedCurrency();
        governanceCurrency = interBtcAPI.getGovernanceCurrency();
        collateralCurrencies = utils_1.getCorrespondingCollateralCurrencies(governanceCurrency);
        vault_1 = keyring.addFromUri(config_1.VAULT_1_URI);
        vault_1_ids = collateralCurrencies.map((collateralCurrency) => src_1.newVaultId(api, vault_1.address, collateralCurrency, wrappedCurrency));
        vault_2 = keyring.addFromUri(config_1.VAULT_2_URI);
        vault_3 = keyring.addFromUri(config_1.VAULT_3_URI);
        vault_3_ids = collateralCurrencies.map((collateralCurrency) => src_1.newVaultId(api, vault_3.address, collateralCurrency, wrappedCurrency));
        vault_to_ban = keyring.addFromUri(config_1.VAULT_TO_BAN_URI);
        vault_to_liquidate = keyring.addFromUri(config_1.VAULT_TO_LIQUIDATE_URI);
        bitcoinCoreClient = new src_1.BitcoinCoreClient(config_1.BITCOIN_CORE_NETWORK, config_1.BITCOIN_CORE_HOST, config_1.BITCOIN_CORE_USERNAME, config_1.BITCOIN_CORE_PASSWORD, config_1.BITCOIN_CORE_PORT, config_1.BITCOIN_CORE_WALLET);
    }));
    after(() => {
        return api.disconnect();
    });
    afterEach(() => {
        // discard any stubbed methods after each test
        sinon_1.default.restore();
    });
    function vaultIsATestVault(vaultAddress) {
        return (vaultAddress === vault_2.address ||
            vaultAddress === vault_1.address ||
            vaultAddress === vault_3.address ||
            vaultAddress === vault_to_ban.address ||
            vaultAddress === vault_to_liquidate.address);
    }
    // FIXME: this should be tested in a way that in doesn't use magic numbers
    it("should get issuable", () => __awaiter(void 0, void 0, void 0, function* () {
        const issuableInterBTC = yield interBtcAPI.vaults.getTotalIssuableAmount();
        const minExpectedIssuableInterBTC = utils_1.newMonetaryAmount(0.002, wrappedCurrency, true);
        chai_1.assert.isTrue(issuableInterBTC.gte(minExpectedIssuableInterBTC), `Issuable ${issuableInterBTC.toHuman()}`);
    }));
    it("should get the required collateral for the vault", () => __awaiter(void 0, void 0, void 0, function* () {
        for (const vault_1_id of vault_1_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.collateral);
            const requiredCollateralForVault = yield interBtcAPI.vaults.getRequiredCollateralForVault(vault_1_id.accountId, collateralCurrency);
            const vault = yield interBtcAPI.vaults.get(vault_1_id.accountId, collateralCurrency);
            // The numeric value of the required collateral should be greater than that of issued tokens.
            // e.g. we require `0.8096` KSM for `0.00014` kBTC
            // edge case: we require 0 KSM for 0 kBTC, so check greater than or equal to
            chai_1.assert.isTrue(requiredCollateralForVault.toBig().gte(vault.getBackedTokens().toBig()), `Expect required collateral (${requiredCollateralForVault.toHuman()}) 
                to be greater than or equal to backed tokens (${vault.getBackedTokens().toHuman()})`);
        }
    }));
    // WARNING: this test is not idempotent
    // PRECONDITION: vault_1 must have issued some tokens against all collateral currencies
    it("should deposit and withdraw collateral", () => __awaiter(void 0, void 0, void 0, function* () {
        const prevAccount = interBtcAPI.account;
        for (const vault_1_id of vault_1_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.collateral);
            const currencyTicker = collateralCurrency.ticker;
            interBtcAPI.setAccount(vault_1);
            const amount = utils_1.newMonetaryAmount(100, collateralCurrency, true);
            const collateralizationBeforeDeposit = yield interBtcAPI.vaults.getVaultCollateralization(src_1.newAccountId(api, vault_1.address), collateralCurrency);
            yield interBtcAPI.vaults.depositCollateral(amount);
            const collateralizationAfterDeposit = yield interBtcAPI.vaults.getVaultCollateralization(src_1.newAccountId(api, vault_1.address), collateralCurrency);
            if (collateralizationBeforeDeposit === undefined || collateralizationAfterDeposit == undefined) {
                chai_1.assert.fail(`Collateralization is undefined for vault with collateral currency ${currencyTicker}
                    - potential cause: the vault may not have any issued tokens secured by ${currencyTicker}`);
                return;
            }
            chai_1.assert.isTrue(collateralizationAfterDeposit.gt(collateralizationBeforeDeposit), `Depositing did not increase collateralization (${currencyTicker} vault),
                expected ${collateralizationAfterDeposit} greater than ${collateralizationBeforeDeposit}`);
            yield interBtcAPI.vaults.withdrawCollateral(amount);
            const collateralizationAfterWithdrawal = yield interBtcAPI.vaults.getVaultCollateralization(src_1.newAccountId(api, vault_1.address), collateralCurrency);
            if (collateralizationAfterWithdrawal === undefined) {
                chai_1.assert.fail(`Collateralization is undefined for vault with collateral currency ${currencyTicker}`);
                return;
            }
            chai_1.assert.isTrue(collateralizationAfterDeposit.gt(collateralizationAfterWithdrawal), `Withdrawing did not decrease collateralization (${currencyTicker} vault), expected
                ${collateralizationAfterDeposit} greater than ${collateralizationAfterWithdrawal}`);
            // removed this assertion because it is flaky
            // TODO: figure out why / fix it (usual suspect: exchange rates change between assertions)
            // assert.equal(
            //     collateralizationBeforeDeposit.toString(),
            //     collateralizationAfterWithdrawal.toString(),
            //     `Collateralization after identical deposit and withdrawal changed (${currencyTicker} vault)`
            // );
        }
        if (prevAccount) {
            interBtcAPI.setAccount(prevAccount);
        }
    }));
    it("should getPremiumRedeemVaults after a price crash", () => __awaiter(void 0, void 0, void 0, function* () {
        chai_1.assert.isAbove(vault_3_ids.length, 0, "Precondition: Expect vault_3_ids to have length > 0");
        // TODO: Look into why requesting the full issuable amount fails, and remove the line below
        // note: divide 90% by number of loops (issue requests) expected.
        const issuableAmountModifier = 0.9 / vault_3_ids.length;
        for (const vault_3_id of vault_3_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_3_id.currencies.collateral);
            const currencyTicker = collateralCurrency.ticker;
            const vault = yield interBtcAPI.vaults.get(vault_3_id.accountId, collateralCurrency);
            let issuableAmount = yield vault.getIssuableTokens();
            issuableAmount = issuableAmount.mul(issuableAmountModifier);
            yield utils_1.issueSingle(interBtcAPI, bitcoinCoreClient, oracleAccount, issuableAmount, vault_3_id);
            const currentVaultCollateralization = yield interBtcAPI.vaults.getVaultCollateralization(src_1.newAccountId(api, vault_3.address), collateralCurrency);
            if (currentVaultCollateralization === undefined) {
                throw new Error("Collateralization is undefined");
            }
            // The factor to adjust the exchange rate by. Calculated such that the resulting collateralization
            // will be 90% of the premium redeem threshold. (e.g. 1.35 * 90% = 1.215)
            const premiumRedeemThreshold = yield interBtcAPI.vaults.getPremiumRedeemThreshold(collateralCurrency);
            const modifyExchangeRateBy = premiumRedeemThreshold.mul(0.9).div(currentVaultCollateralization);
            const initialExchangeRate = yield interBtcAPI.oracle.getExchangeRate(collateralCurrency);
            // crash the exchange rate so that the vault falls below the premium redeem threshold
            const exchangeRateValue = initialExchangeRate.toBig().div(modifyExchangeRateBy);
            const mockExchangeRate = new monetary_js_1.ExchangeRate(monetary_js_1.Bitcoin, collateralCurrency, exchangeRateValue);
            // stub the oracle API to always return the new exchange rate
            const stub = sinon_1.default
                .stub(interBtcAPI.oracle, "getExchangeRate")
                .withArgs(sinon_1.default.match.any)
                .returns(Promise.resolve(mockExchangeRate)); // "as any" to help eslint play nicely
            const premiumRedeemVaults = yield interBtcAPI.vaults.getPremiumRedeemVaults();
            // Check that the stub has indeed been called at least once
            // If not, code has changed and our assumptions when mocking the oracle API are no longer valid
            sinon_1.default.assert.called(stub);
            sinon_1.default.restore();
            // real assertions here
            chai_1.assert.isAtLeast(premiumRedeemVaults.size, 1);
            // locate the amount for the current vault
            let premiumRedeemAmount = undefined;
            for (const [vaultId, amount] of premiumRedeemVaults) {
                if ((yield utils_1.encodeVaultId(assetRegistry, vaultId)) === (yield utils_1.encodeVaultId(assetRegistry, vault_3_id))) {
                    premiumRedeemAmount = amount;
                    break;
                }
            }
            if (premiumRedeemAmount === undefined) {
                chai_1.assert.fail(`Could not locate expected premium redeem amount for vault (${currencyTicker} collateral)`);
                return;
            }
            chai_1.assert.isTrue(premiumRedeemAmount.gte(issuableAmount), `Amount available for premium redeem should be higher (${currencyTicker} vault)`);
        }
    })).timeout(10 * 60000);
    it("should getLiquidationCollateralThreshold", () => __awaiter(void 0, void 0, void 0, function* () {
        const expectedThresholdByTicker = new Map([
            [monetary_js_1.Polkadot.ticker, "1.1"],
            [monetary_js_1.Kusama.ticker, "1.1"],
            [monetary_js_1.Kintsugi.ticker, "2"],
        ]);
        for (const collateralCurrency of collateralCurrencies) {
            const currencyTicker = collateralCurrency.ticker;
            const expectedThreshold = expectedThresholdByTicker.get(currencyTicker);
            if (expectedThreshold === undefined) {
                chai_1.assert.fail(`Precondition: No expected threshold set for ${currencyTicker}`);
                return;
            }
            const threshold = yield interBtcAPI.vaults.getLiquidationCollateralThreshold(collateralCurrency);
            chai_1.assert.equal(threshold.toString(), expectedThreshold, `Liquidation collateral threshold is not ${expectedThreshold} (${currencyTicker})`);
        }
    }));
    it("should getPremiumRedeemThreshold", () => __awaiter(void 0, void 0, void 0, function* () {
        const expectedThresholdByTicker = new Map([
            [monetary_js_1.Polkadot.ticker, "1.35"],
            [monetary_js_1.Kusama.ticker, "1.35"],
            [monetary_js_1.Kintsugi.ticker, "3"],
        ]);
        for (const collateralCurrency of collateralCurrencies) {
            const currencyTicker = collateralCurrency.ticker;
            const expectedThreshold = expectedThresholdByTicker.get(currencyTicker);
            if (expectedThreshold === undefined) {
                chai_1.assert.fail(`Precondition: No expected threshold set for ${currencyTicker}`);
                return;
            }
            const threshold = yield interBtcAPI.vaults.getPremiumRedeemThreshold(collateralCurrency);
            chai_1.assert.equal(threshold.toString(), expectedThreshold, `Premium redeem threshold is not ${expectedThreshold} (${currencyTicker})`);
        }
    }));
    it("should select random vault for issue", () => __awaiter(void 0, void 0, void 0, function* () {
        const randomVault = yield interBtcAPI.vaults.selectRandomVaultIssue(utils_1.newMonetaryAmount(0, wrappedCurrency));
        chai_1.assert.isTrue(vaultIsATestVault(randomVault.accountId.toHuman()));
    }));
    it("should fail if no vault for issuing is found", () => __awaiter(void 0, void 0, void 0, function* () {
        chai_1.assert.isRejected(interBtcAPI.vaults.selectRandomVaultIssue(utils_1.newMonetaryAmount(9000000, wrappedCurrency, true)));
    }));
    it("should select random vault for redeem", () => __awaiter(void 0, void 0, void 0, function* () {
        const randomVault = yield interBtcAPI.vaults.selectRandomVaultRedeem(utils_1.newMonetaryAmount(0, wrappedCurrency));
        chai_1.assert.isTrue(vaultIsATestVault(randomVault.accountId.toHuman()));
    }));
    it("should fail if no vault for redeeming is found", () => __awaiter(void 0, void 0, void 0, function* () {
        const amount = utils_1.newMonetaryAmount(9000000, wrappedCurrency, true);
        chai_1.assert.isRejected(interBtcAPI.vaults.selectRandomVaultRedeem(amount));
    }));
    it("should fail to get vault collateralization for vault with zero collateral", () => __awaiter(void 0, void 0, void 0, function* () {
        for (const vault_1_id of vault_1_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.collateral);
            const currencyTicker = collateralCurrency.ticker;
            const vault1Id = src_1.newAccountId(api, vault_1.address);
            chai_1.assert.isRejected(interBtcAPI.vaults.getVaultCollateralization(vault1Id, collateralCurrency), `Collateralization should not be available (${currencyTicker} vault)`);
        }
    }));
    it("should get the issuable InterBtc for a vault", () => __awaiter(void 0, void 0, void 0, function* () {
        for (const vault_1_id of vault_1_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.collateral);
            const currencyTicker = collateralCurrency.ticker;
            const vault = yield interBtcAPI.vaults.get(vault_1_id.accountId, collateralCurrency);
            const issuableTokens = yield vault.getIssuableTokens();
            chai_1.assert.isTrue(issuableTokens.gt(utils_1.newMonetaryAmount(0, wrappedCurrency)), `Issuable tokens should be greater than 0 (${currencyTicker} vault)`);
        }
    }));
    it("should get the issuable InterBtc", () => __awaiter(void 0, void 0, void 0, function* () {
        const issuableInterBtc = yield interBtcAPI.vaults.getTotalIssuableAmount();
        chai_1.assert.isTrue(issuableInterBtc.gt(utils_1.newMonetaryAmount(0, wrappedCurrency)));
    }));
    // TODO: revisit after next publish why intrReward is always zero
    it.skip("should getFees", () => __awaiter(void 0, void 0, void 0, function* () {
        for (const vault_1_id of vault_1_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.collateral);
            const wrappedCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.wrapped);
            const currencyTicker = collateralCurrency.ticker;
            const vault1Id = src_1.newAccountId(api, vault_1.address);
            const feesWrapped = yield interBtcAPI.vaults.getWrappedReward(vault1Id, collateralCurrency, wrappedCurrency);
            chai_1.assert.isTrue(feesWrapped.gt(utils_1.newMonetaryAmount(0, wrappedCurrency)), `Fees should be greater than 0 (${currencyTicker} vault)`);
            const intrReward = yield interBtcAPI.vaults.getGovernanceReward(vault1Id, collateralCurrency, governanceCurrency);
            chai_1.assert.isTrue(intrReward.gt(utils_1.newMonetaryAmount(0, governanceCurrency)), `Governance reward should be greater than 0 (${currencyTicker} vault)`);
        }
    }));
    it("should getAPY", () => __awaiter(void 0, void 0, void 0, function* () {
        for (const vault_1_id of vault_1_ids) {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, vault_1_id.currencies.collateral);
            const currencyTicker = collateralCurrency.ticker;
            const apy = yield interBtcAPI.vaults.getAPY(src_1.newAccountId(api, vault_1.address), collateralCurrency);
            const apyBig = new big_js_1.default(apy);
            const apyBenchmark = new big_js_1.default("0");
            chai_1.assert.isTrue(apyBig.gte(apyBenchmark), `APY should be greater than or equal to ${apyBenchmark.toString()},
                    but was ${apyBig.toString()} (${currencyTicker} vault)`);
        }
    }));
    it("should getPunishmentFee", () => __awaiter(void 0, void 0, void 0, function* () {
        const punishmentFee = yield interBtcAPI.vaults.getPunishmentFee();
        chai_1.assert.equal(punishmentFee.toString(), "0.1");
    }));
    it("should get vault list", () => __awaiter(void 0, void 0, void 0, function* () {
        const vaults = (yield interBtcAPI.vaults.list()).map((vault) => vault.id.toHuman());
        chai_1.assert.isAbove(vaults.length, 0, "Vault list should not be empty");
    }));
    it("should disable and enable issuing with vault", () => __awaiter(void 0, void 0, void 0, function* () {
        const assertVaultStatus = (id, expectedStatus) => __awaiter(void 0, void 0, void 0, function* () {
            const collateralCurrency = yield index_1.currencyIdToMonetaryCurrency(assetRegistry, id.currencies.collateral);
            const currencyTicker = collateralCurrency.ticker;
            const { status } = yield interBtcAPI.vaults.get(id.accountId, collateralCurrency);
            const assertionMessage = `Vault with id ${id.toString()} (collateral: ${currencyTicker}) was expected to have 
                    status: ${helpers_1.vaultStatusToLabel(expectedStatus)}, but got status: ${helpers_1.vaultStatusToLabel(status)}`;
            chai_1.assert.isTrue(status === expectedStatus, assertionMessage);
        });
        const ACCEPT_NEW_ISSUES = true;
        const REJECT_NEW_ISSUES = false;
        for (const vault_1_id of vault_1_ids) {
            // Check that vault 1 is active.
            yield assertVaultStatus(vault_1_id, index_1.VaultStatusExt.Active);
            // Disables vault 1 which is active.
            yield interBtcAPI.vaults.toggleIssueRequests(vault_1_id, REJECT_NEW_ISSUES);
            // Check that vault 1 is inactive.
            yield assertVaultStatus(vault_1_id, index_1.VaultStatusExt.Inactive);
            // Re-enable issuing with vault 1.
            yield interBtcAPI.vaults.toggleIssueRequests(vault_1_id, ACCEPT_NEW_ISSUES);
            // Check that vault 1 is again active.
            yield assertVaultStatus(vault_1_id, index_1.VaultStatusExt.Active);
        }
    }));
});
//# sourceMappingURL=vaults.test.js.map