@@ -85,27 +85,33 @@ const createTestExtensions = (t, common: CommonSetup) => {
8585 } ) ;
8686
8787 const localTransferVK = vowTools . makeVowKit < void > ( ) ;
88- const resolveLocalTransferV = ( ) => {
89- // pretend funds move from tmpSeat to poolAccount
90- localTransferVK . resolver . resolve ( ) ;
91- } ;
92- const rejectLocalTransfeferV = ( ) => {
88+ // pretend funds move from tmpSeat to poolAccount
89+ const resolveLocalTransferV = ( ) => localTransferVK . resolver . resolve ( ) ;
90+ const rejectLocalTransfeferV = ( ) =>
9391 localTransferVK . resolver . reject (
9492 new Error ( 'One or more deposits failed: simulated error' ) ,
9593 ) ;
96- } ;
94+ const withdrawToSeatVK = vowTools . makeVowKit < void > ( ) ;
95+ const resolveWithdrawToSeatV = ( ) => withdrawToSeatVK . resolver . resolve ( ) ;
96+ const rejectWithdrawToSeatV = ( ) =>
97+ withdrawToSeatVK . resolver . reject (
98+ new Error ( 'One or more deposits failed: simulated error' ) ,
99+ ) ;
97100 const mockZoeTools = Far ( 'MockZoeTools' , {
98101 localTransfer ( ...args : Parameters < ZoeTools [ 'localTransfer' ] > ) {
99102 trace ( 'ZoeTools.localTransfer called with' , args ) ;
100103 return localTransferVK . vow ;
101104 } ,
105+ withdrawToSeat ( ...args : Parameters < ZoeTools [ 'withdrawToSeat' ] > ) {
106+ trace ( 'ZoeTools.withdrawToSeat called with' , args ) ;
107+ return withdrawToSeatVK . vow ;
108+ } ,
102109 } ) ;
103110
104111 const feeConfig = makeTestFeeConfig ( usdc ) ;
105112 const makeAdvancer = prepareAdvancer ( contractZone . subZone ( 'advancer' ) , {
106113 chainHub,
107114 feeConfig,
108- localTransfer : mockZoeTools . localTransfer ,
109115 log,
110116 statusManager,
111117 usdc : harden ( {
@@ -115,6 +121,7 @@ const createTestExtensions = (t, common: CommonSetup) => {
115121 vowTools,
116122 // @ts -expect-error mocked zcf
117123 zcf : mockZCF ,
124+ zoeTools : mockZoeTools ,
118125 } ) ;
119126
120127 type NotifyArgs = Parameters < SettlerKit [ 'notifier' ] [ 'notifyAdvancingResult' ] > ;
@@ -173,6 +180,8 @@ const createTestExtensions = (t, common: CommonSetup) => {
173180 mockNotifyF,
174181 resolveLocalTransferV,
175182 rejectLocalTransfeferV,
183+ resolveWithdrawToSeatV,
184+ rejectWithdrawToSeatV,
176185 } ,
177186 services : {
178187 advancer,
@@ -344,13 +353,13 @@ test('updates status to OBSERVED if makeChainAddress fails', async t => {
344353 ] ) ;
345354} ) ;
346355
347- test ( 'calls notifyAdvancingResult (AdvancedFailed) on failed transfer ' , async t => {
356+ test ( 'recovery behavior if Advance Fails (ADVANCE_FAILED) ' , async t => {
348357 const {
349358 bootstrap : { storage } ,
350359 extensions : {
351360 services : { advancer, feeTools } ,
352- helpers : { inspectLogs, inspectNotifyCalls } ,
353- mocks : { mockPoolAccount, resolveLocalTransferV } ,
361+ helpers : { inspectBorrowerFacetCalls , inspectLogs, inspectNotifyCalls } ,
362+ mocks : { mockPoolAccount, resolveLocalTransferV, resolveWithdrawToSeatV } ,
354363 } ,
355364 brands : { usdc } ,
356365 } = t . context ;
@@ -394,6 +403,132 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t
394403 false , // this indicates transfer failed
395404 ] ,
396405 ] ) ;
406+
407+ // simulate withdrawing `advanceAmount` from PoolAccount to tmpReturnSeat
408+ resolveWithdrawToSeatV ( ) ;
409+ await eventLoopIteration ( ) ;
410+ const { returnToPool } = inspectBorrowerFacetCalls ( ) ;
411+ t . is (
412+ returnToPool . length ,
413+ 1 ,
414+ 'returnToPool is called after ibc transfer fails' ,
415+ ) ;
416+ t . deepEqual (
417+ returnToPool [ 0 ] ,
418+ [
419+ Far ( 'MockZCFSeat' , { exit : theExit } ) ,
420+ usdc . make ( 293999999n ) , // 300000000n net of fees
421+ ] ,
422+ 'same amount borrowed is returned to LP' ,
423+ ) ;
424+ } ) ;
425+
426+ // unexpected, terminal state. test that log('🚨') is called
427+ test ( 'logs error if withdrawToSeat fails during AdvanceFailed recovery' , async t => {
428+ const {
429+ extensions : {
430+ services : { advancer } ,
431+ helpers : { inspectLogs, inspectNotifyCalls } ,
432+ mocks : { mockPoolAccount, resolveLocalTransferV, rejectWithdrawToSeatV } ,
433+ } ,
434+ brands : { usdc } ,
435+ } = t . context ;
436+
437+ const evidence = MockCctpTxEvidences . AGORIC_PLUS_OSMO ( ) ;
438+ void advancer . handleTransactionEvent ( { evidence, risk : { } } ) ;
439+
440+ // pretend borrow succeeded and funds were depositing to the LCA
441+ resolveLocalTransferV ( ) ;
442+ // pretend the IBC Transfer failed
443+ mockPoolAccount . transferVResolver . reject ( new Error ( 'transfer failed' ) ) ;
444+ // pretend withdrawToSeat failed
445+ rejectWithdrawToSeatV ( ) ;
446+ await eventLoopIteration ( ) ;
447+
448+ t . deepEqual ( inspectLogs ( ) , [
449+ [ 'decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men' ] ,
450+ [ 'Advance failed' , Error ( 'transfer failed' ) ] ,
451+ [
452+ '🚨 withdraw {"brand":"[Alleged: USDC brand]","value":"[146999999n]"} from "poolAccount" to return to pool failed' ,
453+ Error ( 'One or more deposits failed: simulated error' ) ,
454+ ] ,
455+ ] ) ;
456+
457+ // ensure Settler is notified of failed advance
458+ t . like ( inspectNotifyCalls ( ) , [
459+ [
460+ {
461+ txHash : evidence . txHash ,
462+ forwardingAddress : evidence . tx . forwardingAddress ,
463+ } ,
464+ false , // indicates transfer failed
465+ ] ,
466+ ] ) ;
467+ } ) ;
468+
469+ test ( 'logs error if returnToPool fails during AdvanceFailed recovery' , async t => {
470+ const {
471+ brands : { usdc } ,
472+ extensions : {
473+ services : { makeAdvancer } ,
474+ helpers : { inspectLogs, inspectNotifyCalls } ,
475+ mocks : {
476+ mockPoolAccount,
477+ mockNotifyF,
478+ resolveLocalTransferV,
479+ resolveWithdrawToSeatV,
480+ } ,
481+ } ,
482+ } = t . context ;
483+
484+ const mockBorrowerFacet = Far ( 'LiquidityPool Borrow Facet' , {
485+ borrow : ( seat : ZCFSeat , amount : NatAmount ) => {
486+ // note: will not be tracked by `inspectBorrowerFacetCalls`
487+ } ,
488+ returnToPool : ( seat : ZCFSeat , amount : NatAmount ) => {
489+ throw new Error ( 'returnToPool failed' ) ;
490+ } ,
491+ } ) ;
492+
493+ // make a new advancer that intentionally throws during returnToPool
494+ const advancer = makeAdvancer ( {
495+ borrower : mockBorrowerFacet ,
496+ notifier : mockNotifyF ,
497+ poolAccount : mockPoolAccount . account ,
498+ intermediateRecipient,
499+ settlementAddress,
500+ } ) ;
501+
502+ const evidence = MockCctpTxEvidences . AGORIC_PLUS_OSMO ( ) ;
503+ void advancer . handleTransactionEvent ( { evidence, risk : { } } ) ;
504+
505+ // pretend borrow succeeded and funds were depositing to the LCA
506+ resolveLocalTransferV ( ) ;
507+ // pretend the IBC Transfer failed
508+ mockPoolAccount . transferVResolver . reject ( new Error ( 'transfer failed' ) ) ;
509+ // pretend withdrawToSeat succeeded
510+ resolveWithdrawToSeatV ( ) ;
511+ await eventLoopIteration ( ) ;
512+
513+ t . deepEqual ( inspectLogs ( ) , [
514+ [ 'decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men' ] ,
515+ [ 'Advance failed' , Error ( 'transfer failed' ) ] ,
516+ [
517+ '🚨 return {"brand":"[Alleged: USDC brand]","value":"[146999999n]"} to pool failed. funds remain on "tmpReturnSeat"' ,
518+ Error ( 'returnToPool failed' ) ,
519+ ] ,
520+ ] ) ;
521+
522+ // ensure Settler is notified of failed advance
523+ t . like ( inspectNotifyCalls ( ) , [
524+ [
525+ {
526+ txHash : evidence . txHash ,
527+ forwardingAddress : evidence . tx . forwardingAddress ,
528+ } ,
529+ false , // indicates transfer failed
530+ ] ,
531+ ] ) ;
397532} ) ;
398533
399534test ( 'updates status to OBSERVED if pre-condition checks fail' , async t => {
@@ -785,8 +920,8 @@ test('notifies of advance failure if bank send fails', async t => {
785920 const {
786921 extensions : {
787922 services : { advancer } ,
788- helpers : { inspectLogs, inspectNotifyCalls } ,
789- mocks : { mockPoolAccount, resolveLocalTransferV } ,
923+ helpers : { inspectLogs, inspectBorrowerFacetCalls , inspectNotifyCalls } ,
924+ mocks : { mockPoolAccount, resolveLocalTransferV, resolveWithdrawToSeatV } ,
790925 } ,
791926 brands : { usdc } ,
792927 } = t . context ;
@@ -820,4 +955,18 @@ test('notifies of advance failure if bank send fails', async t => {
820955 false , // indicates send failed
821956 ] ,
822957 ] ) ;
958+
959+ // verify funds are returned to pool
960+ resolveWithdrawToSeatV ( ) ;
961+ await eventLoopIteration ( ) ;
962+ const { returnToPool } = inspectBorrowerFacetCalls ( ) ;
963+ t . is ( returnToPool . length , 1 , 'returnToPool is called after bank send fails' ) ;
964+ t . deepEqual (
965+ returnToPool [ 0 ] ,
966+ [
967+ Far ( 'MockZCFSeat' , { exit : theExit } ) ,
968+ usdc . make ( 244999999n ) , // 250000000n net of fees
969+ ] ,
970+ 'same amount borrowed is returned to LP' ,
971+ ) ;
823972} ) ;
0 commit comments