"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 });
exports.IonicSdk = exports.IonicBase = void 0;
const logger_1 = require("@ethersproject/logger");
const providers_1 = require("@ethersproject/providers");
const signers_1 = require("@nomiclabs/hardhat-ethers/signers");
const ethers_1 = require("ethers");
const CTokenFirstExtension_1 = __importDefault(require("../../abis/CTokenFirstExtension"));
const EIP20Interface_1 = __importDefault(require("../../abis/EIP20Interface"));
const FeeDistributor_1 = __importDefault(require("../../abis/FeeDistributor"));
const IonicERC4626_1 = __importDefault(require("../../abis/IonicERC4626"));
const IonicFlywheelLensRouter_1 = __importDefault(require("../../abis/IonicFlywheelLensRouter"));
const IonicLiquidator_1 = __importDefault(require("../../abis/IonicLiquidator"));
const PoolDirectory_1 = __importDefault(require("../../abis/PoolDirectory"));
const PoolLens_1 = __importDefault(require("../../abis/PoolLens"));
const PoolLensSecondary_1 = __importDefault(require("../../abis/PoolLensSecondary"));
const Unitroller_1 = __importDefault(require("../../abis/Unitroller"));
const Asset_1 = require("../modules/Asset");
const ConvertMantissa_1 = require("../modules/ConvertMantissa");
const CreateContracts_1 = require("../modules/CreateContracts");
const Flywheel_1 = require("../modules/Flywheel");
const FundOperations_1 = require("../modules/FundOperations");
const Leverage_1 = require("../modules/Leverage");
const SafeLiquidator_1 = require("../modules/liquidation/SafeLiquidator");
const PoolLens_2 = require("../modules/PoolLens");
const Pools_1 = require("../modules/Pools");
const Vaults_1 = require("../modules/Vaults");
const config_1 = require("./config");
const AdjustableAnkrBNBIrm_1 = __importDefault(require("./irm/AdjustableAnkrBNBIrm"));
const AdjustableJumpRateModel_1 = __importDefault(require("./irm/AdjustableJumpRateModel"));
const AnkrBNBInterestRateModel_1 = __importDefault(require("./irm/AnkrBNBInterestRateModel"));
const AnkrFTMInterestRateModel_1 = __importDefault(require("./irm/AnkrFTMInterestRateModel"));
const JumpRateModel_1 = __importDefault(require("./irm/JumpRateModel"));
const utils_1 = require("./utils");
ethers_1.utils.Logger.setLogLevel(logger_1.LogLevel.OFF);
class IonicBase {
    static isSupportedProvider(provider) {
        return signers_1.SignerWithAddress.isSigner(provider) || ethers_1.Signer.isSigner(provider);
    }
    static isSupportedSigner(signer) {
        return signers_1.SignerWithAddress.isSigner(signer) || ethers_1.Signer.isSigner(signer);
    }
    static isSupportedSignerOrProvider(signerOrProvider) {
        return IonicBase.isSupportedSigner(signerOrProvider) || IonicBase.isSupportedProvider(signerOrProvider);
    }
    get provider() {
        return this._provider;
    }
    get signer() {
        if (!this._signer) {
            throw new Error("No Signer available.");
        }
        return this._signer;
    }
    set contracts(newContracts) {
        this._contracts = Object.assign(Object.assign({}, this._contracts), newContracts);
    }
    get contracts() {
        return Object.assign({ PoolDirectory: new ethers_1.Contract(this.chainDeployment.PoolDirectory.address, PoolDirectory_1.default, this.provider), PoolLens: new ethers_1.Contract(this.chainDeployment.PoolLens.address, PoolLens_1.default, this.provider), PoolLensSecondary: new ethers_1.Contract(this.chainDeployment.PoolLensSecondary.address, PoolLensSecondary_1.default, this.provider), IonicLiquidator: new ethers_1.Contract(this.chainDeployment.IonicLiquidator.address, IonicLiquidator_1.default, this.provider), FeeDistributor: new ethers_1.Contract(this.chainDeployment.FeeDistributor.address, FeeDistributor_1.default, this.provider), IonicFlywheelLensRouter: new ethers_1.Contract(this.chainDeployment.IonicFlywheelLensRouter.address, IonicFlywheelLensRouter_1.default, this.provider) }, this._contracts);
    }
    setSigner(signer) {
        this._provider = signer.provider;
        this._signer = signer;
        return this;
    }
    removeSigner(provider) {
        this._provider = provider;
        this._signer = null;
        return this;
    }
    constructor(signerOrProvider, chainConfig, logger = console) {
        this.logger = logger;
        if (!signerOrProvider)
            throw Error("No Provider or Signer");
        if (signers_1.SignerWithAddress.isSigner(signerOrProvider) || ethers_1.Signer.isSigner(signerOrProvider)) {
            this._provider = signerOrProvider.provider;
            this._signer = signerOrProvider;
        }
        else if (providers_1.JsonRpcProvider.isProvider(signerOrProvider) || providers_1.Web3Provider.isProvider(signerOrProvider)) {
            this._provider = signerOrProvider;
            this._signer = signerOrProvider.getSigner ? signerOrProvider.getSigner() : null;
        }
        else {
            this.logger.warn(`Incompatible Provider or Signer: signerOrProvider`);
            throw Error("Signer or Provider not compatible");
        }
        this.chainConfig = chainConfig;
        this.chainId = chainConfig.chainId;
        this.chainDeployment = chainConfig.chainDeployments;
        this.chainSpecificAddresses = chainConfig.chainAddresses;
        this.chainSpecificParams = chainConfig.specificParams;
        this.liquidationConfig = chainConfig.liquidationDefaults;
        this.supportedAssets = chainConfig.assets;
        this.deployedPlugins = chainConfig.deployedPlugins;
        this.marketToPlugin = Object.entries(this.deployedPlugins).reduce((acc, [plugin, pluginData]) => {
            return Object.assign(Object.assign({}, acc), { [pluginData.market]: plugin });
        }, {});
        this.redemptionStrategies = chainConfig.redemptionStrategies;
        this.fundingStrategies = chainConfig.fundingStrategies;
        this.availableOracles = chainConfig.oracles.filter((o) => {
            if (this.chainDeployment[o] === undefined) {
                this.logger.warn(`Oracle ${o} not deployed to chain ${this.chainId}`);
                return false;
            }
            return true;
        });
    }
    deployPool(poolName, enforceWhitelist, closeFactor, liquidationIncentive, priceOracle, // Contract address
    whitelist // An array of whitelisted addresses
    ) {
        var _a;
        return __awaiter(this, void 0, void 0, function* () {
            try {
                // Deploy Comptroller implementation if necessary
                const implementationAddress = this.chainDeployment.Comptroller.address;
                // Register new pool with PoolDirectory
                const contract = this.contracts.PoolDirectory.connect(this.signer);
                const deployTx = yield contract.deployPool(poolName, implementationAddress, new ethers_1.utils.AbiCoder().encode(["address"], [this.chainDeployment.FeeDistributor.address]), enforceWhitelist, closeFactor, liquidationIncentive, priceOracle);
                const deployReceipt = yield deployTx.wait();
                this.logger.info(`Deployment of pool ${poolName} succeeded!`, deployReceipt.status);
                let poolId;
                try {
                    // Latest Event is PoolRegistered which includes the poolId
                    const registerEvent = (_a = deployReceipt.events) === null || _a === void 0 ? void 0 : _a.pop();
                    poolId =
                        registerEvent && registerEvent.args && registerEvent.args[0]
                            ? registerEvent.args[0].toNumber()
                            : undefined;
                }
                catch (e) {
                    this.logger.warn("Unable to retrieve pool ID from receipt events", e);
                }
                const [, existingPools] = yield contract.callStatic.getActivePools();
                // Compute Unitroller address
                const addressOfSigner = yield this.signer.getAddress();
                const poolAddress = (0, utils_1.getPoolAddress)(addressOfSigner, poolName, existingPools.length, this.chainDeployment.FeeDistributor.address, this.chainDeployment.PoolDirectory.address);
                // Accept admin status via Unitroller
                const unitroller = (0, utils_1.getPoolUnitroller)(poolAddress, this.signer);
                const acceptTx = yield unitroller._acceptAdmin();
                const acceptReceipt = yield acceptTx.wait();
                this.logger.info(`Accepted admin status for admin: ${acceptReceipt.status}`);
                // Whitelist
                this.logger.info(`enforceWhitelist: ${enforceWhitelist}`);
                if (enforceWhitelist) {
                    const comptroller = (0, utils_1.getPoolComptroller)(poolAddress, this.signer);
                    // Was enforced by pool deployment, now just add addresses
                    const whitelistTx = yield comptroller._setWhitelistStatuses(whitelist, Array(whitelist.length).fill(true));
                    const whitelistReceipt = yield whitelistTx.wait();
                    this.logger.info(`Whitelist updated: ${whitelistReceipt.status}`);
                }
                return [poolAddress, implementationAddress, priceOracle, poolId];
            }
            catch (error) {
                throw Error(`Deployment of new ionic pool failed:  ${error instanceof Error ? error.message : error}`);
            }
        });
    }
    identifyInterestRateModel(interestRateModelAddress) {
        return __awaiter(this, void 0, void 0, function* () {
            // Get interest rate model type from runtime bytecode hash and init class
            const interestRateModels = {
                JumpRateModel: JumpRateModel_1.default,
                AnkrBNBInterestRateModel: AnkrBNBInterestRateModel_1.default,
                AnkrFTMInterestRateModel: AnkrFTMInterestRateModel_1.default,
                AdjustableJumpRateModel: AdjustableJumpRateModel_1.default,
                AdjustableAnkrBNBIrm: AdjustableAnkrBNBIrm_1.default
            };
            const runtimeBytecodeHash = ethers_1.utils.keccak256(yield this.provider.getCode(interestRateModelAddress));
            let irmModel = null;
            for (const irm of Object.values(interestRateModels)) {
                if (runtimeBytecodeHash === irm.RUNTIME_BYTECODE_HASH) {
                    irmModel = new irm();
                    break;
                }
            }
            if (irmModel === null) {
                throw Error("InterestRateModel not found");
            }
            return irmModel;
        });
    }
    getInterestRateModel(assetAddress) {
        return __awaiter(this, void 0, void 0, function* () {
            // Get interest rate model address from asset address
            const assetContract = (0, utils_1.getContract)(assetAddress, CTokenFirstExtension_1.default, this.provider);
            const interestRateModelAddress = yield assetContract.callStatic.interestRateModel();
            const interestRateModel = yield this.identifyInterestRateModel(interestRateModelAddress);
            if (!interestRateModel) {
                throw Error(`No Interest Rate Model found for asset: ${assetAddress}`);
            }
            yield interestRateModel.init(interestRateModelAddress, assetAddress, this.provider);
            return interestRateModel;
        });
    }
    getPriceOracle(oracleAddress) {
        let oracle = this.availableOracles.find((o) => this.chainDeployment[o].address === oracleAddress);
        if (!oracle) {
            oracle = "Unrecognized Oracle";
        }
        return oracle;
    }
    getEIP20TokenInstance(address, signerOrProvider = this.provider) {
        return new ethers_1.Contract(address, EIP20Interface_1.default, signerOrProvider);
    }
    getUnitrollerInstance(address, signerOrProvider = this.provider) {
        return new ethers_1.Contract(address, Unitroller_1.default, signerOrProvider);
    }
    getPoolDirectoryInstance(signerOrProvider = this.provider) {
        return new ethers_1.Contract(this.chainDeployment.PoolDirectory.address, PoolDirectory_1.default, signerOrProvider);
    }
    getErc4626PluginInstance(address, signerOrProvider = this.provider) {
        return new ethers_1.Contract(address, IonicERC4626_1.default, signerOrProvider);
    }
}
exports.IonicBase = IonicBase;
IonicBase.CTOKEN_ERROR_CODES = config_1.CTOKEN_ERROR_CODES;
const IonicBaseWithModules = (0, PoolLens_2.withPoolLens)((0, FundOperations_1.withFundOperations)((0, SafeLiquidator_1.withSafeLiquidator)((0, Pools_1.withPools)((0, Asset_1.withAsset)((0, Flywheel_1.withFlywheel)((0, Vaults_1.withVaults)((0, Leverage_1.withLeverage)((0, CreateContracts_1.withCreateContracts)((0, ConvertMantissa_1.withConvertMantissa)(IonicBase))))))))));
class IonicSdk extends IonicBaseWithModules {
}
exports.IonicSdk = IonicSdk;
exports.default = IonicSdk;
