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,12 +69,15 @@ 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 ) ,
@@ -81,56 +86,106 @@ export const prepareAdvancer = (
81
86
* XXX For now, assume this is invoked when a new entry is
82
87
* observed via the EventFeed.
83
88
*
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
+ *
84
103
* @param {CctpTxEvidence } evidence
85
104
*/
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
+ }
97
116
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 ) ;
100
119
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
+ ) ;
106
135
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
+ }
108
149
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
+ } ) ;
115
156
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
+ }
120
168
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
+ ) ;
123
173
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
+ }
128
182
} ,
129
183
} ,
130
184
{
131
185
stateShape : harden ( {
132
186
localDenom : M . string ( ) ,
133
187
poolAccount : M . remotable ( ) ,
188
+ usdcBrand : BrandShape ,
134
189
} ) ,
135
190
} ,
136
191
) ;
0 commit comments