Skip to content

Commit 725edd7

Browse files
committed
feat: Advancer exo behaviors
- refs: #10390
1 parent 88f51f3 commit 725edd7

File tree

4 files changed

+373
-146
lines changed

4 files changed

+373
-146
lines changed

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

+92-37
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { AmountMath, BrandShape } from '@agoric/ertp';
12
import { assertAllDefined } from '@agoric/internal';
23
import { ChainAddressShape } from '@agoric/orchestration';
34
import { VowShape } from '@agoric/vow';
@@ -28,7 +29,7 @@ import { addressTools } from '../utils/address.js';
2829
*/
2930
export const prepareAdvancer = (
3031
zone,
31-
{ chainHub, feed, log, statusManager, vowTools: { watch } },
32+
{ chainHub, feed, log, statusManager, vowTools: { watch, when } },
3233
) => {
3334
assertAllDefined({ feed, statusManager, watch });
3435

@@ -51,6 +52,7 @@ export const prepareAdvancer = (
5152
* @param {{ destination: ChainAddress; amount: bigint; }} ctx
5253
*/
5354
onFulfilled(result, { destination, amount }) {
55+
// TODO vstorage update?
5456
log(
5557
'Advance transfer fulfilled',
5658
q({ amount, destination, result }).toString(),
@@ -67,12 +69,15 @@ export const prepareAdvancer = (
6769
return zone.exoClass(
6870
'Fast USDC Advancer',
6971
M.interface('AdvancerI', {
70-
handleTransactionEvent: M.call(CctpTxEvidenceShape).returns(VowShape),
72+
handleTransactionEvent: M.callWhen(CctpTxEvidenceShape).returns(
73+
M.or(M.undefined(), VowShape),
74+
),
7175
}),
7276
/**
7377
* @param {{
7478
* localDenom: Denom;
7579
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
80+
* usdcBrand: Brand<'nat'>;
7681
* }} config
7782
*/
7883
config => harden(config),
@@ -81,56 +86,106 @@ export const prepareAdvancer = (
8186
* XXX For now, assume this is invoked when a new entry is
8287
* observed via the EventFeed.
8388
*
89+
* Returns a Promise for a Vow , since we we `await vt.when()`the
90+
* `poolAccount.getBalance()` call. `getBalance()` interfaces with
91+
* `BankManager` and `ChainHub`, so we can expect the calls to resolve
92+
* promptly. We also don't care how many time `.getBalance` is called,
93+
* and watched vows are not dependent on its result.
94+
*
95+
* This also might return `undefined` (unwrapped) if precondition checks
96+
* fail.
97+
*
98+
* We don't expect any callers to depend on the settlement of
99+
* `handleTransactionEvent` - errors caught are communicated to the
100+
* `StatusManager` - so we don't need to concern ourselves with
101+
* preserving the vow chain for callers.
102+
*
84103
* @param {CctpTxEvidence} evidence
85104
*/
86-
handleTransactionEvent(evidence) {
87-
// XXX assume EventFeed performs validation checks. Advancer will assert uniqueness.
88-
89-
const { recipientAddress } = evidence.aux;
90-
const { EUD } = addressTools.getQueryParams(recipientAddress).params;
91-
if (!EUD) {
92-
statusManager.observe(evidence);
93-
throw makeError(
94-
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
95-
);
96-
}
105+
async handleTransactionEvent(evidence) {
106+
await null;
107+
try {
108+
const { recipientAddress } = evidence.aux;
109+
const { EUD } = addressTools.getQueryParams(recipientAddress).params;
110+
// XXX assume EventFeed performs validation checks. Advancer will assert uniqueness.
111+
if (!EUD) {
112+
throw makeError(
113+
`recipientAddress does not contain EUD param: ${q(recipientAddress)}`,
114+
);
115+
}
97116

98-
// TODO #10391 this can throw, and should make a status update in the catch
99-
const { chainId } = chainHub.getChainInfoByAddress(EUD);
117+
// this will throw if the bech32 prefix is not found
118+
const { chainId } = chainHub.getChainInfoByAddress(EUD);
100119

101-
/** @type {DenomAmount} */
102-
const requestedAmount = harden({
103-
denom: this.state.localDenom,
104-
value: BigInt(evidence.tx.amount),
105-
});
120+
/** @type {DenomAmount} */
121+
const requestedAmount = harden({
122+
denom: this.state.localDenom,
123+
value: BigInt(evidence.tx.amount),
124+
});
125+
/**
126+
* Ensure there's enough funds in poolAccount.
127+
*
128+
* It's safe to await here since we don't care how many
129+
* times we call `getBalance`. Our later Vow call - `transferV` and
130+
* its ctx - are also not reliant on the consistency of this value.
131+
*/
132+
const poolBalance = await when(
133+
E(this.state.poolAccount).getBalance(this.state.localDenom),
134+
);
106135

107-
// TODO #10391 ensure there's enough funds in poolAccount
136+
if (
137+
!AmountMath.isGTE(
138+
AmountMath.make(this.state.usdcBrand, poolBalance.value),
139+
AmountMath.make(this.state.usdcBrand, requestedAmount.value),
140+
)
141+
) {
142+
log(
143+
`Insufficient pool funds`,
144+
`Requested ${q(requestedAmount)} but only have ${q(poolBalance)}`,
145+
);
146+
statusManager.observe(evidence);
147+
return;
148+
}
108149

109-
/** @type {ChainAddress} */
110-
const destination = harden({
111-
chainId,
112-
value: EUD,
113-
encoding: /** @type {const} */ ('bech32'),
114-
});
150+
/** @type {ChainAddress} */
151+
const destination = harden({
152+
chainId,
153+
value: EUD,
154+
encoding: /** @type {const} */ ('bech32'),
155+
});
115156

116-
const transferV = E(this.state.poolAccount).transfer(
117-
destination,
118-
requestedAmount,
119-
);
157+
// mark as Advanced since we're initiating the advance
158+
try {
159+
// will throw if we've already .skipped or .advanced this evidence
160+
statusManager.advance(evidence);
161+
} catch (e) {
162+
// only anticipated error is `assertNotSeen`, so
163+
// intercept the catch so we don't call .skip which
164+
// also performs this check
165+
log('Advancer error:', q(e).toString());
166+
return;
167+
}
120168

121-
// mark as Advanced since we're initiating the advance
122-
statusManager.advance(evidence);
169+
const transferV = E(this.state.poolAccount).transfer(
170+
destination,
171+
requestedAmount,
172+
);
123173

124-
return watch(transferV, transferHandler, {
125-
destination,
126-
amount: requestedAmount.value,
127-
});
174+
return watch(transferV, transferHandler, {
175+
destination,
176+
amount: requestedAmount.value,
177+
});
178+
} catch (e) {
179+
log(`Advancer error:`, q(e).toString());
180+
statusManager.observe(evidence);
181+
}
128182
},
129183
},
130184
{
131185
stateShape: harden({
132186
localDenom: M.string(),
133187
poolAccount: M.remotable(),
188+
usdcBrand: BrandShape,
134189
}),
135190
},
136191
);

0 commit comments

Comments
 (0)