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

Liquidation Visibility - Iteration One #9

Merged
merged 41 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
680330c
create dedicated files for test assertions and tools
Jorge-Lopes Jan 12, 2024
a3f6cf0
Add new test file for liquidation visibility
Jorge-Lopes Jan 12, 2024
9d7abd6
auctioneer snapshot
Jorge-Lopes Jan 12, 2024
5d1088b
Update visibility tests and helper functions
Jorge-Lopes Jan 12, 2024
f35bda1
add tools and assertions to match subscriber with vstorage data
Jorge-Lopes Jan 15, 2024
26dc28f
update test visibility of vault liquidation
Jorge-Lopes Jan 15, 2024
e603254
Merge branch 'master' into feature/unit-tests
anilhelvaci Jan 18, 2024
ae88a64
fix(liquidationVisibility): fix linting errors
anilhelvaci Jan 18, 2024
7d477ff
fix(liquidationVisibility): type error
anilhelvaci Jan 18, 2024
b4c8a21
feat(liquidationVisibility): create liquidation storageNodes and reco…
Jorge-Lopes Jan 18, 2024
21bb9ca
feat(liquidationVisibility): write preAuctionState and auctionResultS…
Jorge-Lopes Jan 19, 2024
3739f25
fix(liquidationVisibility): fix type definitions errors and concurren…
Jorge-Lopes Jan 19, 2024
5dfa289
chore(liquidationVisibility): #4 testing tools setup
anilhelvaci Jan 21, 2024
bbd2e48
chore(liquidationVisibility): #4 improve testing tools
anilhelvaci Jan 22, 2024
7292068
chore(liquidationVisibility): #4 improve testing tools
anilhelvaci Jan 22, 2024
d8af7fa
fix(liquidationVisibility): linting fixes
anilhelvaci Jan 22, 2024
45cea3b
chore(liquidationVisibility): fix test names
anilhelvaci Jan 22, 2024
89db737
fix(liquidationVisibility): lint fix
anilhelvaci Jan 22, 2024
9fefcf0
chore(liquidationVisibility): #4 implement `assertNodeInStorage`
anilhelvaci Jan 22, 2024
e81df01
fix(liquidationVisibility): #4 lint fix
anilhelvaci Jan 22, 2024
419c810
feat(liquidationVisibility): write postAuctionState to Vstorage
Jorge-Lopes Jan 22, 2024
637a282
chore(liquidationVisibility): update helper methods, type definitions…
Jorge-Lopes Jan 22, 2024
4089361
fix(liquidationVisibility): lint fix
Jorge-Lopes Jan 22, 2024
a5815ae
chore(liquidationVisibility): #4 test skeleton is ready
anilhelvaci Jan 23, 2024
51eb929
chore(liquidationVisibility): #4 add marshaller for comparing data fr…
anilhelvaci Jan 23, 2024
ba95348
fix(liquidationVisibility): update sequence to write auction state to…
Jorge-Lopes Jan 23, 2024
59e155b
chore(liquidationVisibility): sample test for `preAuction` and `postA…
anilhelvaci Jan 24, 2024
1a17dd4
chore(liquidationVisibility): make sure `assertStorageData` works
anilhelvaci Jan 24, 2024
b7cdb13
chore(liquidationVisibility): #4 add test for case 2b, uncomment asse…
anilhelvaci Jan 25, 2024
b906859
fix(liquidationVisibility): update postAuctionState structure and cha…
Jorge-Lopes Jan 25, 2024
e680466
fix(liquidationVisibility): remove temporary changes made to tests
Jorge-Lopes Jan 25, 2024
c4e2b34
Merge remote-tracking branch 'origin/jorge/update-contacts' into anil…
anilhelvaci Jan 25, 2024
34ecf5c
fix(liquidationVisibility): #4 test for scenario 2b passed, test api …
anilhelvaci Jan 25, 2024
348707e
chore(liquidationVisibility): #4 test for scenario 2a passed
anilhelvaci Jan 25, 2024
e7259fb
chore(liquidationVisibility): #4 update scenario 1
alexanderem49 Jan 26, 2024
4c45f2a
fix(liquidationVisibility): add pattern matcher to `getVaultState`
anilhelvaci Jan 30, 2024
683f56d
feat(liquidationVisibility): add LiquidationVisibilityWriters to impr…
anilhelvaci Jan 30, 2024
732e1d7
feat(liquidationVisibility): handle errors that might arise from othe…
anilhelvaci Jan 31, 2024
6920d1a
fix(liquidationVisibility): explain Promise.allSettled
anilhelvaci Jan 31, 2024
dd3fbdb
fix(liquidationVisibility): lint fix
anilhelvaci Jan 31, 2024
728d695
chore(liquidationVisibility): uncomment post auction assertion in `li…
anilhelvaci Jan 31, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/inter-protocol/src/vaultFactory/vault.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export const VaultI = M.interface('Vault', {
getCurrentDebt: M.call().returns(AmountShape),
getNormalizedDebt: M.call().returns(AmountShape),
getVaultSeat: M.call().returns(SeatShape),
getVaultState: M.call().returns(M.any()),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be good to introduce a type guard instead of M.any()

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

initVaultKit: M.call(SeatShape, StorageNodeShape).returns(M.promise()),
liquidated: M.call().returns(undefined),
liquidating: M.call().returns(undefined),
Expand Down Expand Up @@ -597,6 +598,13 @@ export const prepareVault = (baggage, makeRecorderKit, zcf) => {
return this.state.vaultSeat;
},

getVaultState() {
return {
idInManager: this.state.idInManager,
phase: this.state.phase,
};
},

/**
* @param {ZCFSeat} seat
* @param {StorageNode} storageNode
Expand Down
2 changes: 1 addition & 1 deletion packages/inter-protocol/src/vaultFactory/vaultDirector.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ const prepareVaultDirector = (
makeLiquidationWaker() {
return makeWaker('liquidationWaker', _timestamp => {
// XXX floating promise
allManagersDo(vm => vm.liquidateVaults(auctioneer));
allManagersDo(vm => vm.liquidateVaults(auctioneer, _timestamp));
});
},
makeReschedulerWaker() {
Expand Down
154 changes: 143 additions & 11 deletions packages/inter-protocol/src/vaultFactory/vaultManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import {
} from '@agoric/zoe/src/contractSupport/index.js';
import { PriceQuoteShape, SeatShape } from '@agoric/zoe/src/typeGuards.js';
import { E } from '@endo/eventual-send';
import { TimestampShape } from '@agoric/time';
import { AuctionPFShape } from '../auction/auctioneer.js';
import {
checkDebtLimit,
Expand Down Expand Up @@ -171,6 +172,7 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => {
* @typedef {{
* assetTopicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<AssetState>;
* debtBrand: Brand<'nat'>;
* liquidationsStorageNode: StorageNode;
* liquidatingVaults: SetStore<Vault>;
* metricsTopicKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<MetricsNotification>;
* poolIncrementSeat: ZCFSeat;
Expand Down Expand Up @@ -205,6 +207,32 @@ export const watchQuoteNotifier = async (notifierP, watcher, ...args) => {
* storedCollateralQuote: PriceQuote | null;
* }}
*/

/**
* @typedef {(
* | string
* | { collateralAmount: Amount<'nat'>; debtAmount: Amount<'nat'> }
* )[][]} PreAuctionState
*
* @typedef {(string | { phase: string })[][]} PostAuctionState
*
* @typedef {{
* collateralOffered?: Amount<'nat'>;
* istTarget?: Amount<'nat'>;
* collateralForReserve?: Amount<'nat'>;
* shortfallToReserve?: Amount<'nat'>;
* mintedProceeds?: Amount<'nat'>;
* collateralSold?: Amount<'nat'>;
* collateralRemaining?: Amount<'nat'>;
* endTime?: import('@agoric/time').TimestampRecord | null;
* }} AuctionResultState
*
* @typedef {{
* preAuctionRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<PreAuctionState>;
* postAuctionRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<PostAuctionState>;
* auctionResultRecorderKit: import('@agoric/zoe/src/contractSupport/recorder.js').RecorderKit<AuctionResultState>;
* }} LiquidationRecorderKits
*/
// any b/c will be filled after start()
const collateralEphemera = makeEphemeraProvider(() => /** @type {any} */ ({}));

Expand All @@ -227,14 +255,18 @@ export const prepareVaultManagerKit = (
const makeVault = prepareVault(baggage, makeRecorderKit, zcf);

/**
* @param {HeldParams & { metricsStorageNode: StorageNode }} params
* @param {HeldParams & {
* metricsStorageNode: StorageNode;
* liquidationsStorageNode: StorageNode;
* }} params
* @returns {HeldParams & ImmutableState & MutableState}
*/
const initState = params => {
const {
debtMint,
collateralBrand,
metricsStorageNode,
liquidationsStorageNode,
startTimeStamp,
storageNode,
} = params;
Expand All @@ -244,7 +276,7 @@ export const prepareVaultManagerKit = (
const immutable = {
debtBrand,
poolIncrementSeat: zcf.makeEmptySeatKit().zcfSeat,

liquidationsStorageNode,
/**
* Vaults that have been sent for liquidation. When we get proceeds (or
* lack thereof) back from the liquidator, we will allocate them among the
Expand Down Expand Up @@ -336,7 +368,9 @@ export const prepareVaultManagerKit = (
getCollateralQuote: M.call().returns(PriceQuoteShape),
getPublicFacet: M.call().returns(M.remotable('publicFacet')),
lockOraclePrices: M.call().returns(PriceQuoteShape),
liquidateVaults: M.call(AuctionPFShape).returns(M.promise()),
liquidateVaults: M.call(AuctionPFShape, TimestampShape).returns(
M.promise(),
),
}),
},
initState,
Expand Down Expand Up @@ -649,6 +683,48 @@ export const prepareVaultManagerKit = (
return E(metricsTopicKit.recorder).write(payload);
},

/**
* @param {{ absValue: any }} timestamp
* @returns {Promise<LiquidationRecorderKits>}
*/
async makeLiquidationRecorderKits(timestamp) {
const {
state: { liquidationsStorageNode },
} = this;

const timestampStorageNode = E(liquidationsStorageNode).makeChildNode(
`${timestamp.absValue}`,
);

const [
preAuctionStorageNode,
postAuctionStorageNode,
auctionResultStorageNode,
] = await Promise.all([
E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode(
'preAuction',
),
E(E(timestampStorageNode).makeChildNode('vaults')).makeChildNode(
'postAuction',
),
E(timestampStorageNode).makeChildNode('auctionResult'),
]);

const preAuctionRecorderKit = makeRecorderKit(preAuctionStorageNode);
const postAuctionRecorderKit = makeRecorderKit(
postAuctionStorageNode,
);
const auctionResultRecorderKit = makeRecorderKit(
auctionResultStorageNode,
);

return {
preAuctionRecorderKit,
postAuctionRecorderKit,
auctionResultRecorderKit,
};
},

/**
* This is designed to tolerate an incomplete plan, in case
* calculateDistributionPlan encounters an error during its calculation.
Expand Down Expand Up @@ -1115,8 +1191,11 @@ export const prepareVaultManagerKit = (
void facets.helper.writeMetrics();
return storedCollateralQuote;
},
/** @param {ERef<AuctioneerPublicFacet>} auctionPF */
async liquidateVaults(auctionPF) {
/**
* @param {ERef<AuctioneerPublicFacet>} auctionPF
* @param {{ absValue: bigint }} timestamp
*/
async liquidateVaults(auctionPF, timestamp) {
const { state, facets } = this;
const { self, helper } = facets;
const {
Expand Down Expand Up @@ -1183,7 +1262,7 @@ export const prepareVaultManagerKit = (
helper.markLiquidating(totalDebt, totalCollateral);
void helper.writeMetrics();

const { userSeatPromise, deposited } = await E.when(
const makeDeposit = E.when(
E(auctionPF).makeDepositInvitation(),
depositInvitation =>
offerTo(
Expand All @@ -1197,10 +1276,31 @@ export const prepareVaultManagerKit = (
),
);

const [{ userSeatPromise, deposited }, liquidationRecorderKits] =
await Promise.all([
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use Promise.allSettled instead of Promise.all? The reason being, if helper.makeLiquidationRecorderKits somehow fails Promise.all is going reject all together and we won't be able to complete the liquidation operation. What are the possible chances of helper.makeLiquidationRecorderKits failing, I don't know. However, when we think about it, helper.makeLiquidationRecorderKits interacts with another vat by calling makeChildNode. So depending on chainStorage vat to work correctly when settling liquidations seems like an unnecessary dependecy.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.
In this case, we need to handle the promise rejection.
Some tests for this edge case will be required as well.

makeDeposit,
helper.makeLiquidationRecorderKits(timestamp),
]);

/** @type PreAuctionState */
const preAuctionState = [...vaultData.entries()].map(
([vault, data]) => [
`vault${vault.getVaultState().idInManager}`,
{ ...data },
],
);

// This is expected to wait for the duration of the auction, which
// is controlled by the auction parameters startFrequency, clockStep,
// and the difference between startingRate and lowestRate.
const [proceeds] = await Promise.all([deposited, userSeatPromise]);
const [auctionSchedule, proceeds] = await Promise.all([
E(auctionPF).getSchedules(),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point in time the auction is already finished. The question is whether or not we get the schedule where liveScheduleAuction is still pointing to the auction we just finished. This is what we need to indicate the endTime of the current liquidation auction.

I'm not sure if we can depend on (E(acutionPF).getSchedule()).liveAuctionSchedule is always going to point to right value due to distributed nature of the vaultFactory and auctioneer vats. Despite not being able put my finger directly on the possibility of a race condition, I still suggest we take the conservative path and call E(acutionPF).getSchedule() somewhere before await deposited.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.
In this case, we need to handle the promise rejection.
Some tests for this edge case will be required as well.

deposited,
userSeatPromise,
E(
liquidationRecorderKits.preAuctionRecorderKit.recorder,
).writeFinal(preAuctionState),
]);

const { storedCollateralQuote } = collateralEphemera(
this.state.collateralBrand,
Expand All @@ -1227,6 +1327,35 @@ export const prepareVaultManagerKit = (
totalDebt,
vaultsInPlan,
});

/** @type AuctionResultState */
const auctionResultState = {
collateralOffered: totalCollateral,
istTarget: totalDebt,
collateralForReserve: plan.collateralForReserve,
shortfallToReserve: plan.shortfallToReserve,
mintedProceeds: plan.mintedProceeds,
collateralSold: plan.collateralSold,
collateralRemaining: plan.collatRemaining,
endTime: auctionSchedule.liveAuctionSchedule?.endTime,
};
void E(
liquidationRecorderKits.auctionResultRecorderKit.recorder,
).writeFinal(auctionResultState);

/** @type PostAuctionState */
const postAuctionState = plan.transfersToVault.map(
([id, transfer]) => [
`vault${vaultsInPlan[id].getVaultState().idInManager}`,
{
...transfer,
phase: vaultsInPlan[id].getVaultState().phase,
},
],
);
void E(
liquidationRecorderKits.postAuctionRecorderKit.recorder,
).writeFinal(postAuctionState);
} catch (err) {
console.error('🚨 Error distributing proceeds:', err);
}
Expand Down Expand Up @@ -1265,16 +1394,19 @@ export const prepareVaultManagerKit = (
/**
* @param {Omit<
* Parameters<typeof makeVaultManagerKitInternal>[0],
* 'metricsStorageNode'
* 'metricsStorageNode' | 'liquidationsStorageNode'
* >} externalParams
*/
const makeVaultManagerKit = async externalParams => {
const metricsStorageNode = await E(
externalParams.storageNode,
).makeChildNode('metrics');
const [metricsStorageNode, liquidationsStorageNode] = await Promise.all([
E(externalParams.storageNode).makeChildNode('metrics'),
E(externalParams.storageNode).makeChildNode('liquidations'),
]);

return makeVaultManagerKitInternal({
...externalParams,
metricsStorageNode,
liquidationsStorageNode,
});
};
return makeVaultManagerKit;
Expand Down
Loading
Loading