Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add multiple auction simulation #610

Draft
wants to merge 10 commits into
base: main
Choose a base branch
from
4 changes: 4 additions & 0 deletions core/simulations/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import surplusAuctionSimulation from './configs/createSurplusAcutionSimulation';
import blockWithVaultsInAllStates from './configs/blocksWithVaultsInAllStates';
import vaultLiquidation from './configs/vaultLiquidation';
import onboardNewCollateral from './configs/onboardNewCollateral';
import multipleVaultLiquidation from './configs/multipleVaultLiquidation';
import skipTime from './configs/skipTime';

export const SIMULATIONS: Simulation[] = [
vaultLiquidation,
Expand All @@ -15,4 +17,6 @@ export const SIMULATIONS: Simulation[] = [
specificBlockFork,
blockWithVaultsInAllStates,
onboardNewCollateral,
multipleVaultLiquidation,
skipTime,
];
197 changes: 197 additions & 0 deletions core/simulations/configs/multipleVaultLiquidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
import { warpTime, resetNetworkAndSetupWallet } from '../../helpers/hardhat/network';
import { addDaiToBalance, addMkrToBalance } from '../../helpers/hardhat/balance';
import { Simulation } from '../types';
import { collectStabilityFees, fetchVault, liquidateVault } from '../../src/vaults';
import { TEST_NETWORK } from '../../helpers/constants';
import createVaultWithCollateral, {
adjustLimitsAndRates,
calculateMinCollateralAmountToOpenVault,
} from '../helpers/createVaultWithCollateral';
import promptToSelectMultipleOptions from '../helpers/promptToSelectMultipleOptions';
import promptToGetBlockNumber from '../helpers/promptToGetBlockNumber';
import promptYesNo from '../helpers/promptYesNo';

import { fetchMaximumAuctionDurationInSeconds } from '../../src/fetch';
import { getAllCollateralTypes } from '../../src/constants/COLLATERALS';
import promptNumber from '../helpers/promptNumber';
import BigNumber from '../../src/bignumber';

const TWO_YEARS_IN_MINUTES = 60 * 24 * 30 * 12 * 2;

interface SuccessfulVaultCreationOutcome {
result: 'success';
type: string;
latestVaultId: number;
}

interface FailedVaultCreation {
type: string;
amount: string;
error: string;
}

interface FailedVaultCreationOutcome extends FailedVaultCreation {
result: 'error';
}

async function createVaultOrReportFailure(
collateralType: string,
amount: BigNumber
): Promise<SuccessfulVaultCreationOutcome | FailedVaultCreationOutcome> {
try {
const latestVaultId: number = await createVaultWithCollateral(collateralType, amount);
return { type: collateralType, latestVaultId, result: 'success' };
} catch (error) {
return {
result: 'error',
type: collateralType,
amount: amount.toFixed(),
error: (error as Error).message || 'unknown',
};
}
}

async function printErrorsForVaultCreation(failedVaultCreations: FailedVaultCreation[]) {
console.info('Some of the vaults were not created.');
const doPrintErrors = await promptYesNo('Print blockchain errors in the list? (they can be long)');
for (const [index, failed] of failedVaultCreations.entries()) {
let message = `${index + 1}. Failed to create vault with type ${failed.type} and amount ${failed.amount}`;
if (doPrintErrors) {
message += `; Error:\n${failed.error}\n`;
}
console.warn(message);
}
}

const simulation: Simulation = {
title: 'Create multiple collateral auctions',
steps: [
{
title: 'Reset blockchain fork',
entry: async () => {
const number = await promptToGetBlockNumber();
const vaultNumberPerCollateral = await promptNumber({
title: 'Number of vaults to create per collateral type',
min: 1,
max: 5,
initial: 1,
});
await resetNetworkAndSetupWallet(number);
const collateralTypes = await promptToSelectMultipleOptions(
'Select the collateral symbols to add to the VAT.',
getAllCollateralTypes()
);
if (collateralTypes.length === 0) {
throw new Error('No collateral types selected.');
}
return {
collateralTypes,
vaultNumberPerCollateral,
};
},
},
{
title: 'Create the vaults',
entry: async context => {
const types: string[] = context.collateralTypes;
for (const type of types) {
await adjustLimitsAndRates(type);
}
const collateralsOwned = await Promise.all(
types.map(async type => ({
type,
minCollateralAmount: await calculateMinCollateralAmountToOpenVault(type),
}))
);
console.info(
`Minimum collaterals amount to open vault: ${JSON.stringify(
collateralsOwned.map(c => ({
[c.type]: c.minCollateralAmount.toFixed(),
}))
)}`
);
const vaultIds: { type: string; latestVaultId: number }[] = [];
const failedVaultCreations: FailedVaultCreation[] = [];
for (const collateralOwned of collateralsOwned) {
let multiplier = 1;
for (let _i = 0; _i < context.vaultNumberPerCollateral; _i++) {
const amount = collateralOwned.minCollateralAmount.multipliedBy(multiplier);
const outcome = await createVaultOrReportFailure(collateralOwned.type, amount);
multiplier += 1;
if (outcome.result === 'success') {
vaultIds.push({ type: outcome.type, latestVaultId: outcome.latestVaultId });
console.info(`Created Vault id: ${outcome.latestVaultId}`);
continue;
}
failedVaultCreations.push({
type: outcome.type,
amount: outcome.amount,
error: outcome.error,
});
}
}
if (failedVaultCreations.length !== 0) {
await printErrorsForVaultCreation(failedVaultCreations);
}
return { ...context, vaultIds };
},
},
{
title: 'Skip time',
entry: async context => {
await warpTime(TWO_YEARS_IN_MINUTES, 60);
return context;
},
},
{
title: 'Collect stability fees',
entry: async context => {
const vaultIds = context.vaultIds;
for (const vault of vaultIds) {
const vaultBefore = await fetchVault(TEST_NETWORK, vault.latestVaultId);
console.info(`stability fees before ${vaultBefore.stabilityFeeRate}`);
await collectStabilityFees(TEST_NETWORK, vault.type);
const vaultAfter = await fetchVault(TEST_NETWORK, vault.latestVaultId);
console.info(`stability fees after ${vaultAfter.stabilityFeeRate}`);
}
return context;
},
},
{
title: 'Liquidate the vault',
entry: async context => {
const vaultIds = context.vaultIds;
for (const vault of vaultIds) {
const v = await fetchVault(TEST_NETWORK, vault.latestVaultId);
await liquidateVault(TEST_NETWORK, v.collateralType, v.address);
}
return context;
},
},
{
title: 'Skip time',
entry: async context => {
const auctionLifetime = await fetchMaximumAuctionDurationInSeconds(
TEST_NETWORK,
context.collateralTypes[0]
);
const warpSeconds = Math.floor(auctionLifetime / 2);
if (!warpSeconds) {
throw new Error('Auction lifetime is too short to warp time.');
}
console.info(`Skipping time: ${warpSeconds} seconds`);
await warpTime(warpSeconds, 1);
return context;
},
},
{
title: 'Add DAI and MKR to the wallet',
entry: async () => {
await addDaiToBalance();
await addMkrToBalance();
},
},
],
};

export default simulation;
24 changes: 24 additions & 0 deletions core/simulations/configs/skipTime.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { warpTime, createWalletForRpc } from '../../helpers/hardhat/network';
import { Simulation } from '../types';
import promptNumber from '../helpers/promptNumber';

const simulation: Simulation = {
title: 'Skip time',
steps: [
{
title: 'Skip time',
entry: async () => {
await createWalletForRpc();
const blocks = await promptNumber({ title: 'Number of blocks to skip', min: 1, initial: 10 });
const gapMinutes = await promptNumber({
title: 'Number of minutes between blocks',
min: 1,
initial: 6,
});
await warpTime(blocks, gapMinutes);
return;
},
},
],
};
export default simulation;
26 changes: 26 additions & 0 deletions core/simulations/helpers/promptNumber.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import prompts from 'prompts';

interface BlockNumberPromt {
title?: string;
initial?: number;
min?: number;
max?: number;
}
const promptNumber = async (params?: BlockNumberPromt) => {
const title: string = params?.title || 'Number';
const min = params?.min || 0;
const initial = params?.initial;
const { number } = await prompts([
{
type: 'number',
name: 'number',
message: title,
initial,
min,
max: params?.max,
},
]);
return number;
};

export default promptNumber;
21 changes: 21 additions & 0 deletions core/simulations/helpers/promptToSelectMultipleOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import prompts from 'prompts';

const promptToSelectMultipleOptions = async (title: string, values: string[]) => {
if (values.length <= 1) {
return values[0];
}
const { selectedValues } = await prompts([
{
type: 'multiselect',
name: 'selectedValues',
message: title,
choices: values.map(value => ({
title: value,
value: value,
})),
},
]);
return selectedValues;
};

export default promptToSelectMultipleOptions;
16 changes: 16 additions & 0 deletions core/simulations/helpers/promptYesNo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import prompts from 'prompts';

const promptYesNo = async (title: string, positive = 'yes', negative = 'no') => {
const { answer } = await prompts([
{
type: 'toggle',
name: 'answer',
message: title,
active: positive,
inactive: negative,
},
]);
return answer;
};

export default promptYesNo;