Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions multichain-testing/scripts/ymax-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ const main = async (
await walletKit.readPublished('agoricNames.instance'),
);
await cf.deliverResolverInvitation(resolver, postalService);
await cf.deliverResolverServiceInvitation(resolver, postalService);
return;
}

Expand Down
2 changes: 2 additions & 0 deletions packages/client-utils/src/signing-smart-wallet-kit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,5 @@ export const reflectWalletStore = (
saveOfferResult,
});
};

export type WalletStore = ReturnType<typeof reflectWalletStore>;
25 changes: 25 additions & 0 deletions packages/portfolio-contract/src/portfolio.contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -481,14 +481,25 @@ export const contract = async (
return makePlanner();
}, 'planner');

const makeResolverServiceInvitation = () =>
zcf.makeInvitation(seat => {
seat.exit();
return resolverService;
}, 'resolver service');

const creatorFacet = zone.exo(
'PortfolioAdmin',
M.interface('PortfolioAdmin', {
makeResolverInvitation: M.callWhen().returns(InvitationShape),
getResolverService: M.call().returns(M.remotable()),
deliverResolverInvitation: M.callWhen(
M.string(),
M.remotable('Instance'),
).returns(),
deliverResolverServiceInvitation: M.callWhen(
M.string(),
M.remotable('Instance'),
).returns(),
makePlannerInvitation: M.callWhen().returns(InvitationShape),
deliverPlannerInvitation: M.callWhen(
M.string(),
Expand All @@ -502,6 +513,9 @@ export const contract = async (
makeResolverInvitation() {
return makeResolverInvitation();
},
getResolverService() {
return resolverService;
},
async deliverResolverInvitation(
address: string,
instancePS: Instance<() => { publicFacet: PostalServiceI }>,
Expand All @@ -513,6 +527,17 @@ export const contract = async (
await E(pfP).deliverPayment(address, invitation);
trace('delivered resolver invitation');
},
async deliverResolverServiceInvitation(
address: string,
instancePS: Instance<() => { publicFacet: PostalServiceI }>,
) {
const zoe = zcf.getZoeService();
const pfP = E(zoe).getPublicFacet(instancePS);
const invitation = await makeResolverServiceInvitation();
trace('made resolver service invitation', invitation);
await E(pfP).deliverPayment(address, invitation);
trace('delivered resolver service invitation');
},
makePlannerInvitation() {
return makePlannerInvitation();
},
Expand Down
24 changes: 7 additions & 17 deletions packages/portfolio-contract/test/cctp-resolver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,42 +4,32 @@ import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import { E } from '@endo/far';
import { mustMatch } from '@endo/patterns';
import type { TransactionSettlementOfferArgs } from '../src/resolver/types.ts';
import { ResolverOfferArgsShapes } from '../src/resolver/types.ts';
import { TransactionSettlementOfferArgsShape } from '../src/resolver/types.ts';
import { deploy } from './contract-setup.ts';

test('CCTP settlement invitation - no pending transaction found', async t => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To demonstrate that this is a compatible change, I'd rather leave the old test in place and add a test that uses invoke.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All of this test was recapitulated by the CCTP confirmation invitation exits seat properly test, which does extra checks, and was kept in place.

const { started, zoe } = await deploy(t);
test('CCTP settlement - no pending transaction found', async t => {
const { started } = await deploy(t);
const { creatorFacet } = started;

const resolverInvitation = await E(creatorFacet).makeResolverInvitation();
const resolverSeat = await E(zoe).offer(resolverInvitation);
const resolverMakers = (await E(resolverSeat).getOfferResult())
.invitationMakers;
const confirmInvitation = await E(resolverMakers).SettleTransaction();
const resolverService = await E(creatorFacet).getResolverService();

const offerArgs: TransactionSettlementOfferArgs = {
status: 'success' as const,
txId: 'tx0',
};
const confirmationSeat = await E(zoe).offer(
confirmInvitation,
{},
undefined,
offerArgs,
);

await t.throwsAsync(E(confirmationSeat).getOfferResult());
await t.throwsAsync(E(resolverService).settleTransaction(offerArgs));
});

test('CCTP confirmation invitation - invalid status throws', async t => {
test('CCTP confirmation - invalid status throws', async t => {
await deploy(t);

const invalidOfferArgs: TransactionSettlementOfferArgs = harden({
status: 'invalid' as any,
txId: 'tx0',
});
t.throws(() =>
mustMatch(invalidOfferArgs, ResolverOfferArgsShapes.SettleTransaction),
mustMatch(invalidOfferArgs, TransactionSettlementOfferArgsShape),
);
});

Expand Down
12 changes: 9 additions & 3 deletions packages/portfolio-contract/test/contract-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import {
makeUSDNIBCTraffic,
portfolio0lcaOrch,
} from './mocks.ts';
import { getResolverMakers, settleTransaction } from './resolver-helpers.ts';
import {
getResolverHelperKitForInvokeService as getResolverHelperKit,
settleTransaction,
} from './resolver-helpers.ts';
import {
chainInfoWithCCTP,
makeIncomingEVMEvent,
Expand Down Expand Up @@ -142,7 +145,10 @@ export const setupTrader = async (t, initial = 10_000) => {
ibcBridge.addMockAck(msg, ack);
}

const resolverMakers = await getResolverMakers(zoe, started.creatorFacet);
const resolverHelperKit = await getResolverHelperKit(
started.creatorFacet,
zoe,
);

/**
* Read pure data (CapData that has no slots) from the storage path
Expand Down Expand Up @@ -173,7 +179,7 @@ export const setupTrader = async (t, initial = 10_000) => {
if (!txIds.length) break;
for (const txId of txIds) {
const txNum = Number(txId.replace(/^tx/, ''));
await settleTransaction(zoe, resolverMakers, txNum, status);
await settleTransaction(resolverHelperKit, txNum, status);
done.push(txId);
}
}
Expand Down
80 changes: 57 additions & 23 deletions packages/portfolio-contract/test/resolver-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,60 +3,94 @@
import { E } from '@endo/far';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import type { UserSeat, ZoeService } from '@agoric/zoe';
import type { ResolverInvitationMakers } from '../src/resolver/resolver.exo.js';
import type {
ResolverInvitationMakers,
ResolverKit,
} from '../src/resolver/resolver.exo.js';
import type { TxStatus } from '../src/resolver/constants.js';

/**
* Helper to get resolver makers from a creator facet.
* Helper to get a resolver kit from a creator facet's invitation makers.
* Encapsulates the common pattern of making a resolver invitation,
* offering it to zoe, and getting the resolver makers from the result.
*
* @param zoe - Zoe service instance
* @param creatorFacet - The creator facet that has makeResolverInvitation
* @returns {Promise<ResolverInvitationMakers>}
* @param zoe - Zoe service instance
*/
export const getResolverMakers = async (
zoe: ZoeService,
export const getResolverHelperKitForInvitationMakers = async (
creatorFacet: any,
): Promise<ResolverInvitationMakers> => {
zoe: ZoeService,
): Promise<{ zoe: ZoeService; invitationMakers: ResolverInvitationMakers }> => {
const resolverInvitation = await E(creatorFacet).makeResolverInvitation();
const resolverSeat = (await E(zoe).offer(resolverInvitation)) as UserSeat<{
invitationMakers: ResolverInvitationMakers;
}>;
return (await E(resolverSeat).getOfferResult()).invitationMakers;
const { invitationMakers } = await E(resolverSeat).getOfferResult();
return { zoe, invitationMakers };
};

/**
* Helper to get a resolver kit from a creator facet's resolver service.
*
* @param creatorFacet - The creator facet that has getResolverService
* @returns {Promise<ResolverKit['service']>}
*/
export const getResolverHelperKitForInvokeService = async (
creatorFacet: any,
_zoe?: ZoeService,
): Promise<{ resolverService: ResolverKit['service'] }> => {
const resolverService = await E(creatorFacet).getResolverService();
return { resolverService };
};

export type ResolverHelperKit =
| Awaited<ReturnType<typeof getResolverHelperKitForInvitationMakers>>
| Awaited<ReturnType<typeof getResolverHelperKitForInvokeService>>;

/**
* Helper to manually settle a transaction in tests.
* This should be called when a test flow includes a transaction operation
* to resolve the waiting promise.
*
* @param zoe - Zoe service instance
* @param resolverMakers - ResolverInvitationMakers instance
* @param helperKit - ResolverHelperKit
* @param txNumber - Transaction number for txId
* @param status - Transaction status
* @param log - Optional logging function (defaults to console.log, pass () => {} to disable)
*/
export const settleTransaction = async (
zoe: ZoeService,
resolverMakers: ResolverInvitationMakers,
helperKit: ResolverHelperKit,
txNumber: number = 0,
status: Exclude<TxStatus, 'pending'> = 'success',
log: (message: string, ...args: any[]) => void = console.log,
): Promise<string> => {
): Promise<void> => {
await eventLoopIteration();
await eventLoopIteration(); // XXX for some reason we need two iterations here to pass the tests

log('Creating transaction settlement invitation...');
const settleInvitation = await E(resolverMakers).SettleTransaction();
log('Got settlement invitation, making offer...');
if ('resolverService' in helperKit) {
log('Calling resolver transaction settlement ...');

await E(helperKit.resolverService).settleTransaction({
status,
txId: `tx${txNumber}`,
});
} else {
log('Creating transaction settlement invitation...');
const settleInvitation = await E(
helperKit.invitationMakers,
).SettleTransaction();
log('Got settlement invitation, making offer...');

const settlementSeat = await E(zoe).offer(settleInvitation, {}, undefined, {
status,
txId: `tx${txNumber}`,
});
const settlementSeat = await E(helperKit.zoe).offer(
settleInvitation,
{},
undefined,
{
status,
txId: `tx${txNumber}`,
},
);

const result = (await E(settlementSeat).getOfferResult()) as string;
log(`Transaction settlement got result:`, result);
return result;
const result = (await E(settlementSeat).getOfferResult()) as string;
log(`Transaction settlement got result:`, result);
}
};
16 changes: 11 additions & 5 deletions packages/portfolio-contract/test/ymax-scenario.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import {
makeUSDNIBCTraffic,
portfolio0lcaOrch,
} from './mocks.ts';
import { getResolverMakers, settleTransaction } from './resolver-helpers.ts';
import {
getResolverHelperKitForInvitationMakers as getResolverHelperKit,
settleTransaction,
} from './resolver-helpers.ts';

// Use an EVM chain whose axelar ID differs from its chain name
const { sourceChain } = evmNamingDistinction;
Expand Down Expand Up @@ -76,7 +79,10 @@ const rebalanceScenarioMacro = test.macro({
? withBrand(scenarios[scenario.previous], usdc.brand)
: undefined;

const resolverMakers = await getResolverMakers(zoe, started.creatorFacet);
const resolverHelperKit = await getResolverHelperKit(
started.creatorFacet,
zoe,
);

if (description.includes('Recover')) {
// simulate arrival of funds in the LCA via IBC from Noble
Expand Down Expand Up @@ -110,16 +116,16 @@ const rebalanceScenarioMacro = test.macro({
await eventLoopIteration();
if (move.dest === '@Arbitrum') {
// Also confirm CCTP transaction for flows to Arbitrum
await settleTransaction(zoe, resolverMakers, index, 'success');
await settleTransaction(resolverHelperKit, index, 'success');
index += 1;
}
if (move.src === '@Arbitrum') {
await settleTransaction(zoe, resolverMakers, index, 'success');
await settleTransaction(resolverHelperKit, index, 'success');
index += 1;
if (move.dest === '@agoric') {
await transmitVTransferEvent('acknowledgementPacket', -1);
// Also confirm Noble transaction for flows to Noble
await settleTransaction(zoe, resolverMakers, index, 'success');
await settleTransaction(resolverHelperKit, index, 'success');
index += 1;
}
}
Expand Down
Loading
Loading