Skip to content

Commit 1321e4f

Browse files
authored
feat(portfolio-contract): include FlowDetail (esp. type) in StatusFor['flow'] (#12178)
refs: - https://github.com/Agoric/agoric-private/issues/507 ## Description once a flow is done or failed, there was no way to see in vstorage whether it was a ‘withdraw’, ‘deposit’, or ‘rebalance’ (without tracing history) ### Scaling Considerations a small increase in vstorage IAVL usage ### Documentation / Testing Considerations types, snapshots as usual ### Upgrade / Security Considerations no exo state changes. `ymaxControl.upgrade(...)` compatible. Only optional fields are added to shapes. This adds a gap when used as `TypedPattern<T>`: runtime validation might pass without satisfying the static type.
2 parents 5c48184 + d8c9fad commit 1321e4f

File tree

13 files changed

+214
-11
lines changed

13 files changed

+214
-11
lines changed

packages/portfolio-api/src/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export type StatusFor = {
144144
totalIn: NatAmount;
145145
totalOut: NatAmount;
146146
};
147-
flow: FlowStatus;
147+
flow: FlowStatus & FlowDetail;
148148
flowSteps: FlowStep[];
149149
flowOrder: FundsFlowPlan['order'];
150150
};

packages/portfolio-api/test/types.test-d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ const status: StatusFor = {
147147
totalIn: natAmount,
148148
totalOut: natAmount,
149149
},
150-
flow: { state: 'done' },
150+
flow: { state: 'done', type: 'rebalance' },
151151
flowSteps,
152152
flowOrder: [
153153
[4, [2, 3]],

packages/portfolio-contract/src/portfolio.flows.ts

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ const trackFlow = async (
183183
traceFlow: TraceLogger,
184184
accounts: AccountsByChain,
185185
order: Job['order'],
186+
detail: FlowDetail,
186187
) => {
187188
const runTask = async (ix: number, running: number[]) => {
188189
// steps are 1-based. the scheduler is 0-based
@@ -195,6 +196,7 @@ const trackFlow = async (
195196
steps,
196197
step: min(...steps),
197198
how: moves[min(...running)].how,
199+
...detail,
198200
});
199201
await null;
200202
try {
@@ -235,11 +237,15 @@ const trackFlow = async (
235237
return errs;
236238
}, undefined);
237239
assert(reasons); // guaranteed by results.some(...) above
238-
reporter.publishFlowStatus(flowId, { state: 'fail', ...reasons });
240+
reporter.publishFlowStatus(flowId, {
241+
state: 'fail',
242+
...reasons,
243+
...detail,
244+
});
239245
throw results;
240246
}
241247

242-
reporter.publishFlowStatus(flowId, { state: 'done' });
248+
reporter.publishFlowStatus(flowId, { state: 'done', ...detail });
243249
};
244250

245251
export const provideCosmosAccount = async <C extends 'agoric' | 'noble'>(
@@ -437,6 +443,7 @@ const stepFlow = async (
437443
kit: GuestInterface<PortfolioKit>,
438444
traceP: TraceLogger,
439445
flowId: number,
446+
flowDetail: FlowDetail,
440447
) => {
441448
const todo: AssetMovement[] = [];
442449

@@ -711,6 +718,7 @@ const stepFlow = async (
711718
state: 'run',
712719
step: 0,
713720
how: `makeAccounts(${acctsToDo.join(', ')})`,
721+
...flowDetail,
714722
});
715723
reporter.publishFlowSteps(
716724
flowId,
@@ -736,6 +744,7 @@ const stepFlow = async (
736744
step: 0,
737745
how: `makeAccount: ${chain}`,
738746
error: err && typeof err === 'object' ? err.message : `${err}`,
747+
...flowDetail,
739748
});
740749
throw err;
741750
}
@@ -787,7 +796,15 @@ const stepFlow = async (
787796
};
788797
traceFlow('accounts for trackFlow', keys(accounts));
789798

790-
await trackFlow(reporter, todo, flowId, traceFlow, accounts, order);
799+
await trackFlow(
800+
reporter,
801+
todo,
802+
flowId,
803+
traceFlow,
804+
accounts,
805+
order,
806+
flowDetail,
807+
);
791808
traceFlow('stepFlow done');
792809
};
793810

