Skip to content

Commit

Permalink
feat(fast-usdc): settler handles OBSERVED tx
Browse files Browse the repository at this point in the history
  • Loading branch information
dckc committed Nov 22, 2024
1 parent fb8c53b commit 25c59e1
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 9 deletions.
38 changes: 32 additions & 6 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { AmountMath } from '@agoric/ertp';
import { assertAllDefined, makeTracer } from '@agoric/internal';
import { atob } from '@endo/base64';
import { makeError, q } from '@endo/errors';
import { E } from '@endo/far';
import { M } from '@endo/patterns';

import { AmountMath } from '@agoric/ertp';
import { PendingTxStatus } from '../constants.js';
import { addressTools } from '../utils/address.js';
import { makeFeeTools } from '../utils/fees.js';

/**
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
* @import {Denom, OrchestrationAccount, LocalAccountMethods} from '@agoric/orchestration';
* @import {Denom, OrchestrationAccount, LocalAccountMethods, ChainHub} from '@agoric/orchestration';
* @import {WithdrawToSeat} from '@agoric/orchestration/src/utils/zoe-tools'
* @import {IBCChannelID, VTransferIBCEvent} from '@agoric/vats';
* @import {Zone} from '@agoric/zone';
Expand All @@ -30,17 +32,21 @@ const trace = makeTracer('Settler');
* @param {FeeConfig} caps.feeConfig
* @param {HostOf<WithdrawToSeat>} caps.withdrawToSeat
* @param {import('@agoric/vow').VowTools} caps.vowTools
* @param {ChainHub} caps.chainHub
*/
export const prepareSettler = (
zone,
{ statusManager, USDC, zcf, feeConfig, withdrawToSeat, vowTools },
{ statusManager, USDC, zcf, feeConfig, withdrawToSeat, vowTools, chainHub },
) => {
assertAllDefined({ statusManager });
return zone.exoClass(
'Fast USDC Settler',
M.interface('SettlerI', {
monitorTransfers: M.callWhen().returns(M.any()),
receiveUpcall: M.call(M.record()).returns(M.promise()),
settleSansFees: M.call(M.string(), M.string(), M.nat()).returns(
M.promise(),
),
}),
/**
* @param {{
Expand Down Expand Up @@ -107,6 +113,11 @@ export const prepareSettler = (
);
}

const pending = statusManager.lookupPending(sender, amountInt);
if (pending.find(it => it.status === PendingTxStatus.Observed)) {
return this.self.settleSansFees(sender, EUD, amountInt);
}

// Disperse funds

const { repayer, settlementAccount } = this.state;
Expand All @@ -133,10 +144,25 @@ export const prepareSettler = (
repayer.repay(settlingSeat, split);

// update status manager, marking tx `SETTLED`
statusManager.settle(
/** @type {NobleAddress} */ (tx.sender),
amountInt,
statusManager.settle(sender, amountInt);
},
/**
* @param {NobleAddress} sender
* @param {string} EUD
* @param {bigint} amountInt
*/
async settleSansFees(sender, EUD, amountInt) {
const { settlementAccount } = this.state;

const dest = chainHub.makeChainAddress(EUD);

const txfrV = E(settlementAccount).transfer(
dest,
AmountMath.make(USDC, amountInt),
);
await vowTools.when(txfrV); // TODO: watch, handle failure

statusManager.settle(sender, amountInt);
},
},
{
Expand Down
65 changes: 64 additions & 1 deletion packages/fast-usdc/test/exos/settler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,17 @@ const makeTestContext = async t => {
return vowTools.asVow(() => {});
};

const { chainHub } = common.facadeServices;
chainHub.registerChain('dydx', fetchedChainInfo.dydx);
chainHub.registerChain('osmosis', fetchedChainInfo.osmosis);
const makeSettler = prepareSettler(zone.subZone('settler'), {
statusManager,
USDC: usdc.brand,
zcf,
withdrawToSeat: mockWithdrawToSeat,
feeConfig: common.commonPrivateArgs.feeConfig,
vowTools: common.bootstrap.vowTools,
chainHub,
});

const defaultSettlerParams = harden({
Expand Down Expand Up @@ -126,7 +130,7 @@ const makeTestContext = async t => {

const test = anyTest as TestFn<Awaited<ReturnType<typeof makeTestContext>>>;

test.before(async t => (t.context = await makeTestContext(t)));
test.beforeEach(async t => (t.context = await makeTestContext(t)));

test('happy path: disburse to LPs; StatusManager removes tx', async t => {
const {
Expand Down Expand Up @@ -218,6 +222,65 @@ test('happy path: disburse to LPs; StatusManager removes tx', async t => {
// TODO, confirm vstorage write for TxStatus.SETTLED
});

test('slow path: disburse to LPs; StatusManager removes tx', async t => {
const {
common,
makeSettler,
statusManager,
defaultSettlerParams,
repayer,
simulate,
accounts,
peekCalls,
} = t.context;
const { usdc } = common.brands;
const { feeConfig } = common.commonPrivateArgs;

const settler = makeSettler({
repayer,
settlementAccount: accounts.settlement.account,
...defaultSettlerParams,
});

const cctpTxEvidence = simulate.observe();
t.deepEqual(
statusManager.lookupPending(
cctpTxEvidence.tx.forwardingAddress,
cctpTxEvidence.tx.amount,
),
[{ ...cctpTxEvidence, status: PendingTxStatus.Observed }],
'statusManager shows this tx is only observed',
);

t.log('Simulate incoming IBC settlement');
void settler.receiveUpcall(MockVTransferEvents.AGORIC_PLUS_OSMO());
await eventLoopIteration();

t.log('review settler interactions with other components');
t.deepEqual(peekCalls(), []);
t.deepEqual(accounts.settlement.callLog, [
[
'transfer',
{
chainId: 'osmosis-1',
encoding: 'bech32',
value: 'osmo183dejcnmkka5dzcu9xw6mywq0p2m5peks28men',
},
usdc.units(150),
],
]);

t.deepEqual(
statusManager.lookupPending(
cctpTxEvidence.tx.forwardingAddress,
cctpTxEvidence.tx.amount,
),
[],
'SETTLED entry removed from StatusManger',
);
// TODO, confirm vstorage write for TxStatus.SETTLED
});

test.todo("StatusManager does not receive update when we can't settle");

test.todo('Observed -> Settle flow');
6 changes: 4 additions & 2 deletions packages/fast-usdc/test/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,10 @@ export const prepareMockOrchAccounts = (
OrchestrationAccount<{ chainId: 'agoric' }>
>;

const settlementCallLog = [] as any[];
const settlementAccountMock = zone.exo('Mock Settlement Account', undefined, {
someMethod() {
throw Error('todo');
transfer(...args) {
settlementCallLog.push(harden(['transfer', ...args]));
},
});
const settlementAccount = settlementAccountMock as unknown as HostInterface<
Expand All @@ -59,6 +60,7 @@ export const prepareMockOrchAccounts = (
},
settlement: {
account: settlementAccount,
callLog: settlementCallLog,
},
};
};
Expand Down

0 comments on commit 25c59e1

Please sign in to comment.