"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());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const initTestEnvironment_1 = require("../utils/initTestEnvironment");
const token_math_1 = require("@melonproject/token-math");
const updateTestingPriceFeed_1 = require("../utils/updateTestingPriceFeed");
const beginSetup_1 = require("../../contracts/factory/transactions/beginSetup");
const getToken_1 = require("../../contracts/dependencies/token/calls/getToken");
const completeSetup_1 = require("../../contracts/factory/transactions/completeSetup");
const createAccounting_1 = require("../../contracts/factory/transactions/createAccounting");
const createFeeManager_1 = require("../../contracts/factory/transactions/createFeeManager");
const createParticipation_1 = require("../../contracts/factory/transactions/createParticipation");
const createPolicyManager_1 = require("../../contracts/factory/transactions/createPolicyManager");
const createShares_1 = require("../../contracts/factory/transactions/createShares");
const createTrading_1 = require("../../contracts/factory/transactions/createTrading");
const createVault_1 = require("../../contracts/factory/transactions/createVault");
const getFundComponents_1 = require("../../utils/getFundComponents");
const withDifferentAccount_1 = require("../../utils/environment/withDifferentAccount");
const deployAndGetSystem_1 = require("../utils/deployAndGetSystem");
const getContract_1 = require("../../utils/solidity/getContract");
const deployContract_1 = require("../../utils/solidity/deployContract");
const Contracts_1 = require("../../Contracts");
const increaseTime_1 = require("../../utils/evm/increaseTime");
const getAllBalances_1 = require("../utils/getAllBalances");
const precisionUnits = token_math_1.power(new token_math_1.BigInteger(10), new token_math_1.BigInteger(18));
let s = {};
beforeAll(() => __awaiter(this, void 0, void 0, function* () {
    s.environment = yield initTestEnvironment_1.initTestEnvironment();
    s.accounts = yield s.environment.eth.getAccounts();
    const { addresses, contracts } = yield deployAndGetSystem_1.deployAndGetSystem(s.environment);
    s.addresses = addresses;
    s = Object.assign(s, contracts);
    [s.deployer, s.manager, s.investor] = s.accounts;
    s.gas = 8000000;
    s.mlnTokenInterface = yield getToken_1.getToken(s.environment, s.mln.options.address);
    s.wethTokenInterface = yield getToken_1.getToken(s.environment, s.weth.options.address);
    const exchangeConfigs = {};
    // Init fees
    s.yearInSeconds = new token_math_1.BigInteger(31536000);
    s.managementFee = getContract_1.getContract(s.environment, Contracts_1.Contracts.ManagementFee, yield deployContract_1.deployContract(s.environment, Contracts_1.Contracts.ManagementFee, []));
    s.performanceFee = getContract_1.getContract(s.environment, Contracts_1.Contracts.PerformanceFee, yield deployContract_1.deployContract(s.environment, Contracts_1.Contracts.PerformanceFee, []));
    s.performanceFeePeriod = new token_math_1.BigInteger(1000);
    s.performanceFeeRate = new token_math_1.BigInteger(token_math_1.multiply(new token_math_1.BigInteger(2), token_math_1.power(new token_math_1.BigInteger(10), new token_math_1.BigInteger(17))));
    const fees = [
        {
            feeAddress: s.managementFee.options.address,
            feePeriod: new token_math_1.BigInteger(0),
            feeRate: new token_math_1.BigInteger(0),
        },
        {
            feeAddress: s.performanceFee.options.address,
            feePeriod: s.performanceFeePeriod,
            feeRate: s.performanceFeeRate,
        },
    ];
    const envManager = withDifferentAccount_1.withDifferentAccount(s.environment, s.manager);
    yield beginSetup_1.beginSetup(envManager, s.version.options.address, {
        defaultTokens: [s.wethTokenInterface, s.mlnTokenInterface],
        exchangeConfigs,
        fees,
        fundName: 'Test fund',
        nativeToken: s.wethTokenInterface,
        priceSource: s.priceSource.options.address,
        quoteToken: s.wethTokenInterface,
    });
    yield createAccounting_1.createAccounting(envManager, s.version.options.address);
    yield createFeeManager_1.createFeeManager(envManager, s.version.options.address);
    yield createParticipation_1.createParticipation(envManager, s.version.options.address);
    yield createPolicyManager_1.createPolicyManager(envManager, s.version.options.address);
    yield createShares_1.createShares(envManager, s.version.options.address);
    yield createTrading_1.createTrading(envManager, s.version.options.address);
    yield createVault_1.createVault(envManager, s.version.options.address);
    const hubAddress = yield completeSetup_1.completeSetup(envManager, s.version.options.address);
    s.fund = yield getFundComponents_1.getFundComponents(envManager, hubAddress);
    yield updateTestingPriceFeed_1.updateTestingPriceFeed(s, s.environment);
}));
test(`fund gets ethToken from investment`, () => __awaiter(this, void 0, void 0, function* () {
    const initialTokenAmount = token_math_1.power(new token_math_1.BigInteger(10), new token_math_1.BigInteger(21));
    yield s.weth.methods
        .transfer(s.investor, `${initialTokenAmount}`)
        .send({ from: s.deployer });
    s.wantedShares = token_math_1.power(new token_math_1.BigInteger(10), new token_math_1.BigInteger(20));
    const preTotalSupply = yield s.fund.shares.methods.totalSupply().call();
    yield s.weth.methods
        .approve(s.fund.participation.options.address, s.wantedShares)
        .send({ from: s.investor, gas: s.gas });
    yield s.fund.participation.methods
        .requestInvestment(`${s.wantedShares}`, `${s.wantedShares}`, s.weth.options.address)
        .send({ from: s.investor, gas: s.gas, value: '10000000000000000' });
    yield updateTestingPriceFeed_1.updateTestingPriceFeed(s, s.environment);
    yield updateTestingPriceFeed_1.updateTestingPriceFeed(s, s.environment);
    yield s.fund.participation.methods
        .executeRequestFor(s.investor)
        .send({ from: s.investor, gas: s.gas });
    const postTotalSupply = yield s.fund.shares.methods.totalSupply().call();
    expect(postTotalSupply).toEqual(token_math_1.add(token_math_1.toBI(preTotalSupply), token_math_1.toBI(s.wantedShares)));
}));
test(`artificially inflate share price by inflating weth`, () => __awaiter(this, void 0, void 0, function* () {
    const preTotalSupply = new token_math_1.BigInteger(yield s.fund.shares.methods.totalSupply().call());
    const preFundCalculations = yield s.fund.accounting.methods
        .performCalculations()
        .call();
    yield s.weth.methods
        .transfer(s.fund.vault.options.address, `${s.wantedShares}`)
        .send({ from: s.deployer });
    const postTotalSupply = new token_math_1.BigInteger(yield s.fund.shares.methods.totalSupply().call());
    const postFundCalculations = yield s.fund.accounting.methods
        .performCalculations()
        .call();
    const feeInDenominationAsset = token_math_1.divide(token_math_1.multiply(token_math_1.toBI(postFundCalculations.feesInShares), token_math_1.toBI(postFundCalculations.gav)), token_math_1.add(postTotalSupply, token_math_1.toBI(postFundCalculations.feesInShares)));
    // const sharePriceUsingNav = divide(
    //   multiply(new BigInteger(postFundCalculations.nav), precisionUnits),
    //   postTotalSupply,
    // );
    const sharePriceUsingGav = token_math_1.divide(token_math_1.multiply(token_math_1.subtract(new token_math_1.BigInteger(postFundCalculations.gav), feeInDenominationAsset), precisionUnits), postTotalSupply);
    expect(postTotalSupply).toEqual(preTotalSupply);
    expect(Number(postFundCalculations.sharePrice)).toBeGreaterThan(Number(preFundCalculations.sharePrice));
    expect(new token_math_1.BigInteger(postFundCalculations.sharePrice)).toEqual(sharePriceUsingGav);
    // TODO: Fix this
    // expect(toBI(postFundCalculations.sharePrice).toString()).toEqual(
    //   sharePriceUsingNav.toString(),
    // );
}));
test(`performance fee is calculated correctly`, () => __awaiter(this, void 0, void 0, function* () {
    const lastHWM = yield s.performanceFee.methods
        .highWaterMark(s.fund.feeManager.options.address)
        .call();
    const currentTotalSupply = new token_math_1.BigInteger(yield s.fund.shares.methods.totalSupply().call());
    const fundCalculations = yield s.fund.accounting.methods
        .performCalculations()
        .call();
    const gavPerShare = token_math_1.divide(token_math_1.multiply(token_math_1.toBI(fundCalculations.gav), precisionUnits), currentTotalSupply);
    const gainInSharePrice = token_math_1.subtract(gavPerShare, token_math_1.toBI(lastHWM));
    const expectedPerformanceFee = token_math_1.divide(token_math_1.multiply(token_math_1.divide(token_math_1.multiply(gainInSharePrice, token_math_1.toBI(s.performanceFeeRate)), precisionUnits), currentTotalSupply), precisionUnits);
    const expectedFeeSharesPreDilution = token_math_1.divide(token_math_1.multiply(currentTotalSupply, expectedPerformanceFee), token_math_1.toBI(fundCalculations.gav));
    const expectedFeeShares = token_math_1.divide(token_math_1.multiply(currentTotalSupply, expectedFeeSharesPreDilution), token_math_1.subtract(currentTotalSupply, expectedFeeSharesPreDilution));
    expect(new token_math_1.BigInteger(fundCalculations.feesInShares)).toEqual(expectedFeeShares);
    // expect(new BigInteger(fundCalculations.feesInDenominationAsset)).toEqual(
    //   expectedPerformanceFee,
    // );
}));
// tslint:disable-next-line:max-line-length
test.skip(`investor redeems half his shares, performance fee deducted`, () => __awaiter(this, void 0, void 0, function* () {
    const currentTotalSupply = new token_math_1.BigInteger(yield s.fund.shares.methods.totalSupply().call());
    // const preManagerShares = new BigInteger(
    //   await s.fund.shares.methods.balanceOf(s.manager).call(),
    // );
    const pre = yield getAllBalances_1.getAllBalances(s, s.accounts, s.fund, s.environment);
    const fundCalculations = yield s.fund.accounting.methods
        .performCalculations()
        .call();
    const redeemingQuantity = token_math_1.divide(s.wantedShares, new token_math_1.BigInteger(2));
    yield s.fund.participation.methods
        .redeemQuantity(`${redeemingQuantity}`)
        .send({ from: s.investor, gas: s.gas });
    // const postManagerShares = new BigInteger(
    //   await s.fund.shares.methods.balanceOf(s.manager).call(),
    // );
    const redeemSharesProportion = token_math_1.divide(token_math_1.multiply(redeemingQuantity, precisionUnits), currentTotalSupply);
    // const redeemSharesProportionAccountingInflation = divide(
    //   multiply(redeemingQuantity, precisionUnits),
    //   add(currentTotalSupply, toBI(fundCalculations.feesInShares)),
    // );
    // const expectedOwedPerformanceFee = divide(
    //   multiply(
    //     redeemSharesProportionAccountingInflation,
    //     toBI(fundCalculations.feesInShares),
    //   ),
    //   precisionUnits,
    // );
    // TODO: Investor why there is a deviation by a factor of 1
    // expect(subtract(postManagerShares, preManagerShares).toString()).toEqual(
    //   expectedOwedPerformanceFee.toString(),
    // );
    yield s.fund.participation.methods
        .redeem()
        .send({ from: s.manager, gas: s.gas });
    const post = yield getAllBalances_1.getAllBalances(s, s.accounts, s.fund, s.environment);
    expect(Number(token_math_1.subtract(post.manager.weth, pre.manager.weth))).toBeCloseTo(Number(token_math_1.divide(token_math_1.multiply(token_math_1.toBI(fundCalculations.feesInDenominationAsset), redeemSharesProportion), precisionUnits)));
}));
test(`manager calls rewardAllFees to update high watermark`, () => __awaiter(this, void 0, void 0, function* () {
    yield increaseTime_1.increaseTime(s.environment, Number(s.performanceFeePeriod));
    const preManagerShares = new token_math_1.BigInteger(yield s.fund.shares.methods.balanceOf(s.manager).call());
    const preFundCalculations = yield s.fund.accounting.methods
        .performCalculations()
        .call();
    yield s.fund.accounting.methods
        .triggerRewardAllFees()
        .send({ from: s.manager, gas: s.gas });
    const currentHWM = yield s.performanceFee.methods
        .highWaterMark(s.fund.feeManager.options.address)
        .call();
    const postManagerShares = new token_math_1.BigInteger(yield s.fund.shares.methods.balanceOf(s.manager).call());
    const postFundCalculations = yield s.fund.accounting.methods
        .performCalculations()
        .call();
    expect(token_math_1.subtract(postManagerShares, preManagerShares)).toEqual(new token_math_1.BigInteger(preFundCalculations.feesInShares));
    expect(postFundCalculations.sharePrice).toEqual(preFundCalculations.sharePrice);
    expect(currentHWM).toEqual(preFundCalculations.gavPerShareNetManagementFee);
    expect(postFundCalculations.gav).toEqual(preFundCalculations.gav);
    // expect(new BigInteger(fundCalculations.feesInDenominationAsset)).toEqual(
    //   expectedPerformanceFee,
    // );
}));
