@melonproject/protocol
Version:
Technology Regulated and Operated Investment Funds
189 lines (188 loc) • 13.6 kB
JavaScript
"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 registerFees_1 = require("../../contracts/version/transactions/registerFees");
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 registerFees_1.registerFees(s.environment, s.registry.options.address, {
addresses: fees.map(f => f.feeAddress),
});
yield beginSetup_1.beginSetup(envManager, s.version.options.address, {
defaultTokens: [s.wethTokenInterface, s.mlnTokenInterface],
exchangeConfigs,
fees,
fundName: 'Test fund',
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 = token_math_1.divide(token_math_1.multiply(new token_math_1.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);
expect(token_math_1.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(Number(fundCalculations.feesInDenominationAsset)).toBeCloseTo(Number(expectedPerformanceFee));
}));
test(`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 token_math_1.BigInteger(yield 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 token_math_1.BigInteger(yield s.fund.shares.methods.balanceOf(s.manager).call());
const redeemSharesProportion = token_math_1.divide(token_math_1.multiply(redeemingQuantity, precisionUnits), currentTotalSupply);
const redeemSharesProportionAccountingInflation = token_math_1.divide(token_math_1.multiply(redeemingQuantity, precisionUnits), token_math_1.add(currentTotalSupply, token_math_1.toBI(fundCalculations.feesInShares)));
const expectedOwedPerformanceFee = token_math_1.divide(token_math_1.multiply(redeemSharesProportionAccountingInflation, token_math_1.toBI(fundCalculations.feesInShares)), precisionUnits);
expect(token_math_1.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,
// );
}));