Skip to content

Commit 8c79835

Browse files
authored
feat: advancer integrates with LP and contract (#10518)
closes: #10390 ## Description - Updates `Advancer` to use `LiquidityPool` borrower facet - _previous iteration used payments instead of seats_ - Integrates `Advancer` with `contract.js` - creates a `PoolAccount` using a flow to provide to `makeAdvancer` ### Security Considerations None ### Scaling Considerations None ### Documentation Considerations None ### Testing Considerations Includes exo tests for `Advancer`. Also includes a contract test covering the happy path, although out of scope for the ticket. ### Upgrade Considerations None, unreleased
2 parents 7e54272 + 7ec6fcf commit 8c79835

11 files changed

+354
-128
lines changed

packages/fast-usdc/src/exos/advancer.js

+68-53
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { AmountMath, AmountShape, PaymentShape } from '@agoric/ertp';
2-
import { assertAllDefined } from '@agoric/internal';
1+
import { AmountMath, AmountShape } from '@agoric/ertp';
2+
import { assertAllDefined, makeTracer } from '@agoric/internal';
33
import { ChainAddressShape } from '@agoric/orchestration';
44
import { pickFacet } from '@agoric/vat-data';
55
import { VowShape } from '@agoric/vow';
@@ -15,31 +15,25 @@ const { isGTE } = AmountMath;
1515
/**
1616
* @import {HostInterface} from '@agoric/async-flow';
1717
* @import {NatAmount} from '@agoric/ertp';
18-
* @import {ChainAddress, ChainHub, Denom, DenomAmount, OrchestrationAccount} from '@agoric/orchestration';
18+
* @import {ChainAddress, ChainHub, Denom, OrchestrationAccount} from '@agoric/orchestration';
19+
* @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
1920
* @import {VowTools} from '@agoric/vow';
2021
* @import {Zone} from '@agoric/zone';
2122
* @import {CctpTxEvidence, FeeConfig, LogFn} from '../types.js';
2223
* @import {StatusManager} from './status-manager.js';
23-
*/
24-
25-
/**
26-
* Expected interface from LiquidityPool
27-
*
28-
* @typedef {{
29-
* lookupBalance(): NatAmount;
30-
* borrow(amount: Amount<"nat">): Promise<Payment<"nat">>;
31-
* repay(payments: PaymentKeywordRecord): Promise<void>
32-
* }} AssetManagerFacet
24+
* @import {LiquidityPoolKit} from './liquidity-pool.js';
3325
*/
3426

3527
/**
3628
* @typedef {{
3729
* chainHub: ChainHub;
3830
* feeConfig: FeeConfig;
39-
* log: LogFn;
31+
* localTransfer: ZoeTools['localTransfer'];
32+
* log?: LogFn;
4033
* statusManager: StatusManager;
4134
* usdc: { brand: Brand<'nat'>; denom: Denom; };
4235
* vowTools: VowTools;
36+
* zcf: ZCF;
4337
* }} AdvancerKitPowers
4438
*/
4539

@@ -49,13 +43,15 @@ const AdvancerKitI = harden({
4943
handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(),
5044
}),
5145
depositHandler: M.interface('DepositHandlerI', {
52-
onFulfilled: M.call(AmountShape, {
46+
onFulfilled: M.call(M.undefined(), {
47+
amount: AmountShape,
5348
destination: ChainAddressShape,
54-
payment: PaymentShape,
49+
tmpSeat: M.remotable(),
5550
}).returns(VowShape),
5651
onRejected: M.call(M.error(), {
52+
amount: AmountShape,
5753
destination: ChainAddressShape,
58-
payment: PaymentShape,
54+
tmpSeat: M.remotable(),
5955
}).returns(),
6056
}),
6157
transferHandler: M.interface('TransferHandlerI', {
@@ -77,7 +73,16 @@ const AdvancerKitI = harden({
7773
*/
7874
export const prepareAdvancerKit = (
7975
zone,
80-
{ chainHub, feeConfig, log, statusManager, usdc, vowTools: { watch, when } },
76+
{
77+
chainHub,
78+
feeConfig,
79+
localTransfer,
80+
log = makeTracer('Advancer', true),
81+
statusManager,
82+
usdc,
83+
vowTools: { watch, when },
84+
zcf,
85+
} = /** @type {AdvancerKitPowers} */ ({}),
8186
) => {
8287
assertAllDefined({
8388
chainHub,
@@ -95,8 +100,8 @@ export const prepareAdvancerKit = (
95100
AdvancerKitI,
96101
/**
97102
* @param {{
98-
* assetManagerFacet: AssetManagerFacet;
99-
* poolAccount: ERef<HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>>;
103+
* borrowerFacet: LiquidityPoolKit['borrower'];
104+
* poolAccount: HostInterface<OrchestrationAccount<{chainId: 'agoric'}>>;
100105
* }} config
101106
*/
102107
config => harden(config),
@@ -115,8 +120,7 @@ export const prepareAdvancerKit = (
115120
async handleTransactionEvent(evidence) {
116121
await null;
117122
try {
118-
// TODO poolAccount might be a vow we need to unwrap
119-
const { assetManagerFacet, poolAccount } = this.state;
123+
const { borrowerFacet, poolAccount } = this.state;
120124
const { recipientAddress } = evidence.aux;
121125
const { EUD } = addressTools.getQueryParams(
122126
recipientAddress,
@@ -129,14 +133,12 @@ export const prepareAdvancerKit = (
129133
const advanceAmount = feeTools.calculateAdvance(requestedAmount);
130134

131135
// TODO: consider skipping and using `borrow()`s internal balance check
132-
const poolBalance = assetManagerFacet.lookupBalance();
136+
const poolBalance = borrowerFacet.getBalance();
133137
if (!isGTE(poolBalance, requestedAmount)) {
134138
log(
135139
`Insufficient pool funds`,
136140
`Requested ${q(advanceAmount)} but only have ${q(poolBalance)}`,
137141
);
138-
// report `requestedAmount`, not `advancedAmount`... do we need to
139-
// communicate net to `StatusManger` in case fees change in between?
140142
statusManager.observe(evidence);
141143
return;
142144
}
@@ -152,19 +154,29 @@ export const prepareAdvancerKit = (
152154
return;
153155
}
154156

157+
const { zcfSeat: tmpSeat } = zcf.makeEmptySeatKit();
158+
const amountKWR = harden({ USDC: advanceAmount });
155159
try {
156-
const payment = await assetManagerFacet.borrow(advanceAmount);
157-
const depositV = E(poolAccount).deposit(payment);
158-
void watch(depositV, this.facets.depositHandler, {
159-
destination,
160-
payment,
161-
});
160+
borrowerFacet.borrow(tmpSeat, amountKWR);
162161
} catch (e) {
163-
// `.borrow()` might fail if the balance changes since we
164-
// requested it. TODO - how to handle this? change ADVANCED -> OBSERVED?
165-
// Note: `depositHandler` handles the `.deposit()` failure
162+
// We do not expect this to fail since there are no turn boundaries
163+
// between .getBalance() and .borrow().
164+
// We catch to report outside of the normal error flow since this is
165+
// not expected.
166166
log('🚨 advance borrow failed', q(e).toString());
167167
}
168+
169+
const depositV = localTransfer(
170+
tmpSeat,
171+
// @ts-expect-error LocalAccountMethods vs OrchestrationAccount
172+
poolAccount,
173+
amountKWR,
174+
);
175+
void watch(depositV, this.facets.depositHandler, {
176+
amount: advanceAmount,
177+
destination,
178+
tmpSeat,
179+
});
168180
} catch (e) {
169181
log('Advancer error:', q(e).toString());
170182
statusManager.observe(evidence);
@@ -173,31 +185,33 @@ export const prepareAdvancerKit = (
173185
},
174186
depositHandler: {
175187
/**
176-
* @param {NatAmount} amount amount returned from deposit
177-
* @param {{ destination: ChainAddress; payment: Payment<'nat'> }} ctx
188+
* @param {undefined} result
189+
* @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
178190
*/
179-
onFulfilled(amount, { destination }) {
191+
onFulfilled(result, { amount, destination }) {
180192
const { poolAccount } = this.state;
181-
const transferV = E(poolAccount).transfer(
182-
destination,
183-
/** @type {DenomAmount} */ ({
184-
denom: usdc.denom,
185-
value: amount.value,
186-
}),
187-
);
193+
const transferV = E(poolAccount).transfer(destination, {
194+
denom: usdc.denom,
195+
value: amount.value,
196+
});
188197
return watch(transferV, this.facets.transferHandler, {
189198
destination,
190199
amount,
191200
});
192201
},
193202
/**
194203
* @param {Error} error
195-
* @param {{ destination: ChainAddress; payment: Payment<'nat'> }} ctx
204+
* @param {{ amount: Amount<'nat'>; destination: ChainAddress; tmpSeat: ZCFSeat }} ctx
196205
*/
197-
onRejected(error, { payment }) {
198-
// TODO return live payment from ctx to LP
206+
onRejected(error, { tmpSeat }) {
207+
// TODO return seat allocation from ctx to LP?
199208
log('🚨 advance deposit failed', q(error).toString());
200-
log('TODO live payment to return to LP', q(payment).toString());
209+
// TODO #10510 (comprehensive error testing) determine
210+
// course of action here
211+
log(
212+
'TODO live payment on seat to return to LP',
213+
q(tmpSeat).toString(),
214+
);
201215
},
202216
},
203217
transferHandler: {
@@ -206,23 +220,24 @@ export const prepareAdvancerKit = (
206220
* @param {{ destination: ChainAddress; amount: NatAmount; }} ctx
207221
*/
208222
onFulfilled(result, { destination, amount }) {
209-
// TODO vstorage update?
223+
// TODO vstorage update? We don't currently have a status for
224+
// Advanced + transferV settled
210225
log(
211226
'Advance transfer fulfilled',
212227
q({ amount, destination, result }).toString(),
213228
);
214229
},
215230
onRejected(error) {
216-
// XXX retry logic?
217-
// What do we do if we fail, should we keep a Status?
231+
// TODO #10510 (comprehensive error testing) determine
232+
// course of action here. This might fail due to timeout.
218233
log('Advance transfer rejected', q(error).toString());
219234
},
220235
},
221236
},
222237
{
223238
stateShape: harden({
224-
assetManagerFacet: M.remotable(),
225-
poolAccount: M.or(VowShape, M.remotable()),
239+
borrowerFacet: M.remotable(),
240+
poolAccount: M.remotable(),
226241
}),
227242
},
228243
);

packages/fast-usdc/src/fast-usdc.contract.js

+44-20
Original file line numberDiff line numberDiff line change
@@ -11,22 +11,27 @@ import {
1111
} from '@agoric/orchestration';
1212
import { provideSingleton } from '@agoric/zoe/src/contractSupport/durability.js';
1313
import { prepareRecorderKitMakers } from '@agoric/zoe/src/contractSupport/recorder.js';
14+
import { makeZoeTools } from '@agoric/orchestration/src/utils/zoe-tools.js';
15+
import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
1416
import { E } from '@endo/far';
1517
import { M, objectMap } from '@endo/patterns';
16-
import { depositToSeat } from '@agoric/zoe/src/contractSupport/zoeHelpers.js';
1718
import { prepareAdvancer } from './exos/advancer.js';
1819
import { prepareLiquidityPoolKit } from './exos/liquidity-pool.js';
1920
import { prepareSettler } from './exos/settler.js';
2021
import { prepareStatusManager } from './exos/status-manager.js';
2122
import { prepareTransactionFeedKit } from './exos/transaction-feed.js';
2223
import { defineInertInvitation } from './utils/zoe.js';
2324
import { FastUSDCTermsShape, FeeConfigShape } from './type-guards.js';
25+
import * as flows from './fast-usdc.flows.js';
2426

2527
const trace = makeTracer('FastUsdc');
2628

2729
/**
2830
* @import {Denom} from '@agoric/orchestration';
31+
* @import {HostInterface} from '@agoric/async-flow';
32+
* @import {OrchestrationAccount} from '@agoric/orchestration';
2933
* @import {OrchestrationPowers, OrchestrationTools} from '@agoric/orchestration/src/utils/start-helper.js';
34+
* @import {Vow} from '@agoric/vow';
3035
* @import {Zone} from '@agoric/zone';
3136
* @import {OperatorKit} from './exos/operator-kit.js';
3237
* @import {CctpTxEvidence, FeeConfig} from './types.js';
@@ -73,35 +78,22 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
7378
);
7479
const statusManager = prepareStatusManager(zone);
7580
const makeSettler = prepareSettler(zone, { statusManager });
76-
const { chainHub, vowTools } = tools;
81+
const { chainHub, orchestrateAll, vowTools } = tools;
82+
const { localTransfer } = makeZoeTools(zcf, vowTools);
7783
const makeAdvancer = prepareAdvancer(zone, {
7884
chainHub,
7985
feeConfig,
80-
log: trace,
86+
localTransfer,
8187
usdc: harden({
8288
brand: terms.brands.USDC,
8389
denom: terms.usdcDenom,
8490
}),
8591
statusManager,
8692
vowTools,
93+
zcf,
8794
});
8895
const makeFeedKit = prepareTransactionFeedKit(zone, zcf);
8996
assertAllDefined({ makeFeedKit, makeAdvancer, makeSettler, statusManager });
90-
const feedKit = makeFeedKit();
91-
const advancer = makeAdvancer(
92-
// @ts-expect-error FIXME
93-
{},
94-
);
95-
// Connect evidence stream to advancer
96-
void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), {
97-
updateState(evidence) {
98-
try {
99-
void advancer.handleTransactionEvent(evidence);
100-
} catch (err) {
101-
trace('🚨 Error handling transaction event', err);
102-
}
103-
},
104-
});
10597
const makeLiquidityPoolKit = prepareLiquidityPoolKit(
10698
zone,
10799
zcf,
@@ -114,16 +106,19 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
114106
'test of forcing evidence',
115107
);
116108

109+
const { makeLocalAccount } = orchestrateAll(flows, {});
110+
117111
const creatorFacet = zone.exo('Fast USDC Creator', undefined, {
118112
/** @type {(operatorId: string) => Promise<Invitation<OperatorKit>>} */
119113
async makeOperatorInvitation(operatorId) {
114+
// eslint-disable-next-line no-use-before-define
120115
return feedKit.creator.makeOperatorInvitation(operatorId);
121116
},
122117
/**
123118
* @param {{ USDC: Amount<'nat'>}} amounts
124119
*/
125120
testBorrow(amounts) {
126-
console.log('🚧🚧 UNTIL: borrow is integrated 🚧🚧', amounts);
121+
console.log('🚧🚧 UNTIL: borrow is integrated (#10388) 🚧🚧', amounts);
127122
const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit();
128123
// eslint-disable-next-line no-use-before-define
129124
poolKit.borrower.borrow(tmpAssetManagerSeat, amounts);
@@ -136,7 +131,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
136131
* @returns {Promise<AmountKeywordRecord>}
137132
*/
138133
async testRepay(amounts, payments) {
139-
console.log('🚧🚧 UNTIL: repay is integrated 🚧🚧', amounts);
134+
console.log('🚧🚧 UNTIL: repay is integrated (#10388) 🚧🚧', amounts);
140135
const { zcfSeat: tmpAssetManagerSeat } = zcf.makeEmptySeatKit();
141136
await depositToSeat(
142137
zcf,
@@ -164,6 +159,7 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
164159
* @param {CctpTxEvidence} evidence
165160
*/
166161
makeTestPushInvitation(evidence) {
162+
// eslint-disable-next-line no-use-before-define
167163
void advancer.handleTransactionEvent(evidence);
168164
return makeTestInvitation();
169165
},
@@ -207,6 +203,34 @@ export const contract = async (zcf, privateArgs, zone, tools) => {
207203
makeLiquidityPoolKit(shareMint, privateArgs.storageNode),
208204
);
209205

206+
const feedKit = zone.makeOnce('Feed Kit', () => makeFeedKit());
207+
208+
const poolAccountV =
209+
// cast to HostInterface
210+
/** @type { Vow<HostInterface<OrchestrationAccount<{chainId: 'agoric';}>>>} */ (
211+
/** @type {unknown}*/ (
212+
zone.makeOnce('Pool Local Orch Account', () => makeLocalAccount())
213+
)
214+
);
215+
const poolAccount = await vowTools.when(poolAccountV);
216+
217+
const advancer = zone.makeOnce('Advancer', () =>
218+
makeAdvancer({
219+
borrowerFacet: poolKit.borrower,
220+
poolAccount,
221+
}),
222+
);
223+
// Connect evidence stream to advancer
224+
void observeIteration(subscribeEach(feedKit.public.getEvidenceSubscriber()), {
225+
updateState(evidence) {
226+
try {
227+
void advancer.handleTransactionEvent(evidence);
228+
} catch (err) {
229+
trace('🚨 Error handling transaction event', err);
230+
}
231+
},
232+
});
233+
210234
return harden({ creatorFacet, publicFacet });
211235
};
212236
harden(contract);
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/**
2+
* @import {Orchestrator, OrchestrationFlow} from '@agoric/orchestration';
3+
*/
4+
5+
/**
6+
* @satisfies {OrchestrationFlow}
7+
* @param {Orchestrator} orch
8+
*/
9+
export const makeLocalAccount = async orch => {
10+
const agoricChain = await orch.getChain('agoric');
11+
return agoricChain.makeAccount();
12+
};
13+
harden(makeLocalAccount);

0 commit comments

Comments
 (0)