"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    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) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
    result["default"] = mod;
    return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
const R = __importStar(require("ramda"));
const Contracts_1 = require("../../Contracts");
const deployMatchingMarketAdapter_1 = require("../../contracts/exchanges/transactions/deployMatchingMarketAdapter");
const deployMatchingMarketAccessor_1 = require("../../contracts/exchanges/transactions/deployMatchingMarketAccessor");
const deployEngine_1 = require("../../contracts/engine/transactions/deployEngine");
const deploy_1 = require("../../contracts/fund/policies/risk-management/transactions/deploy");
const deployRegistry_1 = require("../../contracts/version/transactions/deployRegistry");
const registerAsset_1 = require("../../contracts/version/transactions/registerAsset");
const registerExchange_1 = require("../../contracts/version/transactions/registerExchange");
const updateExchange_1 = require("../../contracts/version/transactions/updateExchange");
const deployVersion_1 = require("../../contracts/version/transactions/deployVersion");
const deployFundRanking_1 = require("../../contracts/factory/transactions/deployFundRanking");
const deployUserWhitelist_1 = require("../../contracts/fund/policies/compliance/transactions/deployUserWhitelist");
const deployAccountingFactory_1 = require("../../contracts/fund/accounting/transactions/deployAccountingFactory");
const deployFeeManagerFactory_1 = require("../../contracts/fund/fees/transactions/deployFeeManagerFactory");
const deployParticipationFactory_1 = require("../../contracts/fund/participation/transactions/deployParticipationFactory");
const deploySharesFactory_1 = require("../../contracts/fund/shares/transactions/deploySharesFactory");
const deployTradingFactory_1 = require("../../contracts/fund/trading/transactions/deployTradingFactory");
const deployVaultFactory_1 = require("../../contracts/fund/vault/transactions/deployVaultFactory");
const deployPolicyManagerFactory_1 = require("../../contracts/fund/policies/transactions/deployPolicyManagerFactory");
const deploy0xAdapter_1 = require("../../contracts/exchanges/transactions/deploy0xAdapter");
const deployEthfinexAdapter_1 = require("../../contracts/exchanges/transactions/deployEthfinexAdapter");
const Environment_1 = require("../environment/Environment");
const deployKyberAdapter_1 = require("../../contracts/exchanges/transactions/deployKyberAdapter");
const setMlnToken_1 = require("../../contracts/version/transactions/setMlnToken");
const setNativeAsset_1 = require("../../contracts/version/transactions/setNativeAsset");
const setPriceSource_1 = require("../../contracts/version/transactions/setPriceSource");
const setEngine_1 = require("../../contracts/version/transactions/setEngine");
const registerVersion_1 = require("../../contracts/version/transactions/registerVersion");
const getVersionInformation_1 = require("../../contracts/version/calls/getVersionInformation");
const setRegistry_1 = require("../../contracts/engine/transactions/setRegistry");
const FunctionSignatures_1 = require("../../contracts/fund/trading/utils/FunctionSignatures");
const getRegistryInformation_1 = require("../../contracts/version/calls/getRegistryInformation");
const deployKyberPriceFeed_1 = require("../../contracts/prices/transactions/deployKyberPriceFeed");
const getLogCurried_1 = require("../environment/getLogCurried");
const updateKyber_1 = require("../../contracts/prices/transactions/updateKyber");
const deployTestingPriceFeed_1 = require("../../contracts/prices/transactions/deployTestingPriceFeed");
const updateTestingPriceFeed_1 = require("../../tests/utils/updateTestingPriceFeed");
const getContract_1 = require("../solidity/getContract");
const setDecimals_1 = require("../../contracts/prices/transactions/setDecimals");
const deployManagementFee_1 = require("../../contracts/fund/fees/transactions/deployManagementFee");
const deployPerformanceFee_1 = require("../../contracts/fund/fees/transactions/deployPerformanceFee");
const pkg = require('../../../package.json');
exports.deployAllContractsConfig = JSON.parse(`{
  "priceSource": "DEPLOY",
  "adapters": {
    "ethfinexAdapter": "DEPLOY",
    "kyberAdapter": "DEPLOY",
    "matchingMarketAdapter": "DEPLOY",
    "matchingMarketAccessor": "DEPLOY",
    "zeroExAdapter": "DEPLOY"
  },
  "policies": {
    "priceTolerance": "DEPLOY",
    "userWhitelist": "DEPLOY"
  },
  "fees" : {
    "managementFee": "DEPLOY",
    "performanceFee": "DEPLOY"
  },
  "factories": {
    "accountingFactory": "DEPLOY",
    "feeManagerFactory": "DEPLOY",
    "participationFactory": "DEPLOY",
    "policyManagerFactory": "DEPLOY",
    "sharesFactory": "DEPLOY",
    "tradingFactory": "DEPLOY",
    "vaultFactory": "DEPLOY"
  },
  "engine": "DEPLOY",
  "registry": "DEPLOY",
  "version": "DEPLOY",
  "ranking": "DEPLOY"
}`);
const getLog = getLogCurried_1.getLogCurried('melon:protocol:utils:deploySystem');
const maybeDeploy = R.curry((path, deployFunction, environmentPromise) => __awaiter(this, void 0, void 0, function* () {
    const environment = yield environmentPromise;
    const environmentPath = ['deployment', 'melonContracts', ...path];
    const { info } = getLog(environment);
    const adoptedContract = R.path(environmentPath, environment);
    if (adoptedContract === 'DEPLOY') {
        info('Deploying', path.join('.'));
        const address = yield deployFunction(environment);
        const newEnvironment = R.assocPath(environmentPath, address, environment);
        return newEnvironment;
    }
    info('Not Deploying', path.join('.'));
    return environment;
}));
const maybeDoSomething = R.curry((shouldIt, something, environmentPromise) => __awaiter(this, void 0, void 0, function* () {
    const environment = yield environmentPromise;
    if (shouldIt)
        yield something(environment);
    return environment;
}));
/**
 * Deploys all contracts and checks their health
 */
