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

feat(run-protocol): distinguish LIQUIDATED from CLOSED vault state #4640

Merged
merged 4 commits into from
Feb 24, 2022

Conversation

turadg
Copy link
Member

@turadg turadg commented Feb 22, 2022

closes: #4539

Description

  • rename VaultState to VaultPhase in anticipation of virtual object refactor (in which vault "state" encompasses all state)
  • make a distinct state for "liquidated"
  • runtime validation of state transitions

Security Considerations

--

Documentation Considerations

--

Testing Considerations

Tests updated.

// TODO state distinct from CLOSED https://github.com/Agoric/agoric-sdk/issues/4539
liquidated: vaultState === VaultState.CLOSED,
vaultState: vstate,
liquidated: phase === VaultPhase.LIQUIDATED,
Copy link
Contributor

Choose a reason for hiding this comment

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

I suspect the UI actually wants to know "is the vault closed or liquidated?". "closed" is a better term for that than "liquidated", but maybe the UI needs to distinguish [ [Active], [Closed, Liquidated], [Transferring]]. I think Liquidating is most like Closed and Liquidating among those, but we should probably check with @samsiegart.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm assuming the end user would want to know all of these states. I'd really like to remove this flag entirely since it's redundant with the richer option, but I was trying to maintain backwards compatibility for now. To that end, I suppose CLOSED achieves that more!

@samsiegart is this backwards compatibility necessary or can we drop the liquidated flag in this PR? If not now then when would be good?

Copy link
Member Author

@turadg turadg Feb 23, 2022

Choose a reason for hiding this comment

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

from convo today, decision was to drop this boolean and rely just on the phase/state value 797a9cf

