1
+ import { AmountMath , BrandShape } from '@agoric/ertp' ;
1
2
import { assertAllDefined } from '@agoric/internal' ;
2
3
import { ChainAddressShape } from '@agoric/orchestration' ;
3
4
import { VowShape } from '@agoric/vow' ;
@@ -28,7 +29,7 @@ import { addressTools } from '../utils/address.js';
28
29
*/
29
30
export const prepareAdvancer = (
30
31
zone ,
31
- { chainHub, feed, log, statusManager, vowTools : { watch } } ,
32
+ { chainHub, feed, log, statusManager, vowTools : { watch, when } } ,
32
33
) => {
33
34
assertAllDefined ( { feed, statusManager, watch } ) ;
34
35
@@ -51,6 +52,7 @@ export const prepareAdvancer = (
51
52
* @param {{ destination: ChainAddress; amount: bigint; } } ctx
52
53
*/
53
54
onFulfilled ( result , { destination, amount } ) {
55
+ // TODO vstorage update?
54
56
log (
55
57
'Advance transfer fulfilled' ,
56
58
q ( { amount, destination, result } ) . toString ( ) ,
@@ -67,57 +69,112 @@ export const prepareAdvancer = (
67
69
return zone . exoClass (
68
70
'Fast USDC Advancer' ,
69
71
M . interface ( 'AdvancerI' , {
70
- handleTransactionEvent : M . call ( CctpTxEvidenceShape ) . returns ( VowShape ) ,
72
+ handleTransactionEvent : M . callWhen ( CctpTxEvidenceShape ) . returns (
73
+ M . or ( M . undefined ( ) , VowShape ) ,
74
+ ) ,
71
75
} ) ,
72
76
/**
73
77
* @param {{
74
78
* localDenom: Denom;
75
79
* poolAccount: HostInterface<OrchestrationAccount<{ chainId: 'agoric' }>>;
80
+ * usdcBrand: Brand<'nat'>;
76
81
* }} config
77
82
*/
78
83
config => harden ( config ) ,
79
84
{
80
- /** @param {CctpTxEvidence } evidence */
81
- handleTransactionEvent ( evidence ) {
82
- // TODO EventFeed will perform input validation checks.
83
- const { recipientAddress } = evidence . aux ;
84
- const { EUD } = addressTools . getQueryParams ( recipientAddress ) . params ;
85
- if ( ! EUD ) {
86
- statusManager . observe ( evidence ) ;
87
- throw makeError (
88
- `recipientAddress does not contain EUD param: ${ q ( recipientAddress ) } ` ,
89
- ) ;
90
- }
85
+ /**
86
+ * Returns a Promise for a Vow , since we we `await vt.when()`the
87
+ * `poolAccount.getBalance()` call. `getBalance()` interfaces with
88
+ * `BankManager` and `ChainHub`, so we can expect the calls to resolve
89
+ * promptly. We also don't care how many time `.getBalance` is called,
90
+ * and watched vows are not dependent on its result.
91
+ *
92
+ * This also might return `undefined` (unwrapped) if precondition checks
93
+ * fail.
94
+ *
95
+ * We do not expect any callers to depend on the settlement of
96
+ * `handleTransactionEvent` - errors caught are communicated to the
97
+ * `StatusManager` - so we don't need to concern ourselves with
98
+ * preserving the vow chain for callers.
99
+ *
100
+ * @param {CctpTxEvidence } evidence
101
+ */
102
+ async handleTransactionEvent ( evidence ) {
103
+ await null ;
104
+ try {
105
+ const { recipientAddress } = evidence . aux ;
106
+ const { EUD } = addressTools . getQueryParams ( recipientAddress ) . params ;
107
+ if ( ! EUD ) {
108
+ throw makeError (
109
+ `recipientAddress does not contain EUD param: ${ q ( recipientAddress ) } ` ,
110
+ ) ;
111
+ }
91
112
92
- // TODO #10391 this can throw, and should make a status update in the catch
93
- const destination = chainHub . makeChainAddress ( EUD ) ;
113
+ // this will throw if the bech32 prefix is not found, but is handled by the catch
114
+ const destination = chainHub . makeChainAddress ( EUD ) ;
94
115
95
- /** @type {DenomAmount } */
96
- const requestedAmount = harden ( {
97
- denom : this . state . localDenom ,
98
- value : BigInt ( evidence . tx . amount ) ,
99
- } ) ;
116
+ /** @type {DenomAmount } */
117
+ const requestedAmount = harden ( {
118
+ denom : this . state . localDenom ,
119
+ value : BigInt ( evidence . tx . amount ) ,
120
+ } ) ;
121
+ /**
122
+ * Ensure there's enough funds in poolAccount.
123
+ *
124
+ * It's safe to await here since we don't care how many
125
+ * times we call `getBalance`. Our later Vow call - `transferV` and
126
+ * its ctx - are also not reliant on the consistency of this value.
127
+ */
128
+ const poolBalance = await when (
129
+ E ( this . state . poolAccount ) . getBalance ( this . state . localDenom ) ,
130
+ ) ;
100
131
101
- // TODO #10391 ensure there's enough funds in poolAccount
132
+ if (
133
+ ! AmountMath . isGTE (
134
+ AmountMath . make ( this . state . usdcBrand , poolBalance . value ) ,
135
+ AmountMath . make ( this . state . usdcBrand , requestedAmount . value ) ,
136
+ )
137
+ ) {
138
+ log (
139
+ `Insufficient pool funds` ,
140
+ `Requested ${ q ( requestedAmount ) } but only have ${ q ( poolBalance ) } ` ,
141
+ ) ;
142
+ statusManager . observe ( evidence ) ;
143
+ return ;
144
+ }
102
145
103
- const transferV = E ( this . state . poolAccount ) . transfer (
104
- destination ,
105
- requestedAmount ,
106
- ) ;
146
+ try {
147
+ // mark as Advanced since `transferV` initiates the advance
148
+ // will throw if we've already .skipped or .advanced this evidence
149
+ statusManager . advance ( evidence ) ;
150
+ } catch ( e ) {
151
+ // only anticipated error is `assertNotSeen`, so
152
+ // intercept the catch so we don't call .skip which
153
+ // also performs this check
154
+ log ( 'Advancer error:' , q ( e ) . toString ( ) ) ;
155
+ return ;
156
+ }
107
157
108
- // mark as Advanced since `transferV` initiates the advance
109
- statusManager . advance ( evidence ) ;
158
+ const transferV = E ( this . state . poolAccount ) . transfer (
159
+ destination ,
160
+ requestedAmount ,
161
+ ) ;
110
162
111
- return watch ( transferV , transferHandler , {
112
- destination,
113
- amount : requestedAmount . value ,
114
- } ) ;
163
+ return watch ( transferV , transferHandler , {
164
+ destination,
165
+ amount : requestedAmount . value ,
166
+ } ) ;
167
+ } catch ( e ) {
168
+ log ( `Advancer error:` , q ( e ) . toString ( ) ) ;
169
+ statusManager . observe ( evidence ) ;
170
+ }
115
171
} ,
116
172
} ,
117
173
{
118
174
stateShape : harden ( {
119
175
localDenom : M . string ( ) ,
120
176
poolAccount : M . remotable ( ) ,
177
+ usdcBrand : BrandShape ,
121
178
} ) ,
122
179
} ,
123
180
) ;
0 commit comments