exports.deploySystem = (environmentWithoutDeployment, thirdPartyContracts, adoptedContracts, description) => __awaiter(this, void 0, void 0, function* () {
    // Set thirdPartyContracts already to have them available in subsequent calls
    const environment = Object.assign({}, environmentWithoutDeployment, { deployment: { thirdPartyContracts, melonContracts: adoptedContracts } });
    const log = getLog(environment);
    log.info('Deploying system from:', environment.wallet.address);
    log.debug('Deploying system', {
        adoptedContracts,
        thirdPartyContracts,
    });
    const wethToken = thirdPartyContracts.tokens.find(t => t.symbol === 'WETH');
    const mlnToken = thirdPartyContracts.tokens.find(t => t.symbol === 'MLN');
    const monthInSeconds = 30 * 24 * 60 * 60;
    const environmentWithDeployment = yield R.pipe(maybeDeploy(['adapters', 'ethfinexAdapter'], environment => deployEthfinexAdapter_1.deployEthfinexAdapter(environment)), maybeDeploy(['adapters', 'kyberAdapter'], environment => deployKyberAdapter_1.deployKyberAdapter(environment)), maybeDeploy(['adapters', 'matchingMarketAdapter'], environment => deployMatchingMarketAdapter_1.deployMatchingMarketAdapter(environment)), maybeDeploy(['adapters', 'matchingMarketAccessor'], environment => deployMatchingMarketAccessor_1.deployMatchingMarketAccessor(environment)), maybeDeploy(['adapters', 'zeroExAdapter'], environment => deploy0xAdapter_1.deploy0xAdapter(environment)), maybeDeploy(['policies', 'priceTolerance'], environment => deploy_1.deploy(environment, 10)), maybeDeploy(['policies', 'userWhitelist'], environment => deployUserWhitelist_1.deployUserWhitelist(environment, [environment.wallet.address])), maybeDeploy(['fees', 'managementFee'], environment => deployManagementFee_1.deployManagementFee(environment)), maybeDeploy(['fees', 'performanceFee'], environment => deployPerformanceFee_1.deployPerformanceFee(environment)), maybeDeploy(['factories', 'accountingFactory'], environment => deployAccountingFactory_1.deployAccountingFactory(environment)), maybeDeploy(['factories', 'feeManagerFactory'], environment => deployFeeManagerFactory_1.deployFeeManagerFactory(environment)), maybeDeploy(['factories', 'participationFactory'], environment => deployParticipationFactory_1.deployParticipationFactory(environment)), maybeDeploy(['factories', 'policyManagerFactory'], environment => deployPolicyManagerFactory_1.deployPolicyManagerFactory(environment)), maybeDeploy(['factories', 'sharesFactory'], environment => deploySharesFactory_1.deploySharesFactory(environment)), maybeDeploy(['factories', 'tradingFactory'], environment => deployTradingFactory_1.deployTradingFactory(environment)), maybeDeploy(['factories', 'vaultFactory'], environment => deployVaultFactory_1.deployVaultFactory(environment)), maybeDeploy(['engine'], environment => deployEngine_1.deployEngine(environment, { delay: monthInSeconds })), maybeDeploy(['registry'], environment => deployRegistry_1.deployRegistry(environment)), maybeDeploy(['priceSource'], environment => environment.track === Environment_1.Tracks.KYBER_PRICE
        ? deployKyberPriceFeed_1.deployKyberPriceFeed(environment, {
            // tslint:disable-next-line:max-line-length
            kyberNetworkProxy: environment.deployment.thirdPartyContracts.exchanges.kyber
                .kyberNetworkProxy,
            quoteToken: wethToken,
            registry: environment.deployment.melonContracts.registry,
        })
        : deployTestingPriceFeed_1.deployTestingPriceFeed(environment, wethToken)), maybeDoSomething(adoptedContracts.priceSource === 'DEPLOY' ||
        adoptedContracts.registry === 'DEPLOY', (environment) => __awaiter(this, void 0, void 0, function* () {
        const { melonContracts } = environment.deployment;
        getLog(environment).info('Register priceSource');
        yield setPriceSource_1.setPriceSource(environment, melonContracts.registry, {
            address: melonContracts.priceSource,
        });
    })), maybeDoSomething(true, // ensure these steps are done at each deployment
    (environment) => __awaiter(this, void 0, void 0, function* () {
        const { melonContracts } = environment.deployment;
        getLog(environment).info('Setting registry & engine');
        yield setNativeAsset_1.setNativeAsset(environment, melonContracts.registry, {
            address: wethToken.address,
        });
        yield setMlnToken_1.setMlnToken(environment, melonContracts.registry, {
            address: mlnToken.address,
        });
        yield setEngine_1.setEngine(environment, melonContracts.registry, {
            address: melonContracts.engine,
        });
        yield setRegistry_1.setRegistry(environment, melonContracts.engine, {
            address: melonContracts.registry,
        });
    })), maybeDeploy(['ranking'], environment => deployFundRanking_1.deployFundRanking(environment)), maybeDeploy(['version'], environment => deployVersion_1.deployVersion(environment, {
        engine: environment.deployment.melonContracts.engine,
        factories: environment.deployment.melonContracts.factories,
        mlnToken,
        priceSource: environment.deployment.melonContracts.priceSource,
        registry: environment.deployment.melonContracts.registry,
    })))(new Promise(resolve => resolve(environment)));
    const { melonContracts } = environmentWithDeployment.deployment;
    const registryInformation = yield getRegistryInformation_1.getRegistryInformation(environmentWithDeployment, melonContracts.registry);
    const versionInformation = yield getVersionInformation_1.getVersionInformation(environment, melonContracts.registry, { version: melonContracts.version });
    if (!versionInformation) {
        yield registerVersion_1.registerVersion(environment, melonContracts.registry, {
            address: melonContracts.version,
            name: pkg.version,
        });
    }
    const exchangeConfigs = {
        [Contracts_1.Exchanges.MatchingMarket]: {
            adapter: melonContracts.adapters.matchingMarketAdapter,
            exchange: thirdPartyContracts.exchanges.matchingMarket,
            takesCustody: true,
        },
        [Contracts_1.Exchanges.KyberNetwork]: {
            adapter: melonContracts.adapters.kyberAdapter,
            exchange: thirdPartyContracts.exchanges.kyber.kyberNetworkProxy,
            takesCustody: false,
        },
        [Contracts_1.Exchanges.ZeroEx]: {
            adapter: melonContracts.adapters.zeroExAdapter,
            exchange: thirdPartyContracts.exchanges.zeroEx,
            takesCustody: false,
        },
        [Contracts_1.Exchanges.Ethfinex]: {
            adapter: melonContracts.adapters.ethfinexAdapter,
            exchange: thirdPartyContracts.exchanges.ethfinex.exchange,
            takesCustody: true,
        },
    };
    for (const [exchangeName, exchangeConfig] of R.toPairs(exchangeConfigs)) {
        const exchange = exchangeConfig.exchange.toLowerCase();
        // HACK: Blindly just update all registered exchanges on every deploy
        // TODO: Check the individual entries (address, adapter, takesCustory, sigs)
        //       and only update if changed
        const action = registryInformation.registeredExchanges[exchange]
            ? updateExchange_1.updateExchange
            : registerExchange_1.registerExchange;
        // Action.name is "execute" for both
        const actionName = registryInformation.registeredExchanges[exchange]
            ? 'updateExchange'
            : 'registerExchange';
        const args = {
            adapter: exchangeConfig.adapter,
            exchange: exchangeConfig.exchange,
            sigs: [
                FunctionSignatures_1.FunctionSignatures.makeOrder,
                FunctionSignatures_1.FunctionSignatures.takeOrder,
                FunctionSignatures_1.FunctionSignatures.cancelOrder,
            ],
            takesCustody: exchangeConfig.takesCustody,
        };
        log.debug(actionName, exchangeName, args);
        yield action(environment, melonContracts.registry, args);
    }
    for (const asset of thirdPartyContracts.tokens) {
        if (!registryInformation.registeredAssets[asset.address.toLowerCase()]) {
            yield registerAsset_1.registerAsset(environment, melonContracts.registry, {
                assetAddress: `${asset.address}`,
                assetSymbol: asset.symbol,
                name: '',
                reserveMin: '',
                sigs: [],
                standards: [],
                url: '',
            });
            if (environment.track !== Environment_1.Tracks.KYBER_PRICE) {
                yield setDecimals_1.setDecimals(environment, melonContracts.priceSource, asset);
            }
        }
    }
    if (environment.track === Environment_1.Tracks.KYBER_PRICE) {
        yield updateKyber_1.updateKyber(environmentWithDeployment, environmentWithDeployment.deployment.melonContracts.priceSource);
    }
    else if (environment.track === Environment_1.Tracks.TESTING) {
        const prices = yield updateTestingPriceFeed_1.getConvertedPrices(environmentWithDeployment, 'ETH');
        const testingPriceFeed = yield getContract_1.getContract(environmentWithDeployment, Contracts_1.Contracts.TestingPriceFeed, environmentWithDeployment.deployment.melonContracts.priceSource);
        yield testingPriceFeed.methods
            .update(Object.keys(prices), Object.values(prices).map(e => e.toString()))
            .send({ from: environmentWithDeployment.wallet.address, gas: 8000000 });
    }
    const track = environment.track;
    const network = yield environment.eth.net.getId();
    const deploymentId = `${network}:${track}`;
    // tslint:disable:object-literal-sort-keys
    const deployment = {
        meta: {
            deployer: environment.wallet.address,
            timestamp: new Date().toISOString(),
            track,
            version: pkg.version,
            chain: network,
            description,
        },
        melonContracts: melonContracts,
        thirdPartyContracts,
        exchangeConfigs,
    };
    log.info('Deployed:', deploymentId);
    log.debug('Deployed:', deployment);
    return Object.assign({}, environment, { deployment });
});
