Skip to content

Commit

Permalink
fix: minted early tracking
Browse files Browse the repository at this point in the history
- ensure multiples of the same NFA+amount are tracked
  • Loading branch information
0xpatrickdev authored and turadg committed Feb 28, 2025
1 parent 6bdc747 commit 400b37b
Show file tree
Hide file tree
Showing 2 changed files with 131 additions and 6 deletions.
24 changes: 18 additions & 6 deletions packages/fast-usdc/src/exos/settler.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
EvmHashShape,
makeNatAmountShape,
} from '../type-guards.js';
import { asMultiset } from '../utils/store.js';

/**
* @import {FungibleTokenPacketData} from '@agoric/cosmic-proto/ibc/applications/transfer/v2/packet.js';
Expand Down Expand Up @@ -149,6 +150,7 @@ export const prepareSettler = (
).returns(M.boolean()),
}),
self: M.interface('SettlerSelfI', {
addMintedEarly: M.call(M.string(), M.nat()).returns(),
disburse: M.call(EvmHashShape, M.nat()).returns(M.promise()),
forward: M.call(EvmHashShape, M.nat(), M.string()).returns(),
}),
Expand All @@ -174,8 +176,8 @@ export const prepareSettler = (
intermediateRecipient: config.intermediateRecipient,
/** @type {HostInterface<TargetRegistration>|undefined} */
registration: undefined,
/** @type {SetStore<ReturnType<typeof makeMintedEarlyKey>>} */
mintedEarly: zone.detached().setStore('mintedEarly'),
/** @type {MapStore<ReturnType<typeof makeMintedEarlyKey>, number>} */
mintedEarly: zone.detached().mapStore('mintedEarly'),
};
},
{
Expand Down Expand Up @@ -221,7 +223,7 @@ export const prepareSettler = (

case PendingTxStatus.Advancing:
log('⚠️ tap: minted while advancing', nfa, amount);
this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
self.addMintedEarly(nfa, amount);
return;

case PendingTxStatus.Observed:
Expand All @@ -234,7 +236,7 @@ export const prepareSettler = (
log('⚠️ tap: minted before observed', nfa, amount);
// XXX consider capturing in vstorage
// we would need a new key, as this does not have a txHash
this.state.mintedEarly.add(makeMintedEarlyKey(nfa, amount));
self.addMintedEarly(nfa, amount);
}
},
},
Expand All @@ -256,7 +258,7 @@ export const prepareSettler = (
const { value: fullValue } = fullAmount;
const key = makeMintedEarlyKey(forwardingAddress, fullValue);
if (mintedEarly.has(key)) {
mintedEarly.delete(key);
asMultiset(mintedEarly).remove(key);
statusManager.advanceOutcomeForMintedEarly(txHash, success);
if (success) {
void this.facets.self.disburse(txHash, fullValue);
Expand Down Expand Up @@ -290,7 +292,7 @@ export const prepareSettler = (
forwardingAddress,
amount,
);
mintedEarly.delete(key);
asMultiset(mintedEarly).remove(key);
statusManager.advanceOutcomeForUnknownMint(evidence);
void this.facets.self.forward(txHash, amount, destination.value);
return true;
Expand All @@ -299,6 +301,16 @@ export const prepareSettler = (
},
},
self: {
/**
* Helper function to track a minted-early transaction by incrementing or initializing its counter
* @param {NobleAddress} address
* @param {NatValue} amount
*/
addMintedEarly(address, amount) {
const key = makeMintedEarlyKey(address, amount);
const { mintedEarly } = this.state;
asMultiset(mintedEarly).add(key);
},
/**
* @param {EvmHash} txHash
* @param {NatValue} fullValue
Expand Down
113 changes: 113 additions & 0 deletions packages/fast-usdc/test/exos/settler.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,119 @@ test('Settlement for unknown transaction (minted early)', async t => {
]);
});

test('Multiple minted early transactions with same address and amount', async t => {
const {
common: {
brands: { usdc },
},
makeSettler,
defaultSettlerParams,
repayer,
accounts,
peekCalls,
inspectLogs,
makeSimulate,
storage,
} = t.context;

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

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

t.log('Simulate second incoming IBC settlement with same address and amount');
void settler.tap.receiveUpcall(MockVTransferEvents.AGORIC_PLUS_OSMO());
await eventLoopIteration();

t.log('Nothing transferred yet - both transactions are minted early');
t.deepEqual(peekCalls(), []);
t.deepEqual(accounts.settlement.callLog, []);
const tapLogs = inspectLogs();

// Should show two instances of "minted before observed"
const mintedBeforeObservedLogs = tapLogs
.flat()
.filter(
log => typeof log === 'string' && log.includes('minted before observed'),
);
t.is(
mintedBeforeObservedLogs.length,
2,
'Should have two "minted before observed" log entries',
);

t.log('Oracle operators report first transaction...');
const evidence1 = simulate.observeLate(
MockCctpTxEvidences.AGORIC_PLUS_OSMO(),
);
await eventLoopIteration();

t.log('First transfer should complete');
t.is(
accounts.settlement.callLog.length,
1,
'First transfer should be initiated',
);
accounts.settlement.transferVResolver.resolve(undefined);
await eventLoopIteration();
t.deepEqual(storage.getDeserialized(`fun.txns.${evidence1.txHash}`), [
{ evidence: evidence1, status: 'OBSERVED' },
{ status: 'FORWARDED' },
]);

t.log(
'Oracle operators report second transaction with same address/amount...',
);
const evidence2 = simulate.observeLate({
...MockCctpTxEvidences.AGORIC_PLUS_OSMO(),
txHash:
'0x0000000000000000000000000000000000000000000000000000000000000000',
});
await eventLoopIteration();

t.log('Second transfer should also complete');
t.is(
accounts.settlement.callLog.length,
2,
'Second transfer should be initiated',
);
accounts.settlement.transferVResolver.resolve(undefined);
await eventLoopIteration();
t.deepEqual(storage.getDeserialized(`fun.txns.${evidence2.txHash}`), [
{ evidence: evidence2, status: 'OBSERVED' },
{ status: 'FORWARDED' },
]);

// Simulate a third transaction and verify no more are tracked as minted early
simulate.observe({
...MockCctpTxEvidences.AGORIC_PLUS_OSMO(),
txHash:
'0x0000000000000000000000000000000000000000000000000000000000000001',
});
const foundMore = inspectLogs()
.flat()
.filter(
log =>
typeof log === 'string' && log.includes('matched minted early key'),
);
t.is(
foundMore.length,
2,
'Should not find any more minted early transactions',
);
t.is(
accounts.settlement.callLog.length,
2,
'No additional transfers should be initiated',
);
});

test('Settlement for Advancing transaction (advance succeeds)', async t => {
const {
accounts,
Expand Down

0 comments on commit 400b37b

Please sign in to comment.