Skip to content

Commit

Permalink
chore(liquidationVisibility): #4 testing tools setup
Browse files Browse the repository at this point in the history
  • Loading branch information
anilhelvaci committed Jan 21, 2024
1 parent 7d477ff commit 5dfa289
Show file tree
Hide file tree
Showing 3 changed files with 323 additions and 14 deletions.
42 changes: 32 additions & 10 deletions packages/inter-protocol/test/liquidationVisibility/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,11 @@ export const assertReserveState = async (t, metricsTopic, method, expected) => {
await m.assertState(expected);
break;
default:
console.log('Default')
console.log('Default');
break;
}

return m;
};

export const assertVaultCurrentDebt = async (t, vault, debt) => {
Expand Down Expand Up @@ -81,6 +83,20 @@ export const assertMintedAmount = async (t, vaultSeat, wantMinted) => {
t.truthy(AmountMath.isEqual(Minted, wantMinted));
};

export const assertMintedProceeds = async (t, vaultSeat, wantMinted) => {
const { Minted } = await E(vaultSeat).getFinalAllocation();
const { Minted: proceedsMinted } = await E(vaultSeat).getPayouts();

t.truthy(AmountMath.isEqual(Minted, wantMinted));

t.truthy(
AmountMath.isEqual(
await E(t.context.run.issuer).getAmountOf(proceedsMinted),
wantMinted,
),
);
};

export const assertVaultLocked = async (t, vaultNotifier, lockedValue) => {
const notification = await E(vaultNotifier).getUpdateSince();
const lockedAmount = notification.value.locked;
Expand All @@ -97,13 +113,17 @@ export const assertVaultDebtSnapshot = async (t, vaultNotifier, wantMinted) => {
debt: AmountMath.add(wantMinted, fee),
interest: makeRatio(100n, t.context.run.brand),
});

return notification;
};

export const assertVaultState = async (t, vaultNotifier, phase) => {
const notification = await E(vaultNotifier).getUpdateSince();
const vaultState = notification.value.vaultState;

t.is(vaultState, phase);

return notification;
};

export const assertVaultSeatExited = async (t, vaultSeat) => {
Expand All @@ -122,16 +142,18 @@ export const assertVaultFactoryRewardAllocation = async (
});
};