@@ -840,7 +857,9 @@ export const rebalance = (async (
840857
if (flow) {
841858
({ flowId } =
842859
startedFlow ?? kit.manager.startFlow({ type: 'rebalance' }, flow));
843-
await stepFlow(orch, ctx, seat, flow, kit, traceP, flowId);
860+
await stepFlow(orch, ctx, seat, flow, kit, traceP, flowId, {
861+
type: 'rebalance',
862+
});
844863
}
845864

846865
if (!seat.hasExited()) {
@@ -1153,7 +1172,7 @@ export const executePlan = (async (
11531172
MovementDesc[] | FundsFlowPlan
11541173
>); // XXX Guest/Host types UNTIL #9822
11551174
try {
1156-
await stepFlow(orch, ctx, seat, plan, pKit, traceP, flowId);
1175+
await stepFlow(orch, ctx, seat, plan, pKit, traceP, flowId, flowDetail);
11571176
return `flow${flowId}`;
11581177
} finally {
11591178
// The seat must be exited no matter what to avoid leaks

packages/portfolio-contract/src/type-guards.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,16 +307,25 @@ export const makeFlowStepsPath = (
307307
prop: 'steps' | 'order' = 'steps',
308308
) => [`portfolio${parent}`, 'flows', `flow${id}`, prop];
309309

310+
const FlowDetailsProps = {
311+
type: M.string(),
312+
amount: AnyNatAmountShape,
313+
};
314+
310315
export const FlowStatusShape: TypedPattern<StatusFor['flow']> = M.or(
311316
M.splitRecord(
312317
{ state: 'run', step: M.number(), how: M.string() },
313-
{ steps: M.arrayOf(M.number()) },
318+
{ steps: M.arrayOf(M.number()), ...FlowDetailsProps },
314319
),
315320
{ state: 'undo', step: M.number(), how: M.string() }, // XXX Not currently used
316-
{ state: 'done' },
321+
M.splitRecord({ state: 'done' }, FlowDetailsProps),
317322
M.splitRecord(
318323
{ state: 'fail', step: M.number(), how: M.string(), error: M.string() },
319-
{ next: M.record(), where: AnyString<AssetPlaceRef>() },
324+
{
325+
next: M.record(), // XXX recursive pattern
326+
where: AnyString<AssetPlaceRef>(), // XXX obsolete
327+
...FlowDetailsProps,
328+
},
320329
{},
321330
),
322331
);

packages/portfolio-contract/test/offer-shapes.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,27 +150,36 @@ test('numeric position references are rejected', t => {
150150
});
151151

152152
test('vstorage flow type matches shape', t => {
153+
const amount = usdc(200n);
153154
const passCases: Record<string, StatusFor['flow']> = harden({
154155
runningFlow: {
155156
state: 'run',
156157
step: 1,
157158
how: 'deposit',
159+
type: 'deposit',
160+
amount,
158161
},
159162
completedFlow: {
160163
state: 'done',
164+
type: 'deposit',
165+
amount,
161166
},
162167
failedFlow: {
163168
state: 'fail',
164169
step: 0,
165170
how: 'transfer',
166171
error: 'Insufficient funds',
172+
type: 'deposit',
173+
amount,
167174
},
168175
failedFlowWithLocation: {
169176
state: 'fail',
170177
step: 1,
171178
how: 'deposit',
172179
error: 'Network timeout',
173180
where: '@Arbitrum',
181+
type: 'deposit',
182+
amount,
174183
},
175184
});
176185

packages/portfolio-contract/test/portfolio.exo.test.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ test('portfolio exo caches storage nodes', async t => {
7979
t.is(nodeQty, 2, 'root + portfolio');
8080

8181
manager.startFlow({ type: 'rebalance' });
82-
const flowStatus: StatusFor['flow'] = { state: 'run', step: 1, how: 'USDN' };
82+
const flowStatus: StatusFor['flow'] = {
83+
state: 'run',
84+
step: 1,
85+
how: 'USDN',
86+
type: 'rebalance',
87+
};
8388
reporter.publishFlowStatus(1, flowStatus);
8489
reporter.publishFlowStatus(1, { ...flowStatus, step: 2 });
8590
await eventLoopIteration();

packages/portfolio-contract/test/portfolio.flows.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1246,6 +1246,7 @@ test('handle failure in provideEVMAccount sendMakeAccountCall', async t => {
12461246
const fs = await getFlowStatus(1, 1);
12471247
t.log(fs);
12481248
t.deepEqual(fs, {
1249+
type: 'rebalance',
12491250
state: 'fail',
12501251
step: 0,
12511252
how: 'makeAccount: Arbitrum',

0 commit comments

Comments
 (0)