case VaultState.LIQUIDATING:
switch (vaultPhase) {
case VaultPhase.ACTIVE:
case VaultPhase.LIQUIDATING:
Copy link
Contributor

Choose a reason for hiding this comment

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

add TRANSFERABLE here.

If no tests broke, then there aren't any tests for transfering. Please add some tests in that case.

Copy link
Member

Choose a reason for hiding this comment

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

There are tests for transferring. "Transfer" is only visible in finish event that is generated when the transfer is started

Copy link
Member Author

@turadg turadg Feb 23, 2022

Choose a reason for hiding this comment

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

This is a smell though that the transfer case is so special. Refactored in 29b997

Copy link
Member Author

Choose a reason for hiding this comment

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

I hit some snags so intend to punt fcdd7a0 until #4415

* @typedef {'active' | 'liquidating' | 'closed' | 'transfer'} VAULT_STATE
* ACTIVE - vault is in use and can be changed
* LIQUIDATING - vault is being liquidated by the vault manager, and cannot be changed by the user
* TRANSFERABLE - vault is released from the manager and able to be transferred
Copy link
Member

Choose a reason for hiding this comment

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

What does "released from the manager" mean?

Copy link
Member

Choose a reason for hiding this comment

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

I like "transfering" better than "tranferable" I think

Copy link
Member Author

Choose a reason for hiding this comment

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

"released from manager" was when I thought the vault freezes for transfer and can't be liquidated, but that's wrong so I'll fix this. What's the best way to describe this? "Able to be transferred (payments and debits frozen until it has a new owner)" ?

Regarding the term, "transferable" feels clunky but "transfering" isn't necessarily true, right? It could sit inert in a transferable state indefinitely. How about SEALED? It can be sealed for transfer and unsealed upon reception.

Copy link
Member Author

Choose a reason for hiding this comment

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

back to TRANSFER but I don't like that it's a verb and the rest are adjectives 792c388

/**
* @param {VAULT_PHASE} newPhase
*/
const assignPhase = newPhase => {
Copy link
Member

Choose a reason for hiding this comment

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

If we want a state machine...then we have a library for it, and can potentially integrate that with the enum proposal

Copy link
Member Author

Choose a reason for hiding this comment

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

Nice. I was considering a fuller state machine with transition functions and figured this was a good incremental step that wasn't entirely out of scope for the ticket :)

I'd like to hear more about the library we have and this enum proposal and consider refactoring this in the future to use them.

Copy link
Contributor

Choose a reason for hiding this comment

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

It's not much: .../zoe/src/contractSupport/stateMachine.js. It started approximately like this. Just enough for present purposes, with a hope for more when we need it.

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for the link. I don't think that version is going to work well for state that need to be serialized into virtual objects because the stateMachine assumes the state is in its own closure and it's got to be in the state object.

We could isolate the actual state from the transition mechanics with a design like https://kentcdodds.com/blog/implementing-a-simple-state-machine-library-in-javascript that would allow:

state.phase = machine.transition(state.phase, 'liquidate')

However I think that's out of scope of this PR and the assignState method closes the gap to a full machine if that proves worthwhile.

@@ -258,7 +290,7 @@ export const makeInnerVault = (
return makeRatioFromAmounts(collateralValueInRun, getDebtAmount());
};

const snapshotState = (vstate, collateralizationRatio) => {
const snapshotState = (phase, collateralizationRatio) => {
Copy link
Member

Choose a reason for hiding this comment

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

not sure I like "phase" when it's really the state for a state machine

Copy link
Member Author

Choose a reason for hiding this comment

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

The motivation is for when we're on virtual objects. Writing state.state = VaultState.ACTIVE was worse :)

// TODO state distinct from CLOSED https://github.com/Agoric/agoric-sdk/issues/4539
liquidated: vaultState === VaultState.CLOSED,
vaultState: vstate,
liquidated: phase === VaultPhase.LIQUIDATED,
Copy link
Member

Choose a reason for hiding this comment

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

this should be named "isClosed"

@@ -285,26 +316,27 @@ export const makeInnerVault = (
// [https://github.com/Agoric/dapp-token-economy/issues/123]
const collateralizationRatio = await getCollateralizationRatio();
/** @type {VaultUIState} */
const uiState = snapshotState(vaultState, collateralizationRatio);
const uiState = snapshotState(vaultPhase, collateralizationRatio);
Copy link
Member

Choose a reason for hiding this comment

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

I'd use "phase" rather than "vaultPhase" if we are using it

Copy link
Member Author

@turadg turadg Feb 23, 2022

Choose a reason for hiding this comment

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

👍 837381e

case VaultState.LIQUIDATING:
switch (vaultPhase) {
case VaultPhase.ACTIVE:
case VaultPhase.LIQUIDATING:
Copy link
Member

Choose a reason for hiding this comment

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

There are tests for transferring. "Transfer" is only visible in finish event that is generated when the transfer is started

@turadg turadg force-pushed the ta/4539-liquidated-vs-closed branch from 797a9cf to 3b4843e Compare February 23, 2022 23:36
@turadg turadg marked this pull request as ready for review February 24, 2022 01:34
@turadg turadg force-pushed the ta/4539-liquidated-vs-closed branch 2 times, most recently from 5a0ed49 to 546f638 Compare February 24, 2022 13:27
@@ -1657,15 +1657,15 @@ test('mutable liquidity triggers and interest', async t => {
await waitForPromisesToSettle();
aliceUpdate = await E(aliceNotifier).getUpdateSince(aliceUpdate.updateCount);
bobUpdate = await E(bobNotifier).getUpdateSince();
t.truthy(aliceUpdate.value.liquidated);
t.is(aliceUpdate.value.vaultState, 'liquidated');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(aliceUpdate.value.vaultState, 'liquidated');
t.is(aliceUpdate.value.vaultState, VaultPhase.LIQUIDATED);

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree it's better to use a constant instead of the literal. I'll define the constant in this test so that if the actual string VaultFactory returns changes this test will break.


for (let i = 0; i < 5; i += 1) {
manualTimer.tick();
}
await waitForPromisesToSettle();
bobUpdate = await E(bobNotifier).getUpdateSince();

t.truthy(bobUpdate.value.liquidated);
t.is(bobUpdate.value.vaultState, 'liquidated');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(bobUpdate.value.vaultState, 'liquidated');
t.is(bobUpdate.value.vaultState, VaultPhase.LIQUIDATED);

@@ -2159,13 +2159,13 @@ test('mutable liquidity triggers and interest sensitivity', async t => {
await waitForPromisesToSettle();
aliceUpdate = await E(aliceNotifier).getUpdateSince(aliceUpdate.updateCount);
bobUpdate = await E(bobNotifier).getUpdateSince();
t.falsy(aliceUpdate.value.liquidated);
t.is(aliceUpdate.value.vaultState, 'active');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(aliceUpdate.value.vaultState, 'active');
t.is(aliceUpdate.value.vaultState, VaultPhase.ACTIVE);


for (let i = 0; i < 5; i += 1) {
manualTimer.tick();
}
await waitForPromisesToSettle();
bobUpdate = await E(bobNotifier).getUpdateSince();

t.truthy(bobUpdate.value.liquidated);
t.is(bobUpdate.value.vaultState, 'liquidated');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(bobUpdate.value.vaultState, 'liquidated');
t.is(bobUpdate.value.vaultState, VaultPhase.LIQUIDATED);


const debtAmountAfter = await E(vault).getDebtAmount();
const finalNotification = await E(uiNotifier).getUpdateSince();
t.truthy(finalNotification.value.liquidated);
t.is(finalNotification.value.vaultState, 'liquidated');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(finalNotification.value.vaultState, 'liquidated');
t.is(finalNotification.value.vaultState, VaultPhase.LIQUIDATED);


await manualTimer.tick();
notification = await E(uiNotifier).getUpdateSince(notification.updateCount);
t.falsy(notification.updateCount);
t.truthy(notification.value.liquidated);
t.is(notification.value.vaultState, 'liquidated');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(notification.value.vaultState, 'liquidated');
t.is(notification.value.vaultState, VaultPhase.LIQUIDATED);


await manualTimer.tick();
notification = await E(uiNotifier).getUpdateSince(notification.updateCount);
trace('price changed to liquidate', notification.value.vaultState);
t.falsy(notification.value.liquidated);
t.is(notification.value.vaultState, VaultState.LIQUIDATING);
t.is(notification.value.vaultState, 'liquidating');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(notification.value.vaultState, 'liquidating');
t.is(notification.value.vaultState, VaultPhase.LIQUIDATING);

@@ -519,22 +519,22 @@ test('price drop', async t => {
);
await manualTimer.tick();
notification = await E(uiNotifier).getUpdateSince();
t.falsy(notification.value.liquidated);
t.is(notification.value.vaultState, 'active');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(notification.value.vaultState, 'active');
t.is(notification.value.vaultState, VaultPhase.ACTIVE);

@@ -508,7 +508,7 @@ test('price drop', async t => {
let notification = await E(uiNotifier).getUpdateSince();
trace('got notificaation', notification);

t.falsy(notification.value.liquidated);
t.is(notification.value.vaultState, 'active');
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
t.is(notification.value.vaultState, 'active');
t.is(notification.value.vaultState, VaultPhase.ACTIVE);

@turadg turadg force-pushed the ta/4539-liquidated-vs-closed branch from 546f638 to 7fdf359 Compare February 24, 2022 19:17
@turadg turadg added the automerge:no-update (expert!) Automatically merge without updates label Feb 24, 2022
@turadg turadg force-pushed the ta/4539-liquidated-vs-closed branch from 918964f to 59ee9b0 Compare February 24, 2022 23:13
@mergify mergify bot merged commit f8486f4 into master Feb 24, 2022
@mergify mergify bot deleted the ta/4539-liquidated-vs-closed branch February 24, 2022 23:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:no-update (expert!) Automatically merge without updates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability for vault observers to know liquidated vs closed
3 participants