Skip to content

Commit

Permalink
chore: use send for local transfers (#10827)
Browse files Browse the repository at this point in the history
closes: #10658

## Description
Currently, FUSDC advances to an `agoric1...` account will use an IBC Transfer to fulfill them. This is obviously not ideal, as it adds unnecessary time and work. Instead, use a bank send. An ERTP transfer would require the EUD to have a smart wallet.

### Security Considerations
None.

### Scaling Considerations
Code changes achieve same goal with less resources.

### Documentation Considerations
None

### Testing Considerations
Includes new tests. E2E (multichain) covered by existing scenario.

### Upgrade Considerations
None, unreleased code.
  • Loading branch information
mergify[bot] authored Jan 9, 2025
2 parents b5d1abe + 891d363 commit 78063fc
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 21 deletions.
32 changes: 17 additions & 15 deletions packages/fast-usdc/src/exos/advancer.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { makeFeeTools } from '../utils/fees.js';
* @import {ZoeTools} from '@agoric/orchestration/src/utils/zoe-tools.js';
* @import {VowTools} from '@agoric/vow';
* @import {Zone} from '@agoric/zone';
* @import {CctpTxEvidence, AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
* @import {AddressHook, EvmHash, FeeConfig, LogFn, NobleAddress, EvidenceWithRisk} from '../types.js';
* @import {StatusManager} from './status-manager.js';
* @import {LiquidityPoolKit} from './liquidity-pool.js';
*/
Expand Down Expand Up @@ -213,14 +213,20 @@ export const prepareAdvancerKit = (
* @param {AdvancerVowCtx & { tmpSeat: ZCFSeat }} ctx
*/
onFulfilled(result, ctx) {
const { poolAccount, intermediateRecipient } = this.state;
const { poolAccount, intermediateRecipient, settlementAddress } =
this.state;
const { destination, advanceAmount, tmpSeat: _, ...detail } = ctx;
const transferV = E(poolAccount).transfer(
destination,
{ denom: usdc.denom, value: advanceAmount.value },
{ forwardOpts: { intermediateRecipient } },
);
return watch(transferV, this.facets.transferHandler, {
const amount = harden({
denom: usdc.denom,
value: advanceAmount.value,
});
const transferOrSendV =
destination.chainId === settlementAddress.chainId
? E(poolAccount).send(destination, amount)
: E(poolAccount).transfer(destination, amount, {
forwardOpts: { intermediateRecipient },
});
return watch(transferOrSendV, this.facets.transferHandler, {
destination,
advanceAmount,
...detail,
Expand Down Expand Up @@ -253,17 +259,13 @@ export const prepareAdvancerKit = (
},
transferHandler: {
/**
* @param {unknown} result TODO confirm this is not a bigint (sequence)
* @param {undefined} result
* @param {AdvancerVowCtx} ctx
*/
onFulfilled(result, ctx) {
const { notifyFacet } = this.state;
const { advanceAmount, destination, ...detail } = ctx;
log('Advance transfer fulfilled', {
advanceAmount,
destination,
result,
});
log('Advance succeeded', { advanceAmount, destination });
// During development, due to a bug, this call threw.
// The failure was silent (no diagnostics) due to:
// - #10576 Vows do not report unhandled rejections
Expand All @@ -278,7 +280,7 @@ export const prepareAdvancerKit = (
*/
onRejected(error, ctx) {
const { notifyFacet } = this.state;
log('Advance transfer rejected', error);
log('Advance failed', error);
const { advanceAmount: _, ...restCtx } = ctx;
notifyFacet.notifyAdvancingResult(restCtx, false);
},
Expand Down
105 changes: 100 additions & 5 deletions packages/fast-usdc/test/exos/advancer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const createTestExtensions = (t, common: CommonSetup) => {

const { log, inspectLogs } = makeTestLogger(t.log);

chainHub.registerChain('agoric', fetchedChainInfo.agoric);
chainHub.registerChain('dydx', fetchedChainInfo.dydx);
chainHub.registerChain('osmosis', fetchedChainInfo.osmosis);

Expand Down Expand Up @@ -221,7 +222,7 @@ test('updates status to ADVANCING in happy path', async t => {
t.deepEqual(inspectLogs(), [
['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'],
[
'Advance transfer fulfilled',
'Advance succeeded',
{
advanceAmount: {
brand: usdc.brand,
Expand All @@ -232,7 +233,6 @@ test('updates status to ADVANCING in happy path', async t => {
encoding: 'bech32',
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
result: undefined,
},
],
]);
Expand Down Expand Up @@ -364,7 +364,7 @@ test('calls notifyAdvancingResult (AdvancedFailed) on failed transfer', async t

t.deepEqual(inspectLogs(), [
['decoded EUD: dydx183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'],
['Advance transfer rejected', Error('simulated error')],
['Advance failed', Error('simulated error')],
]);

// We expect to see an `AdvancedFailed` update, but that is now Settler's job.
Expand Down Expand Up @@ -488,15 +488,14 @@ test('will not advance same txHash:chainId evidence twice', async t => {
t.deepEqual(inspectLogs(), [
['decoded EUD: osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men'],
[
'Advance transfer fulfilled',
'Advance succeeded',
{
advanceAmount: { brand: usdc.brand, value: 146999999n },
destination: {
chainId: 'osmosis-1',
encoding: 'bech32',
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
result: undefined,
},
],
]);
Expand Down Expand Up @@ -714,3 +713,99 @@ test('no status update if `checkMintedEarly` returns true', async t => {
// no add'l logs as we return early
]);
});

test('uses bank send for agoric1 EUD', async t => {
const {
extensions: {
services: { advancer },
helpers: { inspectLogs, inspectNotifyCalls },
mocks: { mockPoolAccount, resolveLocalTransferV },
},
brands: { usdc },
bootstrap: { storage },
} = t.context;

const evidence = MockCctpTxEvidences.AGORIC_PLUS_AGORIC();
void advancer.handleTransactionEvent({ evidence, risk: {} });

// pretend borrow succeeded and funds were depositing to the LCA
resolveLocalTransferV();
// pretend the Bank Send settled
mockPoolAccount.sendVResolver.resolve();
// wait for handleTransactionEvent to do work
await eventLoopIteration();

t.deepEqual(inspectLogs(), [
['decoded EUD: agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2'],
[
'Advance succeeded',
{
advanceAmount: {
brand: usdc.brand,
value: 244999999n,
},
destination: {
chainId: 'agoric-3',
encoding: 'bech32',
value: 'agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2',
},
},
],
]);

// ensure Settler is notified of successful advance
t.like(inspectNotifyCalls(), [
[
{
txHash: evidence.txHash,
forwardingAddress: evidence.tx.forwardingAddress,
fullAmount: usdc.make(evidence.tx.amount),
destination: {
value: decodeAddressHook(evidence.aux.recipientAddress).query.EUD,
},
},
true, // indicates send succeeded
],
]);
});

test('notifies of advance failure if bank send fails', async t => {
const {
extensions: {
services: { advancer },
helpers: { inspectLogs, inspectNotifyCalls },
mocks: { mockPoolAccount, resolveLocalTransferV },
},
brands: { usdc },
} = t.context;

const evidence = MockCctpTxEvidences.AGORIC_PLUS_AGORIC();
void advancer.handleTransactionEvent({ evidence, risk: {} });

// pretend borrow succeeded and funds were depositing to the LCA
resolveLocalTransferV();
// pretend the Bank Send failed
mockPoolAccount.sendVResolver.reject(new Error('simulated error'));
// wait for handleTransactionEvent to do work
await eventLoopIteration();

t.deepEqual(inspectLogs(), [
['decoded EUD: agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2'],
['Advance failed', Error('simulated error')],
]);

// ensure Settler is notified of failed advance
t.like(inspectNotifyCalls(), [
[
{
txHash: evidence.txHash,
forwardingAddress: evidence.tx.forwardingAddress,
fullAmount: usdc.make(evidence.tx.amount),
destination: {
value: decodeAddressHook(evidence.aux.recipientAddress).query.EUD,
},
},
false, // indicates send failed
],
]);
});
31 changes: 31 additions & 0 deletions packages/fast-usdc/test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { CctpTxEvidence, EvmAddress } from '../src/types.js';
const mockScenarios = [
'AGORIC_PLUS_OSMO',
'AGORIC_PLUS_DYDX',
'AGORIC_PLUS_AGORIC',
'AGORIC_NO_PARAMS',
'AGORIC_UNKNOWN_EUD',
] as const;
Expand Down Expand Up @@ -64,6 +65,27 @@ export const MockCctpTxEvidences: Record<
},
chainId: 1,
}),
AGORIC_PLUS_AGORIC: (receiverAddress?: string) => ({
blockHash:
'0x80d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee6z9',
blockNumber: 21037600n,
txHash:
'0xd81bc6105b60a234c7c50ac17816ebcd5561d366df8bf3be59ff3875527617z9',
tx: {
amount: 250000000n,
forwardingAddress: 'noble17ww3rfusv895d92c0ncgj0fl9trntn70jz7hd5',
sender: Senders.default,
},
aux: {
forwardingChannel: 'channel-21',
recipientAddress:
receiverAddress ||
encodeAddressHook(settlementAddress.value, {
EUD: 'agoric13rj0cc0hm5ac2nt0sdup2l7gvkx4v9tyvgq3h2',
}),
},
chainId: 1,
}),
AGORIC_NO_PARAMS: (receiverAddress?: string) => ({
blockHash:
'0x70d7343e04f8160892e94f02d6a9b9f255663ed0ac34caca98544c8143fee699',
Expand Down Expand Up @@ -136,6 +158,15 @@ export const MockVTransferEvents: Record<
recieverAddress ||
MockCctpTxEvidences.AGORIC_PLUS_DYDX().aux.recipientAddress,
}),
AGORIC_PLUS_AGORIC: (recieverAddress?: string) =>
buildVTransferEvent({
...nobleDefaultVTransferParams,
amount: MockCctpTxEvidences.AGORIC_PLUS_AGORIC().tx.amount,
sender: MockCctpTxEvidences.AGORIC_PLUS_AGORIC().tx.forwardingAddress,
receiver:
recieverAddress ||
MockCctpTxEvidences.AGORIC_PLUS_AGORIC().aux.recipientAddress,
}),
AGORIC_NO_PARAMS: (recieverAddress?: string) =>
buildVTransferEvent({
...nobleDefaultVTransferParams,
Expand Down
6 changes: 6 additions & 0 deletions packages/fast-usdc/test/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const prepareMockOrchAccounts = (
},
) => {
// each can only be resolved/rejected once per test
const poolAccountSendVK = makeVowKit<undefined>();
const poolAccountTransferVK = makeVowKit<undefined>();
const settleAccountTransferVK = makeVowKit<undefined>();

Expand All @@ -37,6 +38,10 @@ export const prepareMockOrchAccounts = (
// XXX consider a mock for deposit failure
return asVow(async () => usdc.issuer.getAmountOf(payment));
},
send(destination: ChainAddress, amount: DenomAmount) {
log('PoolAccount.send() called with', destination, amount);
return poolAccountSendVK.vow;
},
});

const poolAccount = mockedPoolAccount as unknown as HostInterface<
Expand All @@ -57,6 +62,7 @@ export const prepareMockOrchAccounts = (
mockPoolAccount: {
account: poolAccount,
transferVResolver: poolAccountTransferVK.resolver,
sendVResolver: poolAccountSendVK.resolver,
},
settlement: {
account: settlementAccount,
Expand Down
2 changes: 1 addition & 1 deletion packages/orchestration/src/orchestration-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ export interface OrchestrationAccountCommon {
* @param amount - the amount to send
* @returns void
*/
send: (toAccount: ChainAddress, amounts: AmountArg) => Promise<void>;
send: (toAccount: ChainAddress, amount: AmountArg) => Promise<void>;

/**
* Transfer multiple amounts to another account on the same chain. The promise settles when the transfer is complete.
Expand Down

0 comments on commit 78063fc

Please sign in to comment.