"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.withAsset = void 0;
const types_1 = require("@ionicprotocol/types");
const ethers_1 = require("ethers");
const CErc20Delegator_json_1 = __importDefault(require("../../artifacts/CErc20Delegator.json"));
const config_1 = require("../IonicSdk/config");
function withAsset(Base) {
    return class PoolAsset extends Base {
        constructor() {
            super(...arguments);
            this.COMPTROLLER_ERROR_CODES = config_1.COMPTROLLER_ERROR_CODES;
        }
        deployAsset(config) {
            return __awaiter(this, void 0, void 0, function* () {
                //1. Validate configuration
                yield this.validateConfiguration(config);
                //2. Deploy new asset to existing pool via SDK
                try {
                    const [assetAddress, implementationAddress, receipt] = yield this.deployMarket(config);
                    return [assetAddress, implementationAddress, receipt];
                }
                catch (error) {
                    this.logger.error(`deployAsset raw error:  ${error} using MarketConfig: ${JSON.stringify(config)}`);
                    throw Error("Deployment of asset to Ionic pool failed: " + (error instanceof Error ? error.message : error));
                }
            });
        }
        validateConfiguration(config) {
            return __awaiter(this, void 0, void 0, function* () {
                // BigNumbers
                // 10% -> 0.1 * 1e18
                const reserveFactorBN = ethers_1.utils.parseEther((config.reserveFactor / 100).toString());
                // 5% -> 0.05 * 1e18
                const adminFeeBN = ethers_1.utils.parseEther((config.adminFee / 100).toString());
                // 50% -> 0.5 * 1e18
                // TODO: find out if this is a number or string. If its a number, parseEther will not work. Also parse Units works if number is between 0 - 0.9
                const collateralFactorBN = ethers_1.utils.parseEther((config.collateralFactor / 100).toString());
                // Check collateral factor
                if (!collateralFactorBN.gte(ethers_1.constants.Zero) || collateralFactorBN.gt(ethers_1.utils.parseEther("0.9")))
                    throw Error("Collateral factor must range from 0 to 0.9.");
                // Check reserve factor + admin fee + ionic fee
                if (!reserveFactorBN.gte(ethers_1.constants.Zero))
                    throw Error("Reserve factor cannot be negative.");
                if (!adminFeeBN.gte(ethers_1.constants.Zero))
                    throw Error("Admin fee cannot be negative.");
                // If reserveFactor or adminFee is greater than zero, we get ionic fee.
                // Sum of reserveFactor and adminFee should not be greater than ionic fee. ? i think
                if (reserveFactorBN.gt(ethers_1.constants.Zero) || adminFeeBN.gt(ethers_1.constants.Zero)) {
                    const ionicFee = yield this.contracts.FeeDistributor.interestFeeRate();
                    if (reserveFactorBN.add(adminFeeBN).add(ethers_1.BigNumber.from(ionicFee)).gt(ethers_1.constants.WeiPerEther))
                        throw Error("Sum of reserve factor and admin fee should range from 0 to " + (1 - ionicFee.div(1e18).toNumber()) + ".");
                }
            });
        }
        deployMarket(config) {
            return __awaiter(this, void 0, void 0, function* () {
                const abiCoder = new ethers_1.utils.AbiCoder();
                const reserveFactorBN = ethers_1.utils.parseUnits((config.reserveFactor / 100).toString());
                const adminFeeBN = ethers_1.utils.parseUnits((config.adminFee / 100).toString());
                const collateralFactorBN = ethers_1.utils.parseUnits((config.collateralFactor / 100).toString());
                const comptroller = this.createComptroller(config.comptroller, this.signer);
                // Use Default CErc20Delegate
                const implementationAddress = this.chainDeployment.CErc20Delegate.address;
                const delegateType = 1; // regular delegate = CErc20Delegate
                const implementationData = "0x00";
                // Prepare Transaction Data
                const deployArgs = [
                    config.underlying,
                    config.comptroller,
                    config.feeDistributor,
                    config.interestRateModel,
                    config.name,
                    config.symbol,
                    reserveFactorBN,
                    adminFeeBN
                ];
                const constructorData = abiCoder.encode(["address", "address", "address", "address", "string", "string", "uint256", "uint256"], deployArgs);
                // Test Transaction
                const errorCode = yield comptroller.callStatic._deployMarket(delegateType, constructorData, implementationData, collateralFactorBN);
                if (errorCode.toNumber() !== 0) {
                    throw `Unable to _deployMarket: ${this.COMPTROLLER_ERROR_CODES[errorCode.toNumber()]}`;
                }
                // Make actual Transaction
                const tx = yield comptroller._deployMarket(delegateType, constructorData, implementationData, collateralFactorBN);
                // Recreate Address of Deployed Market
                const receipt = yield tx.wait();
                if (receipt.status != ethers_1.constants.One.toNumber()) {
                    throw "Failed to deploy market ";
                }
                const marketCounter = yield this.contracts.FeeDistributor.callStatic.marketsCounter();
                const saltsHash = ethers_1.utils.solidityKeccak256(["address", "address", "uint"], [config.comptroller, config.underlying, marketCounter]);
                const byteCodeHash = ethers_1.utils.keccak256(CErc20Delegator_json_1.default.bytecode.object + constructorData.substring(2));
                const cErc20DelegatorAddress = ethers_1.utils.getCreate2Address(this.chainDeployment.FeeDistributor.address, saltsHash, byteCodeHash);
                // Return cToken proxy and implementation contract addresses
                return [cErc20DelegatorAddress, implementationAddress, receipt];
            });
        }
        getUpdatedAssets(mode, index, assets, amount) {
            return __awaiter(this, void 0, void 0, function* () {
                const assetToBeUpdated = assets[index];
                const interestRateModel = yield this.getInterestRateModel(assetToBeUpdated.cToken);
                let updatedAsset;
                if (mode === types_1.FundOperationMode.SUPPLY) {
                    const supplyBalance = assetToBeUpdated.supplyBalance.add(amount);
                    const totalSupply = assetToBeUpdated.totalSupply.add(amount);
                    updatedAsset = Object.assign(Object.assign({}, assetToBeUpdated), { supplyBalance,
                        totalSupply, supplyBalanceNative: Number(ethers_1.utils.formatUnits(supplyBalance, assetToBeUpdated.underlyingDecimals)) *
                            Number(ethers_1.utils.formatUnits(assetToBeUpdated.underlyingPrice, 18)), supplyRatePerBlock: interestRateModel.getSupplyRate(totalSupply.gt(ethers_1.constants.Zero)
                            ? assetToBeUpdated.totalBorrow.mul(ethers_1.constants.WeiPerEther).div(totalSupply)
                            : ethers_1.constants.Zero) });
                }
                else if (mode === types_1.FundOperationMode.WITHDRAW) {
                    const supplyBalance = assetToBeUpdated.supplyBalance.sub(amount);
                    const totalSupply = assetToBeUpdated.totalSupply.sub(amount);
                    updatedAsset = Object.assign(Object.assign({}, assetToBeUpdated), { supplyBalance,
                        totalSupply, supplyBalanceNative: Number(ethers_1.utils.formatUnits(supplyBalance, assetToBeUpdated.underlyingDecimals)) *
                            Number(ethers_1.utils.formatUnits(assetToBeUpdated.underlyingPrice, 18)), supplyRatePerBlock: interestRateModel.getSupplyRate(totalSupply.gt(ethers_1.constants.Zero)
                            ? assetToBeUpdated.totalBorrow.mul(ethers_1.constants.WeiPerEther).div(totalSupply)
                            : ethers_1.constants.Zero) });
                }
                else if (mode === types_1.FundOperationMode.BORROW) {
                    const borrowBalance = assetToBeUpdated.borrowBalance.add(amount);
                    const totalBorrow = assetToBeUpdated.totalBorrow.add(amount);
                    updatedAsset = Object.assign(Object.assign({}, assetToBeUpdated), { borrowBalance,
                        totalBorrow, borrowBalanceNative: Number(ethers_1.utils.formatUnits(borrowBalance, assetToBeUpdated.underlyingDecimals)) *
                            Number(ethers_1.utils.formatUnits(assetToBeUpdated.underlyingPrice, 18)), borrowRatePerBlock: interestRateModel.getBorrowRate(assetToBeUpdated.totalSupply.gt(ethers_1.constants.Zero)
                            ? totalBorrow.mul(ethers_1.constants.WeiPerEther).div(assetToBeUpdated.totalSupply)
                            : ethers_1.constants.Zero) });
                }
                else if (mode === types_1.FundOperationMode.REPAY) {
                    const borrowBalance = assetToBeUpdated.borrowBalance.sub(amount);
                    const totalBorrow = assetToBeUpdated.totalBorrow.sub(amount);
                    const borrowRatePerBlock = interestRateModel.getBorrowRate(assetToBeUpdated.totalSupply.gt(ethers_1.constants.Zero)
                        ? totalBorrow.mul(ethers_1.constants.WeiPerEther).div(assetToBeUpdated.totalSupply)
                        : ethers_1.constants.Zero);
                    updatedAsset = Object.assign(Object.assign({}, assetToBeUpdated), { borrowBalance,
                        totalBorrow, borrowBalanceNative: Number(ethers_1.utils.formatUnits(borrowBalance, assetToBeUpdated.underlyingDecimals)) *
                            Number(ethers_1.utils.formatUnits(assetToBeUpdated.underlyingPrice, 18)), borrowRatePerBlock });
                }
                return assets.map((value, _index) => {
                    if (_index === index) {
                        return updatedAsset;
                    }
                    else {
                        return value;
                    }
                });
            });
        }
    };
}
exports.withAsset = withAsset;
