Skip to content

Commit

Permalink
chore: return advance payment to LP if zoeTools.localTransfer fails
Browse files Browse the repository at this point in the history
  • Loading branch information
0xpatrickdev committed Dec 9, 2024
1 parent 56d1c14 commit e33f9e8
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 13 deletions.
21 changes: 14 additions & 7 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,22 @@ export const prepareAdvancerKit = (
* @param {Error} error
* @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
*/
onRejected(error, { tmpSeat }) {
// TODO return seat allocation from ctx to LP?
log('🚨 advance deposit failed', q(error).toString());
// TODO #10510 (comprehensive error testing) determine
// course of action here
onRejected(error, { tmpSeat, advanceAmount, ...restCtx }) {
// we don't expect this to be a common failure. if it happens, return funds to LP
log(
'TODO live payment on seat to return to LP',
q(tmpSeat).toString(),
'⚠️ deposit to localOrchAccount failed, attempting to return payment to LP',
q(error).toString(),
);
try {
const { borrowerFacet, notifyFacet } = this.state;
notifyFacet.notifyAdvancingResult(restCtx, false);
borrowerFacet.repay(tmpSeat, harden({ USDC: advanceAmount }));
} catch (e) {
log(
'🚨 deposit to localOrchAccount failure recovery failed',
q(error).toString(),
);
}
},
},
transferHandler: {
Expand Down
24 changes: 23 additions & 1 deletion packages/fast-usdc/src/exos/liquidity-pool.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
SeatShape,
harden({ USDC: makeNatAmountShape(USDC, 1n) }),
).returns(),
repay: M.call(
SeatShape,
harden({ USDC: makeNatAmountShape(USDC, 1n) }),
).returns(),
}),
repayer: M.interface('repayer', {
repay: M.call(
Expand Down Expand Up @@ -178,7 +182,25 @@ export const prepareLiquidityPoolKit = (zone, zcf, USDC, tools) => {
Object.assign(this.state, post);
this.facets.external.publishPoolMetrics();
},
// TODO method to repay failed `LOA.deposit()`
/**
* If something fails during advance, return funds to the pool.
*
* @param {ZCFSeat} borrowSeat
* @param {{ USDC: Amount<'nat'>}} amountKWR
*/
repay(borrowSeat, amountKWR) {
const { zcfSeat: repaySeat } = zcf.makeEmptySeatKit();
const returnAmounts = harden({
Principal: amountKWR.USDC,
PoolFee: makeEmpty(USDC),
ContractFee: makeEmpty(USDC),
});
// arrange payments in a format repay is expecting
zcf.atomicRearrange(
harden([[borrowSeat, repaySeat, amountKWR, returnAmounts]]),
);
return this.facets.repayer.repay(repaySeat, returnAmounts);
},
},
repayer: {
/**
Expand Down
79 changes: 74 additions & 5 deletions packages/fast-usdc/test/exos/advancer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ const createTestExtensions = (t, common: CommonSetup) => {
// pretend funds move from tmpSeat to poolAccount
localTransferVK.resolver.resolve();
};
const rejectLocalTransfeferV = () => {
localTransferVK.resolver.reject(
new Error('One or more deposits failed: simulated error'),
);
};
const mockZoeTools = Far('MockZoeTools', {
localTransfer(...args: Parameters<ZoeTools['localTransfer']>) {
console.log('ZoeTools.localTransfer called with', args);
Expand Down Expand Up @@ -94,19 +99,30 @@ const createTestExtensions = (t, common: CommonSetup) => {
},
});

const mockBorrowerFacetCalls: unknown[][] = [];

const mockBorrowerF = Far('LiquidityPool Borrow Facet', {
borrow: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => {
console.log('LP.borrow called with', amounts);
console.log('LpBorrowFacet.borrow called with', amounts);
mockBorrowerFacetCalls.push(['borrow', seat, amounts]);
},
repay: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => {
console.log('LpBorrowFacet.repay called with', amounts);
mockBorrowerFacetCalls.push(['repay', seat, amounts]);
},
});

const mockBorrowerErrorF = Far('LiquidityPool Borrow Facet', {
borrow: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => {
console.log('LP.borrow called with', amounts);
console.log('LpBorrowFacet.borrow called with', amounts);
throw new Error(
`Cannot borrow. Requested ${q(amounts.USDC)} must be less than pool balance ${q(usdc.make(1n))}.`,
);
},
repay: (seat: ZCFSeat, amounts: { USDC: NatAmount }) => {
console.log('LpBorrowFacet.repay called with', amounts);
throw new Error('Mock Cannot repay. Contract will shut down.');
},
});

const advancer = makeAdvancer({
Expand All @@ -124,12 +140,14 @@ const createTestExtensions = (t, common: CommonSetup) => {
helpers: {
inspectLogs,
inspectNotifyCalls: () => harden(notifyAdvancingResultCalls),
inspectBorrowerFacetCalls: () => harden(mockBorrowerFacetCalls),
},
mocks: {
...mockAccounts,
mockBorrowerErrorF,
mockNotifyF,
resolveLocalTransferV,
rejectLocalTransfeferV,
},
services: {
advancer,
Expand Down Expand Up @@ -391,6 +409,57 @@ test('will not advance same txHash:chainId evidence twice', async t => {
]);
});

test.todo(
'#10510 zoeTools.localTransfer fails to deposit borrowed USDC to LOA',
);
test('returns payment to LP if zoeTools.localTransfer fails', async t => {
const {
extensions: {
services: { advancer },
helpers: { inspectLogs, inspectBorrowerFacetCalls, inspectNotifyCalls },
mocks: { rejectLocalTransfeferV },
},
brands: { usdc },
} = t.context;
const mockEvidence = MockCctpTxEvidences.AGORIC_PLUS_OSMO();

void advancer.handleTransactionEvent(mockEvidence);
rejectLocalTransfeferV();

await eventLoopIteration();

t.deepEqual(
inspectLogs(0),
[
'⚠️ deposit to localOrchAccount failed, attempting to return payment to LP',
'"[Error: One or more deposits failed: simulated error]"',
],
'contract logs report error',
);

const [borrowCall, repayCall] = inspectBorrowerFacetCalls();

t.is(borrowCall[0], 'borrow');
t.like(borrowCall[2], {
USDC: {
brand: usdc.brand,
},
});

t.is(repayCall[0], 'repay', 'repay is called when zt.localTransfer fails');
t.is(
repayCall[1],
borrowCall[1],
'same temp borrowSeat is supplied to LP during repay',
);
t.deepEqual(repayCall[2], borrowCall[2], 'advance amount is returned to LP');

t.like(
inspectNotifyCalls()[0],
[
{
txHash: mockEvidence.txHash,
forwardingAddress: mockEvidence.tx.forwardingAddress,
},
false, // indicates advance failed
],
'Advancing tx is recorded as AdvanceFailed',
);
});

0 comments on commit e33f9e8

Please sign in to comment.