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());
    });
};
import { LogLevel } from "@ethersproject/logger";
import { JsonRpcProvider, Web3Provider } from "@ethersproject/providers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { Contract, Signer, utils } from "ethers";
import CTokenFirstExtensionABI from "../../abis/CTokenFirstExtension";
import EIP20InterfaceABI from "../../abis/EIP20Interface";
import FeeDistributorABI from "../../abis/FeeDistributor";
import IonicERC4626ABI from "../../abis/IonicERC4626";
import IonicFlywheelLensRouterABI from "../../abis/IonicFlywheelLensRouter";
import IonicLiquidatorABI from "../../abis/IonicLiquidator";
import PoolDirectoryABI from "../../abis/PoolDirectory";
import PoolLensABI from "../../abis/PoolLens";
import PoolLensSecondaryABI from "../../abis/PoolLensSecondary";
import UnitrollerABI from "../../abis/Unitroller";
import { withAsset } from "../modules/Asset";
import { withConvertMantissa } from "../modules/ConvertMantissa";
import { withCreateContracts } from "../modules/CreateContracts";
import { withFlywheel } from "../modules/Flywheel";
import { withFundOperations } from "../modules/FundOperations";
import { withLeverage } from "../modules/Leverage";
import { withSafeLiquidator } from "../modules/liquidation/SafeLiquidator";
import { withPoolLens } from "../modules/PoolLens";
import { withPools } from "../modules/Pools";
import { withVaults } from "../modules/Vaults";
import { CTOKEN_ERROR_CODES } from "./config";
import AdjustableAnkrBNBIrm from "./irm/AdjustableAnkrBNBIrm";
import AdjustableJumpRateModel from "./irm/AdjustableJumpRateModel";
import AnkrBNBInterestRateModel from "./irm/AnkrBNBInterestRateModel";
import AnkrFTMInterestRateModel from "./irm/AnkrFTMInterestRateModel";
import JumpRateModel from "./irm/JumpRateModel";
import { getContract, getPoolAddress, getPoolComptroller, getPoolUnitroller } from "./utils";
utils.Logger.setLogLevel(LogLevel.OFF);
export class IonicBase {
    static isSupportedProvider(provider) {
        return SignerWithAddress.isSigner(provider) || Signer.isSigner(provider);
    }
    static isSupportedSigner(signer) {
        return SignerWithAddress.isSigner(signer) || 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 Contract(this.chainDeployment.PoolDirectory.address, PoolDirectoryABI, this.provider), PoolLens: new Contract(this.chainDeployment.PoolLens.address, PoolLensABI, this.provider), PoolLensSecondary: new Contract(this.chainDeployment.PoolLensSecondary.address, PoolLensSecondaryABI, this.provider), IonicLiquidator: new Contract(this.chainDeployment.IonicLiquidator.address, IonicLiquidatorABI, this.provider), FeeDistributor: new Contract(this.chainDeployment.FeeDistributor.address, FeeDistributorABI, this.provider), IonicFlywheelLensRouter: new Contract(this.chainDeployment.IonicFlywheelLensRouter.address, IonicFlywheelLensRouterABI, 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 (SignerWithAddress.isSigner(signerOrProvider) || Signer.isSigner(signerOrProvider)) {
            this._provider = signerOrProvider.provider;
            this._signer = signerOrProvider;
        }
        else if (JsonRpcProvider.isProvider(signerOrProvider) || 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 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 = getPoolAddress(addressOfSigner, poolName, existingPools.length, this.chainDeployment.FeeDistributor.address, this.chainDeployment.PoolDirectory.address);
                // Accept admin status via Unitroller
                const unitroller = 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 = 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,
                AnkrBNBInterestRateModel: AnkrBNBInterestRateModel,
                AnkrFTMInterestRateModel: AnkrFTMInterestRateModel,
                AdjustableJumpRateModel: AdjustableJumpRateModel,
                AdjustableAnkrBNBIrm: AdjustableAnkrBNBIrm
            };
            const runtimeBytecodeHash = 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 = getContract(assetAddress, CTokenFirstExtensionABI, 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 Contract(address, EIP20InterfaceABI, signerOrProvider);
    }
    getUnitrollerInstance(address, signerOrProvider = this.provider) {
        return new Contract(address, UnitrollerABI, signerOrProvider);
    }
    getPoolDirectoryInstance(signerOrProvider = this.provider) {
        return new Contract(this.chainDeployment.PoolDirectory.address, PoolDirectoryABI, signerOrProvider);
    }
    getErc4626PluginInstance(address, signerOrProvider = this.provider) {
        return new Contract(address, IonicERC4626ABI, signerOrProvider);
    }
}
IonicBase.CTOKEN_ERROR_CODES = CTOKEN_ERROR_CODES;
const IonicBaseWithModules = withPoolLens(withFundOperations(withSafeLiquidator(withPools(withAsset(withFlywheel(withVaults(withLeverage(withCreateContracts(withConvertMantissa(IonicBase))))))))));
export class IonicSdk extends IonicBaseWithModules {
}
export default IonicSdk;
