"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 index_1 = require("../../../../../src/index");
const factory_1 = require("../../../../../src/factory");
const config_1 = require("../../../../config");
const helpers_1 = require("../../../../utils/helpers");
const chai_1 = require("../../../../chai");
const sinon_1 = __importDefault(require("sinon"));
const monetary_js_1 = require("@interlay/monetary-js");
describe("Loans", () => {
    const approx10Blocks = 10 * helpers_1.APPROX_BLOCK_TIME_MS;
    let api;
    let keyring;
    let userInterBtcAPI;
    let user2InterBtcAPI;
    let sudoInterBtcAPI;
    let TransactionAPI;
    let LoansAPI;
    let userAccount;
    let user2Account;
    let sudoAccount;
    let userAccountId;
    let user2AccountId;
    let lendTokenId;
    let underlyingCurrencyId;
    let underlyingCurrency;
    before(function () {
        return __awaiter(this, void 0, void 0, function* () {
            this.timeout(approx10Blocks);
            api = yield factory_1.createSubstrateAPI(config_1.PARACHAIN_ENDPOINT);
            keyring = new api_1.Keyring({ type: "sr25519" });
            userAccount = keyring.addFromUri(config_1.USER_1_URI);
            user2Account = keyring.addFromUri(config_1.USER_2_URI);
            userInterBtcAPI = new index_1.DefaultInterBtcApi(api, "regtest", userAccount, config_1.ESPLORA_BASE_PATH);
            user2InterBtcAPI = new index_1.DefaultInterBtcApi(api, "regtest", user2Account, config_1.ESPLORA_BASE_PATH);
            sudoAccount = keyring.addFromUri(config_1.SUDO_URI);
            sudoInterBtcAPI = new index_1.DefaultInterBtcApi(api, "regtest", sudoAccount, config_1.ESPLORA_BASE_PATH);
            userAccountId = index_1.newAccountId(api, userAccount.address);
            user2AccountId = index_1.newAccountId(api, user2Account.address);
            TransactionAPI = new index_1.DefaultTransactionAPI(api, userAccount);
            LoansAPI = new index_1.DefaultLoansAPI(api, userInterBtcAPI.assetRegistry, TransactionAPI);
            // Add market for governance currency.
            underlyingCurrencyId = sudoInterBtcAPI.api.consts.escrowRewards.getNativeCurrencyId;
            underlyingCurrency = sudoInterBtcAPI.getGovernanceCurrency();
            lendTokenId = index_1.newCurrencyId(sudoInterBtcAPI.api, { lendToken: { id: 1 } });
            const percentageToPermill = (percentage) => percentage * 10000;
            const marketData = {
                collateralFactor: percentageToPermill(50),
                liquidationThreshold: percentageToPermill(55),
                reserveFactor: percentageToPermill(15),
                closeFactor: percentageToPermill(50),
                liquidateIncentive: "1100000000000000000",
                liquidateIncentiveReservedFactor: percentageToPermill(3),
                rateModel: {
                    Jump: {
                        baseRate: "20000000000000000",
                        jumpRate: "100000000000000000",
                        fullRate: "320000000000000000",
                        jumpUtilization: percentageToPermill(80),
                    },
                },
                state: "Pending",
                supplyCap: "5000000000000000000000",
                borrowCap: "5000000000000000000000",
                lendTokenId,
            };
            const addMarketExtrinsic = sudoInterBtcAPI.api.tx.loans.addMarket(underlyingCurrencyId, marketData);
            const activateMarketExtrinsic = sudoInterBtcAPI.api.tx.loans.activateMarket(underlyingCurrencyId);
            const addMarketAndActivateExtrinsic = sudoInterBtcAPI.api.tx.utility.batchAll([
                addMarketExtrinsic,
                activateMarketExtrinsic,
            ]);
            const [eventFound] = yield Promise.all([
                helpers_1.waitForEvent(sudoInterBtcAPI, sudoInterBtcAPI.api.events.sudo.Sudid, false, approx10Blocks),
                api.tx.sudo.sudo(addMarketAndActivateExtrinsic).signAndSend(sudoAccount),
            ]);
            chai_1.expect(eventFound, `Sudo event to create new market not found - timed out after ${approx10Blocks} ms`).to.be.true;
        });
    });
    after(() => __awaiter(void 0, void 0, void 0, function* () {
        api.disconnect();
    }));
    afterEach(() => {
        // discard any stubbed methods after each test
        sinon_1.default.restore();
    });
    describe("getLendTokens", () => {
        it("should get lend token for each existing market", () => __awaiter(void 0, void 0, void 0, function* () {
            const [markets, lendTokens] = yield Promise.all([
                api.query.loans.markets.entries(),
                userInterBtcAPI.loans.getLendTokens(),
            ]);
            const marketsUnderlyingCurrencyId = markets[0][0].args[0];
            chai_1.expect(markets.length).to.be.equal(lendTokens.length);
            chai_1.expect(marketsUnderlyingCurrencyId.eq(underlyingCurrencyId)).to.be.true;
        }));
        it("should return LendToken in correct format - 'q' prefix, correct id", () => __awaiter(void 0, void 0, void 0, function* () {
            // Requires first market to be initialized for governance currency.
            const lendTokens = yield userInterBtcAPI.loans.getLendTokens();
            const lendToken = lendTokens[0];
            // Should have same amount of decimals as underlying currency.
            chai_1.expect(lendToken.decimals).to.be.eq(underlyingCurrency.decimals);
            // Should add 'q' prefix.
            chai_1.expect(lendToken.name).to.be.eq(`q${underlyingCurrency.name}`);
            chai_1.expect(lendToken.ticker).to.be.eq(`q${underlyingCurrency.ticker}`);
            chai_1.expect(lendToken.lendToken.id).to.be.eq(lendTokenId.asLendToken.toNumber());
        }));
        it("should return empty array if no market exists", () => __awaiter(void 0, void 0, void 0, function* () {
            // Mock empty list returned from chain.
            sinon_1.default.stub(LoansAPI, "getLoansMarketsEntries").returns(Promise.resolve([]));
            const lendTokens = yield LoansAPI.getLendTokens();
            chai_1.expect(lendTokens).to.be.empty;
            sinon_1.default.restore();
            sinon_1.default.reset();
        }));
    });
    describe("getLendPositionsOfAccount", () => {
        let lendAmount;
        before(function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                lendAmount = index_1.newMonetaryAmount(1, underlyingCurrency, true);
                yield userInterBtcAPI.loans.lend(underlyingCurrency, lendAmount);
            });
        });
        it("should get all lend positions of account in correct format", () => __awaiter(void 0, void 0, void 0, function* () {
            var _a;
            const [lendPosition] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
            chai_1.expect(lendPosition.amount.toString()).to.be.equal(lendAmount.toString());
            chai_1.expect(lendPosition.currency).to.be.equal(underlyingCurrency);
            chai_1.expect(lendPosition.isCollateral).to.be.false;
            // No interest & reward because no one borrowed yet and no reward subsidies enabled.
            chai_1.expect(lendPosition.earnedInterest.toBig().eq(0)).to.be.true;
            chai_1.expect((_a = lendPosition.earnedReward) === null || _a === void 0 ? void 0 : _a.toBig().eq(0)).to.be.true;
            // TODO: add tests for more markets
        }));
        it("should get correct data after position is enabled as collateral", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                yield userInterBtcAPI.loans.enableAsCollateral(underlyingCurrency);
                const [lendPosition] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                chai_1.expect(lendPosition.isCollateral).to.be.true;
            });
        });
        it("should get empty array when no lend position exists for account", () => __awaiter(void 0, void 0, void 0, function* () {
            const lendPositions = yield user2InterBtcAPI.loans.getLendPositionsOfAccount(user2AccountId);
            chai_1.expect(lendPositions).to.be.empty;
        }));
        it.skip("should get correct interest amount", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                // Borrows underlying currency with 2nd user account
                const user2LendAmount = index_1.newMonetaryAmount(100, underlyingCurrency, true);
                const user2BorrowAmount = index_1.newMonetaryAmount(20, underlyingCurrency, true);
                const user2LendExtrinsic = user2InterBtcAPI.api.tx.loans.mint(underlyingCurrencyId, user2LendAmount.toString(true));
                const user2CollateralExtrinsic = user2InterBtcAPI.api.tx.loans.depositAllCollateral(underlyingCurrencyId);
                const user2BorrowExtrinsic = user2InterBtcAPI.api.tx.loans.borrow(underlyingCurrencyId, user2BorrowAmount.toString(true));
                const [eventFound] = yield Promise.all([
                    helpers_1.waitForEvent(user2InterBtcAPI, user2InterBtcAPI.api.events.loans.Borrowed),
                    user2InterBtcAPI.api.tx.utility.batchAll([
                        user2LendExtrinsic,
                        user2CollateralExtrinsic,
                        user2BorrowExtrinsic,
                    ]),
                ]);
                chai_1.expect(eventFound, "No event found for depositing collateral");
                // TODO: cannot submit timestamp.set - gettin error
                //   'RpcError: 1010: Invalid Transaction: Transaction dispatch is mandatory; transactions may not have mandatory dispatches.'
                // Solution: Move APR calculation to separate function and unit test it without using actual parachain value,
                // mock the parachain response for this.
                // Manipulates time to accredit interest.
                const timestamp1MonthInFuture = Date.now() + 1000 * 60 * 60 * 24 * 30;
                const setTimeToFutureExtrinsic = sudoInterBtcAPI.api.tx.timestamp.set(timestamp1MonthInFuture);
                const [sudoEventFound] = yield Promise.all([
                    helpers_1.waitForEvent(sudoInterBtcAPI, sudoInterBtcAPI.api.events.sudo.Sudid, false, approx10Blocks),
                    api.tx.sudo.sudo(setTimeToFutureExtrinsic).signAndSend(sudoAccount),
                ]);
                chai_1.expect(sudoEventFound, `Sudo event to manipulate time not found - timed out after ${approx10Blocks} ms`).to
                    .be.true;
                const [lendPosition] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                // test printing out to see earned Interest value
                console.log(lendPosition.earnedInterest.toHuman());
            });
        });
    });
    describe("getUnderlyingCurrencyFromLendTokenId", () => {
        it("should return correct underlying currency for lend token", () => __awaiter(void 0, void 0, void 0, function* () {
            const returnedUnderlyingCurrency = yield userInterBtcAPI.loans.getUnderlyingCurrencyFromLendTokenId(lendTokenId);
            chai_1.expect(returnedUnderlyingCurrency).to.deep.equal(underlyingCurrency);
        }));
        it("should throw when lend token id is of non-existing currency", () => __awaiter(void 0, void 0, void 0, function* () {
            const invalidLendTokenId = (lendTokenId = index_1.newCurrencyId(sudoInterBtcAPI.api, {
                lendToken: { id: 999 },
            }));
            yield chai_1.expect(userInterBtcAPI.loans.getUnderlyingCurrencyFromLendTokenId(invalidLendTokenId)).to.be.rejected;
        }));
    });
    describe("lend", () => {
        it("should lend expected amount of currency to protocol", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                const [{ amount: lendAmountBefore }] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                const lendAmount = index_1.newMonetaryAmount(100, underlyingCurrency, true);
                yield userInterBtcAPI.loans.lend(underlyingCurrency, lendAmount);
                const [{ amount: lendAmountAfter }] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                const actuallyLentAmount = lendAmountAfter.sub(lendAmountBefore);
                // Check that lent amount is same as sent amount
                chai_1.expect(actuallyLentAmount.eq(lendAmount)).to.be.true;
            });
        });
        it("should throw if trying to lend from inactive market", () => __awaiter(void 0, void 0, void 0, function* () {
            const inactiveUnderlyingCurrency = monetary_js_1.Kusama;
            const amount = index_1.newMonetaryAmount(1, inactiveUnderlyingCurrency);
            yield chai_1.expect(userInterBtcAPI.loans.lend(inactiveUnderlyingCurrency, amount)).to.be.rejected;
        }));
        it("should throw if trying to lend more than free balance", () => __awaiter(void 0, void 0, void 0, function* () {
            const tooBigAmount = index_1.newMonetaryAmount("1000000000000000000000000000000000000", underlyingCurrency);
            yield chai_1.expect(userInterBtcAPI.loans.lend(underlyingCurrency, tooBigAmount)).to.be.rejected;
        }));
    });
    describe("withdraw", () => {
        before(function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                const [eventFound] = yield Promise.all([
                    helpers_1.waitForEvent(userInterBtcAPI, api.events.loans.WithdrawCollateral, false, approx10Blocks),
                    userInterBtcAPI.api.tx.loans.withdrawAllCollateral(underlyingCurrencyId).signAndSend(userAccount),
                ]);
                chai_1.expect(eventFound, "No event found for withdrawing all collateral").to.be.true;
            });
        });
        it("should withdraw part of lent amount", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                const [{ amount: lendAmountBefore }] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                const amountToWithdraw = index_1.newMonetaryAmount(1, underlyingCurrency, true);
                yield userInterBtcAPI.loans.withdraw(underlyingCurrency, amountToWithdraw);
                const [{ amount: lendAmountAfter }] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                const actuallyWithdrawnAmount = lendAmountBefore.sub(lendAmountAfter).toBig().round(2);
                chai_1.expect(actuallyWithdrawnAmount.eq(amountToWithdraw.toBig()), 
                // eslint-disable-next-line max-len
                `Expected withdrawn amount: ${amountToWithdraw.toHuman()} is different from the actual amount: ${actuallyWithdrawnAmount.toString()}!`).to.be.true;
            });
        });
    });
    describe("withdrawAll", () => {
        it("should withdraw full amount from lending protocol", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                yield userInterBtcAPI.loans.withdrawAll(underlyingCurrency);
                const lendPositions = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                chai_1.expect(lendPositions, "Expected to withdraw full amount and close position!").to.be.empty;
            });
        });
    });
    describe("enableAsCollateral", () => {
        it("should enable lend position as collateral", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                const lendAmount = index_1.newMonetaryAmount(1, underlyingCurrency, true);
                yield userInterBtcAPI.loans.lend(underlyingCurrency, lendAmount);
                yield userInterBtcAPI.loans.enableAsCollateral(underlyingCurrency);
                const [{ isCollateral }] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                chai_1.expect(isCollateral).to.be.true;
            });
        });
    });
    describe("disableAsCollateral", () => {
        it("should disable enabled collateral position if there are no borrows", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                yield userInterBtcAPI.loans.disableAsCollateral(underlyingCurrency);
                const [{ isCollateral }] = yield userInterBtcAPI.loans.getLendPositionsOfAccount(userAccountId);
                chai_1.expect(isCollateral).to.be.false;
            });
        });
    });
    describe("getLoanAssets", () => {
        it("should get loan assets in correct format", () => __awaiter(void 0, void 0, void 0, function* () {
            const loanAssets = yield userInterBtcAPI.loans.getLoanAssets();
            const underlyingCurrencyLoanAsset = loanAssets[underlyingCurrency.ticker];
            chai_1.expect(underlyingCurrencyLoanAsset).is.not.undefined;
            chai_1.expect(underlyingCurrencyLoanAsset.currency).to.be.deep.equal(underlyingCurrency);
            chai_1.expect(underlyingCurrencyLoanAsset.isActive).to.be.true;
            // TODO: add more tests to check data validity
        }));
        it("should return empty object if there are no added markets", () => __awaiter(void 0, void 0, void 0, function* () {
            // Mock empty list returned from chain.
            sinon_1.default.stub(LoansAPI, "getLoansMarketsEntries").returns(Promise.resolve([]));
            const loanAssets = yield LoansAPI.getLoanAssets();
            chai_1.expect(loanAssets).to.be.empty;
            sinon_1.default.restore();
            sinon_1.default.reset();
        }));
    });
    describe("claimAllSubsidyRewards", () => {
        it("should claim all subsidy rewards", () => {
            //TODO
        });
    });
    describe("borrow", () => {
        it("should borrow specified amount", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                const lendAmount = index_1.newMonetaryAmount(100, underlyingCurrency, true);
                const borrowAmount = index_1.newMonetaryAmount(1, underlyingCurrency, true);
                yield user2InterBtcAPI.loans.lend(underlyingCurrency, lendAmount);
                yield user2InterBtcAPI.loans.enableAsCollateral(underlyingCurrency);
                yield user2InterBtcAPI.loans.borrow(underlyingCurrency, borrowAmount);
                const [{ amount }] = yield user2InterBtcAPI.loans.getBorrowPositionsOfAccount(user2AccountId);
                const roundedAmount = amount.toBig().round(2);
                chai_1.expect(roundedAmount.eq(borrowAmount.toBig()), `Expected borrowed amount to equal ${borrowAmount.toString()}, but it is ${amount.toString()}.`).to.be.true;
            });
        });
    });
    describe("repay", () => {
        it("should repay specified amount", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                const repayAmount = index_1.newMonetaryAmount(0.5, underlyingCurrency, true);
                const [{ amount: borrowAmountBefore }] = yield user2InterBtcAPI.loans.getBorrowPositionsOfAccount(user2AccountId);
                yield user2InterBtcAPI.loans.repay(underlyingCurrency, repayAmount);
                const [{ amount: borrowAmountAfter }] = yield user2InterBtcAPI.loans.getBorrowPositionsOfAccount(user2AccountId);
                const borrowAmountAfterRounded = borrowAmountAfter.toBig().round(2);
                const expectedRemainingAmount = borrowAmountBefore.sub(repayAmount);
                chai_1.expect(borrowAmountAfterRounded.eq(expectedRemainingAmount.toBig()), `Expected remaining borrow amount to equal ${expectedRemainingAmount.toString()}, but it is ${borrowAmountAfter.toString()}`).to.be.true;
            });
        });
    });
    describe("repayAll", () => {
        it("should repay whole loan", function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                yield user2InterBtcAPI.loans.repayAll(underlyingCurrency);
                const borrowPositions = yield user2InterBtcAPI.loans.getBorrowPositionsOfAccount(user2AccountId);
                chai_1.expect(borrowPositions, `Expected to repay full borrow position, but positions: ${borrowPositions} were found`).to.be.empty;
            });
        });
    });
    describe("getBorrowPositionsOfAccount", () => {
        before(function () {
            return __awaiter(this, void 0, void 0, function* () {
                this.timeout(approx10Blocks);
                // TODO:borrow
            });
        });
        it("should get borrow positions in correct format", function () {
            return __awaiter(this, void 0, void 0, function* () {
                //TODO
            });
        });
    });
});
//# sourceMappingURL=loans.test.js.map