"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const web3_js_1 = require("@solana/web3.js");
const bn_js_1 = __importDefault(require("bn.js"));
const chai_1 = require("chai");
const console_1 = require("console");
const I80F48_1 = require("../numbers/I80F48");
const utils_1 = require("../utils");
const mangoAccount_1 = require("./mangoAccount");
describe('Mango Account', () => {
    const mangoAccount = new mangoAccount_1.MangoAccount(web3_js_1.PublicKey.default, web3_js_1.PublicKey.default, web3_js_1.PublicKey.default, [], web3_js_1.PublicKey.default, 0, false, false, new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(100), new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), 0, 0, new bn_js_1.default(0), [], [], [], [], [], new Map());
    it('test calculateEquivalentSourceAmount', (done) => {
        const mockedSOLBank = {
            mintDecimals: 9,
            price: I80F48_1.I80F48.fromNumber(0.09870201707999726),
        };
        const SOLDepositLimitLeft = new bn_js_1.default((0, utils_1.toNative)(0.5, mockedSOLBank.mintDecimals));
        const mockedUSDCBank = {
            mintDecimals: 6,
            price: I80F48_1.I80F48.fromNumber(1.0000536899999979),
        };
        const USDCDepositLimitLeft = new bn_js_1.default((0, utils_1.toNative)(1, mockedUSDCBank.mintDecimals));
        const maxSourceForUSDCTarget = mangoAccount.calculateEquivalentSourceAmount(mockedSOLBank, mockedUSDCBank, USDCDepositLimitLeft);
        const maxSourceForSOLTarget = mangoAccount.calculateEquivalentSourceAmount(mockedUSDCBank, mockedSOLBank, SOLDepositLimitLeft);
        // Expected u can sell max 0.01 sol for 1 USDC
        (0, chai_1.expect)((0, utils_1.toUiDecimals)(maxSourceForUSDCTarget, mockedSOLBank.mintDecimals).toFixed(2) === '0.01').to.be.true;
        // Expected u can buy max of 0.49 SOL for 49 USDC
        (0, chai_1.expect)((0, utils_1.toUiDecimals)(maxSourceForSOLTarget, mockedUSDCBank.mintDecimals).toFixed(0) === '49').to.be.true;
        done();
    });
});
describe('maxWithdraw', () => {
    const protoAccount = new mangoAccount_1.MangoAccount(web3_js_1.PublicKey.default, web3_js_1.PublicKey.default, web3_js_1.PublicKey.default, [], web3_js_1.PublicKey.default, 0, false, false, new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), new bn_js_1.default(0), 0, 0, new bn_js_1.default(0), [], [], [], [], [], new Map());
    protoAccount.tokens.push(new mangoAccount_1.TokenPosition((0, I80F48_1.ZERO_I80F48)(), 0, 0, (0, I80F48_1.ZERO_I80F48)(), 0, 0));
    protoAccount.tokens.push(new mangoAccount_1.TokenPosition((0, I80F48_1.ZERO_I80F48)(), 1, 0, (0, I80F48_1.ZERO_I80F48)(), 0, 0));
    const protoBank = {
        vault: web3_js_1.PublicKey.default,
        mint: web3_js_1.PublicKey.default,
        tokenIndex: 0,
        price: (0, I80F48_1.ONE_I80F48)(),
        getAssetPrice() {
            return this.price;
        },
        getLiabPrice() {
            return this.price;
        },
        stablePriceModel: { stablePrice: (0, I80F48_1.ONE_I80F48)() },
        initAssetWeight: I80F48_1.I80F48.fromNumber(0.8),
        initLiabWeight: I80F48_1.I80F48.fromNumber(1.2),
        maintWeights() {
            return [I80F48_1.I80F48.fromNumber(0.9), I80F48_1.I80F48.fromNumber(1.1)];
        },
        scaledInitAssetWeight(price) {
            return this.initAssetWeight;
        },
        scaledInitLiabWeight(price) {
            return this.initLiabWeight;
        },
        loanOriginationFeeRate: I80F48_1.I80F48.fromNumber(0.001),
        minVaultToDepositsRatio: I80F48_1.I80F48.fromNumber(0.1),
        depositIndex: I80F48_1.I80F48.fromNumber(1000000),
        borrowIndex: I80F48_1.I80F48.fromNumber(1000000),
        indexedDeposits: I80F48_1.I80F48.fromNumber(0),
        indexedBorrows: I80F48_1.I80F48.fromNumber(0),
        nativeDeposits() {
            return this.depositIndex.mul(this.indexedDeposits);
        },
        nativeBorrows() {
            return this.borrowIndex.mul(this.indexedBorrows);
        },
        areBorrowsReduceOnly() {
            return false;
        },
    };
    function makeGroup(bank0, bank1, vaultAmount) {
        return {
            getFirstBankByMint(mint) {
                if (mint.equals(bank0.mint)) {
                    return bank0;
                }
                else if (mint.equals(bank1.mint)) {
                    return bank1;
                }
            },
            getFirstBankByTokenIndex(tokenIndex) {
                return [bank0, bank1][tokenIndex];
            },
            getFirstBankForPerpSettlement() {
                return bank0;
            },
            vaultAmountsMap: new Map([
                [bank0.vault.toBase58(), new bn_js_1.default(vaultAmount)],
            ]),
        };
    }
    function setup(vaultAmount) {
        const account = (0, utils_1.deepClone)(protoAccount);
        const bank0 = (0, utils_1.deepClone)(protoBank);
        const bank1 = (0, utils_1.deepClone)(protoBank);
        bank1.tokenIndex = 1;
        bank1.mint = web3_js_1.PublicKey.unique();
        bank1.initAssetWeight = (0, I80F48_1.ONE_I80F48)();
        bank1.initLiabWeight = (0, I80F48_1.ONE_I80F48)();
        const group = makeGroup(bank0, bank1, vaultAmount);
        return [group, bank0, bank1, account];
    }
    function deposit(bank, account, amount) {
        const amountV = I80F48_1.I80F48.fromNumber(amount);
        const indexedAmount = amountV.div(bank.depositIndex);
        if (indexedAmount.mul(bank.depositIndex).lt(amountV)) {
            const delta = new I80F48_1.I80F48(new bn_js_1.default(1));
            indexedAmount.iadd(delta);
        }
        bank.indexedDeposits.iadd(indexedAmount);
        const tp = account.tokens[bank.tokenIndex];
        (0, console_1.assert)(!tp.indexedPosition.isNeg());
        tp.indexedPosition.iadd(indexedAmount);
    }
    function borrow(bank, account, amount) {
        const indexedAmount = I80F48_1.I80F48.fromNumber(amount).div(bank.borrowIndex);
        bank.indexedBorrows.iadd(indexedAmount);
        const tp = account.tokens[bank.tokenIndex];
        (0, console_1.assert)(!tp.indexedPosition.isPos());
        tp.indexedPosition.isub(indexedAmount);
    }
    function maxWithdraw(group, account) {
        return account
            .getMaxWithdrawWithBorrowForToken(group, web3_js_1.PublicKey.default)
            .toNumber();
    }
    it('full withdraw', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        deposit(bank0, account, 100);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(100);
        done();
    });
    it('full withdraw limited vault', (done) => {
        const [group, bank0, bank1, account] = setup(90);
        deposit(bank0, account, 100);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(90);
        done();
    });
    it('full withdraw limited utilization', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        const other = (0, utils_1.deepClone)(account);
        deposit(bank0, account, 100);
        borrow(bank0, other, 50);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(50);
        done();
    });
    it('withdraw limited health', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        deposit(bank0, account, 100);
        borrow(bank1, account, 50);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(Math.floor(100 - 50 / 0.8));
        done();
    });
    it('pure borrow', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        const other = (0, utils_1.deepClone)(account);
        deposit(bank0, other, 1000); // so there's something to borrow
        deposit(bank1, account, 100);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(Math.floor(100 / 1.2));
        done();
    });
    it('pure borrow limited utilization', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        const other = (0, utils_1.deepClone)(account);
        deposit(bank0, other, 50);
        deposit(bank1, account, 100);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(44); // due to origination fees!
        bank0.loanOriginationFeeRate = (0, I80F48_1.ZERO_I80F48)();
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(45);
        done();
    });
    it('withdraw and borrow', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        const other = (0, utils_1.deepClone)(account);
        deposit(bank0, account, 100);
        deposit(bank1, account, 100);
        deposit(bank0, other, 10000);
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(100 + Math.floor(100 / 1.2));
        done();
    });
    it('withdraw limited health and scaling', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        bank0.scaledInitAssetWeight = function (price) {
            const startScale = I80F48_1.I80F48.fromNumber(50);
            if (this.nativeDeposits().gt(startScale)) {
                return this.initAssetWeight.div(this.nativeDeposits().div(startScale));
            }
            return this.initAssetWeight;
        };
        const other = (0, utils_1.deepClone)(account);
        deposit(bank0, other, 100);
        deposit(bank0, account, 200);
        borrow(bank1, account, 20);
        // initial account health = 200 * 0.8 * 50 / 300 - 20 = 6.66
        // zero account health = 100 * 0.8 * 50 / 200 - 20 = 0
        // so can withdraw 100
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(100);
        done();
    });
    it('borrow limited health and scaling', (done) => {
        const [group, bank0, bank1, account] = setup(1000000);
        bank0.scaledInitLiabWeight = function (price) {
            const startScale = I80F48_1.I80F48.fromNumber(50);
            if (this.nativeBorrows().gt(startScale)) {
                return this.initLiabWeight.mul(this.nativeBorrows().div(startScale));
            }
            return this.initLiabWeight;
        };
        const other = (0, utils_1.deepClone)(account);
        deposit(bank0, other, 100);
        deposit(bank1, account, 100);
        // -64*1.2*64/50+100 = 1.69
        // -65*1.2*65/50+100 = -1.4
        (0, chai_1.expect)(maxWithdraw(group, account)).equal(64);
        done();
    });
});
