"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MintInfo = exports.Bank = void 0;
const anchor_1 = require("@coral-xyz/anchor");
const bytes_1 = require("@coral-xyz/anchor/dist/cjs/utils/bytes");
const web3_js_1 = require("@solana/web3.js");
const I80F48_1 = require("../numbers/I80F48");
const utils_1 = require("../utils");
const oracle_1 = require("./oracle");
class Bank {
    publicKey;
    group;
    mint;
    vault;
    oracle;
    stablePriceModel;
    indexLastUpdated;
    bankRateLastUpdated;
    flashLoanTokenAccountInitial;
    flashLoanApprovedAmount;
    tokenIndex;
    mintDecimals;
    bankNum;
    minVaultToDepositsRatio;
    netBorrowLimitWindowSizeTs;
    lastNetBorrowsWindowStartTs;
    netBorrowLimitPerWindowQuote;
    netBorrowsInWindow;
    borrowWeightScaleStartQuote;
    depositWeightScaleStartQuote;
    reduceOnly;
    forceClose;
    feesWithdrawn;
    tokenConditionalSwapTakerFeeRate;
    tokenConditionalSwapMakerFeeRate;
    flashLoanSwapFeeRate;
    interestTargetUtilization;
    interestCurveScaling;
    potentialSerumTokens;
    maintWeightShiftStart;
    maintWeightShiftEnd;
    fallbackOracle;
    depositLimit;
    allowAssetLiquidation;
    collateralFeePerDay;
    forceWithdraw;
    name;
    oracleConfig;
    depositIndex;
    borrowIndex;
    indexedDeposits;
    indexedBorrows;
    avgUtilization;
    adjustmentFactor;
    maxRate;
    rate0;
    rate1;
    util0;
    util1;
    _price;
    _uiPrice;
    _oracleLastUpdatedSlot;
    _oracleLastKnownDeviation;
    _oracleProvider;
    collectedFeesNative;
    loanFeeRate;
    loanOriginationFeeRate;
    initAssetWeight;
    maintAssetWeight;
    initLiabWeight;
    maintLiabWeight;
    liquidationFee;
    dust;
    maintWeightShiftDurationInv;
    maintWeightShiftAssetTarget;
    maintWeightShiftLiabTarget;
    zeroUtilRate;
    platformLiquidationFee;
    collectedLiquidationFees;
    collectedCollateralFees;
    static from(publicKey, obj) {
        return new Bank(publicKey, obj.group, obj.name, obj.mint, obj.vault, obj.oracle, obj.oracleConfig, obj.stablePriceModel, obj.depositIndex, obj.borrowIndex, obj.indexedDeposits, obj.indexedBorrows, obj.indexLastUpdated, obj.bankRateLastUpdated, obj.avgUtilization, obj.adjustmentFactor, obj.util0, obj.rate0, obj.util1, obj.rate1, obj.maxRate, obj.collectedFeesNative, obj.loanOriginationFeeRate, obj.loanFeeRate, obj.maintAssetWeight, obj.initAssetWeight, obj.maintLiabWeight, obj.initLiabWeight, obj.liquidationFee, obj.dust, obj.flashLoanTokenAccountInitial, obj.flashLoanApprovedAmount, obj.tokenIndex, obj.mintDecimals, obj.bankNum, obj.minVaultToDepositsRatio, obj.netBorrowLimitWindowSizeTs, obj.lastNetBorrowsWindowStartTs, obj.netBorrowLimitPerWindowQuote, obj.netBorrowsInWindow, obj.borrowWeightScaleStartQuote, obj.depositWeightScaleStartQuote, obj.reduceOnly, obj.forceClose == 1, obj.feesWithdrawn, obj.tokenConditionalSwapTakerFeeRate, obj.tokenConditionalSwapMakerFeeRate, obj.flashLoanSwapFeeRate, obj.interestTargetUtilization, obj.interestCurveScaling, obj.potentialSerumTokens, obj.maintWeightShiftStart, obj.maintWeightShiftEnd, obj.maintWeightShiftDurationInv, obj.maintWeightShiftAssetTarget, obj.maintWeightShiftLiabTarget, obj.fallbackOracle, obj.depositLimit, obj.zeroUtilRate, obj.platformLiquidationFee, obj.collectedLiquidationFees, obj.disableAssetLiquidation == 0, obj.collectedCollateralFees, obj.collateralFeePerDay, obj.forceWithdraw == 1);
    }
    constructor(publicKey, group, name, mint, vault, oracle, oracleConfig, stablePriceModel, depositIndex, borrowIndex, indexedDeposits, indexedBorrows, indexLastUpdated, bankRateLastUpdated, avgUtilization, adjustmentFactor, util0, rate0, util1, rate1, maxRate, collectedFeesNative, loanOriginationFeeRate, loanFeeRate, maintAssetWeight, initAssetWeight, maintLiabWeight, initLiabWeight, liquidationFee, dust, flashLoanTokenAccountInitial, flashLoanApprovedAmount, tokenIndex, mintDecimals, bankNum, minVaultToDepositsRatio, netBorrowLimitWindowSizeTs, lastNetBorrowsWindowStartTs, netBorrowLimitPerWindowQuote, netBorrowsInWindow, borrowWeightScaleStartQuote, depositWeightScaleStartQuote, reduceOnly, forceClose, feesWithdrawn, tokenConditionalSwapTakerFeeRate, tokenConditionalSwapMakerFeeRate, flashLoanSwapFeeRate, interestTargetUtilization, interestCurveScaling, potentialSerumTokens, maintWeightShiftStart, maintWeightShiftEnd, maintWeightShiftDurationInv, maintWeightShiftAssetTarget, maintWeightShiftLiabTarget, fallbackOracle, depositLimit, zeroUtilRate, platformLiquidationFee, collectedLiquidationFees, allowAssetLiquidation, collectedCollateralFees, collateralFeePerDay, forceWithdraw) {
        this.publicKey = publicKey;
        this.group = group;
        this.mint = mint;
        this.vault = vault;
        this.oracle = oracle;
        this.stablePriceModel = stablePriceModel;
        this.indexLastUpdated = indexLastUpdated;
        this.bankRateLastUpdated = bankRateLastUpdated;
        this.flashLoanTokenAccountInitial = flashLoanTokenAccountInitial;
        this.flashLoanApprovedAmount = flashLoanApprovedAmount;
        this.tokenIndex = tokenIndex;
        this.mintDecimals = mintDecimals;
        this.bankNum = bankNum;
        this.minVaultToDepositsRatio = minVaultToDepositsRatio;
        this.netBorrowLimitWindowSizeTs = netBorrowLimitWindowSizeTs;
        this.lastNetBorrowsWindowStartTs = lastNetBorrowsWindowStartTs;
        this.netBorrowLimitPerWindowQuote = netBorrowLimitPerWindowQuote;
        this.netBorrowsInWindow = netBorrowsInWindow;
        this.borrowWeightScaleStartQuote = borrowWeightScaleStartQuote;
        this.depositWeightScaleStartQuote = depositWeightScaleStartQuote;
        this.reduceOnly = reduceOnly;
        this.forceClose = forceClose;
        this.feesWithdrawn = feesWithdrawn;
        this.tokenConditionalSwapTakerFeeRate = tokenConditionalSwapTakerFeeRate;
        this.tokenConditionalSwapMakerFeeRate = tokenConditionalSwapMakerFeeRate;
        this.flashLoanSwapFeeRate = flashLoanSwapFeeRate;
        this.interestTargetUtilization = interestTargetUtilization;
        this.interestCurveScaling = interestCurveScaling;
        this.potentialSerumTokens = potentialSerumTokens;
        this.maintWeightShiftStart = maintWeightShiftStart;
        this.maintWeightShiftEnd = maintWeightShiftEnd;
        this.fallbackOracle = fallbackOracle;
        this.depositLimit = depositLimit;
        this.allowAssetLiquidation = allowAssetLiquidation;
        this.collateralFeePerDay = collateralFeePerDay;
        this.forceWithdraw = forceWithdraw;
        this.name = bytes_1.utf8.decode(new Uint8Array(name)).split('\x00')[0];
        this.oracleConfig = {
            confFilter: I80F48_1.I80F48.from(oracleConfig.confFilter),
            maxStalenessSlots: oracleConfig.maxStalenessSlots,
        };
        this.depositIndex = I80F48_1.I80F48.from(depositIndex);
        this.borrowIndex = I80F48_1.I80F48.from(borrowIndex);
        this.indexedDeposits = I80F48_1.I80F48.from(indexedDeposits);
        this.indexedBorrows = I80F48_1.I80F48.from(indexedBorrows);
        this.avgUtilization = I80F48_1.I80F48.from(avgUtilization);
        this.adjustmentFactor = I80F48_1.I80F48.from(adjustmentFactor);
        this.maxRate = I80F48_1.I80F48.from(maxRate);
        this.util0 = I80F48_1.I80F48.from(util0);
        this.rate0 = I80F48_1.I80F48.from(rate0);
        this.util1 = I80F48_1.I80F48.from(util1);
        this.rate1 = I80F48_1.I80F48.from(rate1);
        this.collectedFeesNative = I80F48_1.I80F48.from(collectedFeesNative);
        this.loanFeeRate = I80F48_1.I80F48.from(loanFeeRate);
        this.loanOriginationFeeRate = I80F48_1.I80F48.from(loanOriginationFeeRate);
        this.maintAssetWeight = I80F48_1.I80F48.from(maintAssetWeight);
        this.initAssetWeight = I80F48_1.I80F48.from(initAssetWeight);
        this.maintLiabWeight = I80F48_1.I80F48.from(maintLiabWeight);
        this.initLiabWeight = I80F48_1.I80F48.from(initLiabWeight);
        this.liquidationFee = I80F48_1.I80F48.from(liquidationFee);
        this.dust = I80F48_1.I80F48.from(dust);
        this.maintWeightShiftDurationInv = I80F48_1.I80F48.from(maintWeightShiftDurationInv);
        this.maintWeightShiftAssetTarget = I80F48_1.I80F48.from(maintWeightShiftAssetTarget);
        this.maintWeightShiftLiabTarget = I80F48_1.I80F48.from(maintWeightShiftLiabTarget);
        this.zeroUtilRate = I80F48_1.I80F48.from(zeroUtilRate);
        this.platformLiquidationFee = I80F48_1.I80F48.from(platformLiquidationFee);
        this.collectedLiquidationFees = I80F48_1.I80F48.from(collectedLiquidationFees);
        this.collectedCollateralFees = I80F48_1.I80F48.from(collectedCollateralFees);
        this._price = undefined;
        this._uiPrice = undefined;
        this._oracleLastUpdatedSlot = undefined;
        this._oracleProvider = undefined;
    }
    toString() {
        return ('Bank ' +
            '\n public key - ' +
            this.publicKey.toBase58() +
            '\n token index - ' +
            this.tokenIndex +
            '\n token name - ' +
            this.name +
            '\n vault - ' +
            this.vault.toBase58() +
            '\n mintDecimals - ' +
            this.mintDecimals +
            '\n oracle - ' +
            this.oracle.toBase58() +
            '\n price - ' +
            this._price?.toString() +
            '\n uiPrice - ' +
            this._uiPrice +
            '\n deposit index - ' +
            this.depositIndex.toString() +
            '\n borrow index - ' +
            this.borrowIndex.toString() +
            '\n indexedDeposits - ' +
            this.indexedDeposits.toString() +
            '\n indexedBorrows - ' +
            this.indexedBorrows.toString() +
            '\n indexLastUpdated - ' +
            new Date(this.indexLastUpdated.toNumber() * 1000) +
            '\n bankRateLastUpdated - ' +
            new Date(this.bankRateLastUpdated.toNumber() * 1000) +
            '\n avgUtilization - ' +
            this.avgUtilization.toString() +
            '\n adjustmentFactor - ' +
            this.adjustmentFactor.toString() +
            '\n maxRate - ' +
            this.maxRate.toString() +
            '\n util0 - ' +
            this.util0.toString() +
            '\n rate0 - ' +
            this.rate0.toString() +
            '\n util1 - ' +
            this.util1.toString() +
            '\n rate1 - ' +
            this.rate1.toString() +
            '\n loanFeeRate - ' +
            this.loanFeeRate.toString() +
            '\n loanOriginationFeeRate - ' +
            this.loanOriginationFeeRate.toString() +
            '\n maintAssetWeight - ' +
            this.maintAssetWeight.toString() +
            '\n initAssetWeight - ' +
            this.initAssetWeight.toString() +
            '\n maintLiabWeight - ' +
            this.maintLiabWeight.toString() +
            '\n initLiabWeight - ' +
            this.initLiabWeight.toString() +
            '\n liquidationFee - ' +
            this.liquidationFee.toString() +
            '\n uiDeposits() - ' +
            this.uiDeposits() +
            '\n uiBorrows() - ' +
            this.uiBorrows() +
            '\n getDepositRate() - ' +
            this.getDepositRate().toString() +
            '\n getBorrowRate() - ' +
            this.getBorrowRate().toString());
    }
    isOracleStaleOrUnconfident(nowSlot) {
        return (0, oracle_1.isOracleStaleOrUnconfident)(nowSlot, this.oracleConfig.maxStalenessSlots.toNumber(), this.oracleLastUpdatedSlot, this._oracleLastKnownDeviation, this.oracleConfig.confFilter, this.price);
    }
    areDepositsReduceOnly() {
        return this.reduceOnly == 1;
    }
    areBorrowsReduceOnly() {
        return this.reduceOnly == 1 || this.reduceOnly == 2;
    }
    scaledInitAssetWeight(price) {
        const depositsQuote = this.nativeDeposits()
            .add(I80F48_1.I80F48.fromU64(this.potentialSerumTokens))
            .mul(price);
        if (this.depositWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
            depositsQuote.lte(I80F48_1.I80F48.fromNumber(this.depositWeightScaleStartQuote))) {
            return this.initAssetWeight;
        }
        return this.initAssetWeight.mul(I80F48_1.I80F48.fromNumber(this.depositWeightScaleStartQuote).div(depositsQuote));
    }
    scaledInitLiabWeight(price) {
        const borrowsQuote = this.nativeBorrows().mul(price);
        if (this.borrowWeightScaleStartQuote >= Number.MAX_SAFE_INTEGER ||
            borrowsQuote.lte(I80F48_1.I80F48.fromNumber(this.borrowWeightScaleStartQuote))) {
            return this.initLiabWeight;
        }
        return this.initLiabWeight.mul(borrowsQuote.div(I80F48_1.I80F48.fromNumber(this.borrowWeightScaleStartQuote)));
    }
    maintWeights() {
        const nowTs = new anchor_1.BN(Date.now() / 1000);
        if (this.maintWeightShiftDurationInv.isZero() ||
            nowTs.lte(this.maintWeightShiftStart)) {
            return [this.maintAssetWeight, this.maintLiabWeight];
        }
        else if (nowTs.gte(this.maintWeightShiftEnd)) {
            return [
                this.maintWeightShiftAssetTarget,
                this.maintWeightShiftLiabTarget,
            ];
        }
        else {
            const scale = I80F48_1.I80F48.fromU64(nowTs.sub(this.maintWeightShiftStart)).mul(this.maintWeightShiftDurationInv);
            const asset = this.maintAssetWeight.add(this.maintWeightShiftAssetTarget.sub(this.maintAssetWeight).mul(scale));
            const liab = this.maintLiabWeight.add(this.maintWeightShiftLiabTarget.sub(this.maintLiabWeight).mul(scale));
            return [asset, liab];
        }
    }
    getAssetPrice() {
        return this.price.min(I80F48_1.I80F48.fromNumber(this.stablePriceModel.stablePrice));
    }
    getLiabPrice() {
        return this.price.max(I80F48_1.I80F48.fromNumber(this.stablePriceModel.stablePrice));
    }
    get price() {
        if (this._price === undefined) {
            throw new Error(`Undefined price for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`);
        }
        return this._price;
    }
    get uiPrice() {
        if (this._uiPrice === undefined) {
            throw new Error(`Undefined uiPrice for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`);
        }
        return this._uiPrice;
    }
    get oracleLastUpdatedSlot() {
        if (this._oracleLastUpdatedSlot === undefined) {
            throw new Error(`Undefined oracleLastUpdatedSlot for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`);
        }
        return this._oracleLastUpdatedSlot;
    }
    get oracleProvider() {
        if (this._oracleProvider === undefined) {
            throw new Error(`Undefined oracleProvider for bank ${this.publicKey} with tokenIndex ${this.tokenIndex}!`);
        }
        return this._oracleProvider;
    }
    nativeDeposits() {
        return this.indexedDeposits.mul(this.depositIndex);
    }
    nativeBorrows() {
        return this.indexedBorrows.mul(this.borrowIndex);
    }
    uiDeposits() {
        return (0, utils_1.toUiDecimals)(this.nativeDeposits(), this.mintDecimals);
    }
    uiBorrows() {
        return (0, utils_1.toUiDecimals)(this.nativeBorrows(), this.mintDecimals);
    }
    /**
     *
     * @returns borrow rate, 0 is 0% where 1 is 100%; not including loan upkeep rate
     */
    getBorrowRateWithoutUpkeepRate() {
        const totalBorrows = this.nativeBorrows();
        const totalDeposits = this.nativeDeposits();
        if (totalDeposits.isZero() || totalBorrows.isZero()) {
            return (0, I80F48_1.ZERO_I80F48)();
        }
        const utilization = totalBorrows
            .div(totalDeposits)
            .max((0, I80F48_1.ZERO_I80F48)())
            .min((0, I80F48_1.ONE_I80F48)());
        const scaling = I80F48_1.I80F48.fromNumber(this.interestCurveScaling == 0.0 ? 1.0 : this.interestCurveScaling);
        if (utilization.lt(this.util0)) {
            const slope = this.rate0.sub(this.zeroUtilRate).div(this.util0);
            return this.zeroUtilRate.add(slope.mul(utilization)).mul(scaling);
        }
        else if (utilization.lt(this.util1)) {
            const extraUtil = utilization.sub(this.util0);
            const slope = this.rate1.sub(this.rate0).div(this.util1.sub(this.util0));
            return this.rate0.add(slope.mul(extraUtil)).mul(scaling);
        }
        else {
            const extraUtil = utilization.sub(this.util1);
            const slope = this.maxRate
                .sub(this.rate1)
                .div(I80F48_1.I80F48.fromNumber(1).sub(this.util1));
            return this.rate1.add(slope.mul(extraUtil)).mul(scaling);
        }
    }
    /**
     *
     * @returns total borrow rate, 0 is 0% where 1 is 100% (including loan upkeep rate)
     */
    getBorrowRate() {
        return this.getBorrowRateWithoutUpkeepRate().add(this.loanFeeRate);
    }
    /**
     *
     * @returns total borrow rate percentage (including loan upkeep rate)
     */
    getBorrowRateUi() {
        return this.getBorrowRate().toNumber() * 100;
    }
    /**
     *
     * @returns deposit rate, 0 is 0% where 1 is 100%
     */
    getDepositRate() {
        const borrowRate = this.getBorrowRate();
        const totalBorrows = this.nativeBorrows();
        const totalDeposits = this.nativeDeposits();
        if (totalDeposits.isZero() && totalBorrows.isZero()) {
            return (0, I80F48_1.ZERO_I80F48)();
        }
        else if (totalDeposits.isZero()) {
            return this.maxRate;
        }
        const utilization = totalBorrows.div(totalDeposits);
        return utilization.mul(borrowRate);
    }
    /**
     *
     * @returns deposit rate percentage
     */
    getDepositRateUi() {
        return this.getDepositRate().toNumber() * 100;
    }
    getNetBorrowLimitPerWindow() {
        return I80F48_1.I80F48.fromI64(this.netBorrowLimitPerWindowQuote).div(this.price);
    }
    getBorrowLimitLeftInWindow() {
        return this.getNetBorrowLimitPerWindow()
            .sub(I80F48_1.I80F48.fromI64(this.netBorrowsInWindow))
            .max((0, I80F48_1.ZERO_I80F48)());
    }
    getNetBorrowLimitPerWindowUi() {
        return (0, utils_1.toUiDecimals)(this.getNetBorrowLimitPerWindow(), this.mintDecimals);
    }
    getMaxWithdraw(vaultBalance, userDeposits = (0, I80F48_1.ZERO_I80F48)()) {
        userDeposits = userDeposits.max((0, I80F48_1.ZERO_I80F48)());
        // any borrow must respect the minVaultToDepositsRatio
        const minVaultBalanceRequired = this.nativeDeposits().mul(I80F48_1.I80F48.fromNumber(this.minVaultToDepositsRatio));
        const maxBorrowFromVault = I80F48_1.I80F48.fromI64(vaultBalance)
            .sub(minVaultBalanceRequired)
            .max((0, I80F48_1.ZERO_I80F48)());
        // User deposits can exceed maxWithdrawFromVault
        let maxBorrow = maxBorrowFromVault.sub(userDeposits).max((0, I80F48_1.ZERO_I80F48)());
        // any borrow must respect the limit left in window
        maxBorrow = maxBorrow.min(this.getBorrowLimitLeftInWindow());
        // borrows would be applied a fee
        maxBorrow = maxBorrow.div((0, I80F48_1.ONE_I80F48)().add(this.loanOriginationFeeRate));
        // user deposits can always be withdrawn
        // even if vaults can be depleted
        return maxBorrow.add(userDeposits).min(I80F48_1.I80F48.fromI64(vaultBalance));
    }
    getTimeToNextBorrowLimitWindowStartsTs() {
        const timeToNextBorrowLimitWindowStartsTs = this.netBorrowLimitWindowSizeTs
            .sub(new anchor_1.BN(Date.now() / 1000).sub(this.lastNetBorrowsWindowStartTs))
            .toNumber();
        return Math.max(timeToNextBorrowLimitWindowStartsTs, 0);
    }
    /**
     *
     * @param mintPk
     * @returns remaining deposit limit for mint, returns null if there is no limit for bank
     */
    getRemainingDepositLimit() {
        const nativeDeposits = this.nativeDeposits();
        const isNoLimit = this.depositLimit.isZero();
        const remainingDepositLimit = !isNoLimit
            ? this.depositLimit
                .sub(new anchor_1.BN(nativeDeposits.toNumber()))
                .sub(this.potentialSerumTokens)
            : null;
        return remainingDepositLimit
            ? remainingDepositLimit.isNeg()
                ? new anchor_1.BN(0)
                : remainingDepositLimit
            : null;
    }
}
exports.Bank = Bank;
class MintInfo {
    publicKey;
    group;
    tokenIndex;
    mint;
    banks;
    vaults;
    oracle;
    registrationTime;
    fallbackOracle;
    groupInsuranceFund;
    static from(publicKey, obj) {
        return new MintInfo(publicKey, obj.group, obj.tokenIndex, obj.mint, obj.banks, obj.vaults, obj.oracle, obj.registrationTime, obj.fallbackOracle, obj.groupInsuranceFund == 1);
    }
    constructor(publicKey, group, tokenIndex, mint, banks, vaults, oracle, registrationTime, fallbackOracle, groupInsuranceFund) {
        this.publicKey = publicKey;
        this.group = group;
        this.tokenIndex = tokenIndex;
        this.mint = mint;
        this.banks = banks;
        this.vaults = vaults;
        this.oracle = oracle;
        this.registrationTime = registrationTime;
        this.fallbackOracle = fallbackOracle;
        this.groupInsuranceFund = groupInsuranceFund;
    }
    firstBank() {
        return this.banks[0];
    }
    firstVault() {
        return this.vaults[0];
    }
    toString() {
        const res = 'mint ' +
            this.mint.toBase58() +
            '\n oracle ' +
            this.oracle.toBase58() +
            '\n banks ' +
            this.banks
                .filter((pk) => pk.toBase58() !== web3_js_1.PublicKey.default.toBase58())
                .toString() +
            '\n vaults ' +
            this.vaults
                .filter((pk) => pk.toBase58() !== web3_js_1.PublicKey.default.toBase58())
                .toString();
        return res;
    }
}
exports.MintInfo = MintInfo;