export const assertCollateralProceeds = async (
t,
proceedsCollateralPayment,
collProceedsValue,
) => {
const collProceeds = await t.context.aeth.issuer.getAmountOf(
proceedsCollateralPayment,
export const assertCollateralProceeds = async (t, seat, colWanted) => {
const { Collateral: withdrawnCol } = await E(seat).getFinalAllocation();
const proceeds4 = await E(seat).getPayouts();
t.deepEqual(withdrawnCol, colWanted);

const collateralWithdrawn = await proceeds4.Collateral;
t.truthy(
AmountMath.isEqual(
await E(t.context.aeth.issuer).getAmountOf(collateralWithdrawn),
colWanted,
),
);

t.deepEqual(collProceeds, t.context.aeth.make(collProceedsValue));
};

// Update these assertions to use a tracker similar to test-auctionContract
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,34 @@ import { deeplyFulfilled } from '@endo/marshal';
import { makeTracer } from '@agoric/internal';
import { buildManualTimer } from '@agoric/swingset-vat/tools/manual-timer.js';
import {
ceilMultiplyBy,
makeRatio,
makeRatioFromAmounts,
} from '@agoric/zoe/src/contractSupport/index.js';
import { documentStorageSchema } from '@agoric/governance/tools/storageDoc.js';
import { AmountMath } from '@agoric/ertp';
import {
defaultParamValues,
legacyOfferResult,
} from '../vaultFactory/vaultFactoryUtils.js';
import { SECONDS_PER_HOUR as ONE_HOUR } from '../../src/proposals/econ-behaviors.js';
import { reserveInitialState } from '../metrics.js';
import {
SECONDS_PER_HOUR as ONE_HOUR,
SECONDS_PER_DAY as ONE_DAY,
SECONDS_PER_WEEK as ONE_WEEK,
} from '../../src/proposals/econ-behaviors.js';
import {
reserveInitialState,
subscriptionTracker,
vaultManagerMetricsTracker,
} from '../metrics.js';
import {
bid,
setClockAndAdvanceNTimes,
setupBasics,
setupServices,
startAuctionClock,
getDataFromVstorage,
openVault,
} from './tools.js';
import {
assertBidderPayout,
Expand All @@ -41,7 +52,9 @@ import {
assertAuctioneerSchedule,
assertAuctioneerPathData,
assertVaultData,
assertMintedProceeds,
} from './assertions.js';
import { Phase } from '../vaultFactory/driver.js';

const trace = makeTracer('TestLiquidationVisibility', false);

Expand Down Expand Up @@ -180,6 +193,196 @@ test('test vault liquidation', async t => {
await assertReserveState(t, metricsTopic, 'like', expectedReserveState);
});

// We'll make a loan, and trigger liquidation via price changes. The interest
// rate is 40%. The liquidation margin is 105%. The priceAuthority will
// initially quote 10:1 Run:Aeth, and drop to 7:1. The loan will initially be
// overcollateralized 100%. Alice will withdraw enough of the overage that
// she'll get caught when prices drop.
// A bidder will buy at the 65% level, so there will be a shortfall.
test('Auction sells all collateral w/shortfall', async t => {
const { zoe, aeth, run, rates: defaultRates } = t.context;

// Add a vaultManager with 10000 aeth collateral at a 200 aeth/Minted rate
const rates = harden({
...defaultRates,
// charge 40% interest / year
interestRate: run.makeRatio(40n),
liquidationMargin: run.makeRatio(130n),
});
t.context.rates = rates;

// Interest is charged daily, and auctions are every week
t.context.interestTiming = {
chargingPeriod: ONE_DAY,
recordingPeriod: ONE_DAY,
};

const manualTimer = buildManualTimer();
const services = await setupServices(
t,
makeRatio(100n, run.brand, 10n, aeth.brand),
aeth.make(1n),
manualTimer,
ONE_WEEK,
{ StartFrequency: ONE_HOUR },
);

const {
vaultFactory: { aethCollateralManager },
aethTestPriceAuthority,
reserveKit: { reserveCreatorFacet, reservePublicFacet },
auctioneerKit,
} = services;
await E(reserveCreatorFacet).addIssuer(aeth.issuer, 'Aeth');

const metricsTopic = await E.get(E(reservePublicFacet).getPublicTopics())
.metrics;
const m = await assertReserveState(
t,
metricsTopic,
'initial',
reserveInitialState(run.makeEmpty()),
);
let shortfallBalance = 0n;

const aethVaultMetrics = await vaultManagerMetricsTracker(
t,
aethCollateralManager,
);
await aethVaultMetrics.assertInitial({
// present
numActiveVaults: 0,
numLiquidatingVaults: 0,
totalCollateral: aeth.make(0n),
totalDebt: run.make(0n),
retainedCollateral: aeth.make(0n),

// running
numLiquidationsCompleted: 0,
numLiquidationsAborted: 0,
totalOverageReceived: run.make(0n),
totalProceedsReceived: run.make(0n),
totalCollateralSold: aeth.make(0n),
liquidatingCollateral: aeth.make(0n),
liquidatingDebt: run.make(0n),
totalShortfallReceived: run.make(0n),
lockedQuote: null,
});

// ALICE's loan ////////////////////////////////////////////

// Create a loan for Alice for 5000 Minted with 1000 aeth collateral
// ratio is 4:1
const aliceCollateralAmount = aeth.make(1000n);
const aliceWantMinted = run.make(5000n);
/** @type {UserSeat<VaultKit>} */
const aliceVaultSeat = await openVault({
t,
cm: aethCollateralManager,
collateralAmount: aliceCollateralAmount,
wantMintedAmount: aliceWantMinted,
colKeyword: 'aeth',
});
const {
vault: aliceVault,
publicNotifiers: { vault: aliceNotifier },
} = await legacyOfferResult(aliceVaultSeat);

await assertVaultCurrentDebt(t, aliceVault, aliceWantMinted);
await assertMintedProceeds(t, aliceVaultSeat, aliceWantMinted);
await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted);

let totalDebt = 5250n;
await aethVaultMetrics.assertChange({
numActiveVaults: 1,
totalCollateral: { value: 1000n },
totalDebt: { value: totalDebt },
});

// reduce collateral /////////////////////////////////////

trace(t, 'alice reduce collateral');

// Alice reduce collateral by 300. That leaves her at 700 * 10 > 1.05 * 5000.
// Prices will drop from 10 to 7, she'll be liquidated: 700 * 7 < 1.05 * 5000.
const collateralDecrement = aeth.make(300n);
const aliceReduceCollateralSeat = await E(zoe).offer(
E(aliceVault).makeAdjustBalancesInvitation(),
harden({
want: { Collateral: collateralDecrement },
}),
);
await E(aliceReduceCollateralSeat).getOfferResult();

trace('alice ');
await assertCollateralProceeds(t, aliceReduceCollateralSeat, aeth.make(300n));

await assertVaultDebtSnapshot(t, aliceNotifier, aliceWantMinted);
trace(t, 'alice reduce collateral');
await aethVaultMetrics.assertChange({
totalCollateral: { value: 700n },
});

await E(aethTestPriceAuthority).setPrice(
makeRatio(70n, run.brand, 10n, aeth.brand),
);
trace(t, 'changed price to 7 RUN/Aeth');

// A BIDDER places a BID //////////////////////////
const bidAmount = run.make(3300n);
const desired = aeth.make(700n);
const bidderSeat = await bid(t, zoe, auctioneerKit, aeth, bidAmount, desired);

const { startTime: start1, time: now1 } = await startAuctionClock(
auctioneerKit,
manualTimer,
);
let currentTime = now1;

await aethVaultMetrics.assertChange({
lockedQuote: makeRatioFromAmounts(
aeth.make(1_000_000n),
run.make(7_000_000n),
),
});

// expect Alice to be liquidated because her collateral is too low.
await assertVaultState(t, aliceNotifier, Phase.LIQUIDATING);

currentTime = await setClockAndAdvanceNTimes(manualTimer, 2, start1, 2n);

await assertVaultState(t, aliceNotifier, Phase.LIQUIDATED);
trace(t, 'alice liquidated', currentTime);
totalDebt += 30n;
await aethVaultMetrics.assertChange({
numActiveVaults: 0,
numLiquidatingVaults: 1,
liquidatingCollateral: { value: 700n },
liquidatingDebt: { value: 5250n },
lockedQuote: null,
});

shortfallBalance += 2065n;
await m.assertChange({
shortfallBalance: { value: shortfallBalance },
});

await aethVaultMetrics.assertChange({
liquidatingDebt: { value: 0n },
liquidatingCollateral: { value: 0n },
totalCollateral: { value: 0n },
totalDebt: { value: 0n },
numLiquidatingVaults: 0,
numLiquidationsCompleted: 1,
totalCollateralSold: { value: 700n },
totalProceedsReceived: { value: 3185n },
totalShortfallReceived: { value: shortfallBalance },
});

// Bidder bought 800 Aeth
await assertBidderPayout(t, bidderSeat, run, 115n, aeth, 700n);
});

test('test liquidate vault with snapshot', async t => {
const { zoe, run, aeth } = t.context;
const manualTimer = buildManualTimer();
Expand Down
Loading

0 comments on commit 5dfa289

Please sign in to comment.