Skip to content

Commit 12ce494

Browse files
authored
test: continuing offers in orchestrate async-flows (#9679)
closes: #9673 refs: #9066 refs: #9042 ## Description - Adds `basic-flow.contract.js`, proposal, and builder for testing `orchestrate` (`async-flow`) continuing invitations to facilitate testing. These are also the first bootstrap + multichain tests with `orchestrate` / `async-flow`. - Updates `asContinuingInvitation` and `getPublicTopics` methods to return Vows to satisfy membrane constraints - Ensures `storagePath`, part of `ContinuingOfferResult` is a string instead of a Promise/Vow so `smart-wallet` can easily handle this and the `async-flow` membrane is appeased (no promises) - Updates chain facade implementations, ensuring each account has it's own storage node (towards #9066) ### Testing Considerations Includes unit, bootstrap, and multichain (e2e) tests for an async-flow contract. E2E ensure accounts can be created on agoric, cosmos, and osmosis and return continuing invitations. ### Upgrade Considerations This did not require any changes to smart-wallet. There is a change to `PublicTopicShape` to accept a promise or a string (previously just a promise), but this is only in contracts relaying on smart-wallet, not smart-wallet itself.
2 parents 0b6d5f3 + b7a9ed3 commit 12ce494

26 files changed

Lines changed: 678 additions & 138 deletions

multichain-testing/.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
.yarn/*
33
!.yarn/patches/*
44
revise-chain-info*
5-
start-*
5+
start*
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import anyTest from '@endo/ses-ava/prepare-endo.js';
2+
import type { TestFn } from 'ava';
3+
import { commonSetup, SetupContextWithWallets } from './support.js';
4+
import { makeDoOffer } from '../tools/e2e-tools.js';
5+
import { chainConfig, chainNames } from './support.js';
6+
7+
const test = anyTest as TestFn<SetupContextWithWallets>;
8+
9+
const accounts = ['user1', 'user2', 'user3']; // one account for each scenario
10+
11+
const contractName = 'basicFlows';
12+
const contractBuilder =
13+
'../packages/builders/scripts/orchestration/init-basic-flows.js';
14+
15+
test.before(async t => {
16+
const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t);
17+
deleteTestKeys(accounts).catch();
18+
const wallets = await setupTestKeys(accounts);
19+
t.context = { ...rest, wallets, deleteTestKeys };
20+
21+
t.log('bundle and install contract', contractName);
22+
await t.context.deployBuilder(contractBuilder);
23+
const vstorageClient = t.context.makeQueryTool();
24+
await t.context.retryUntilCondition(
25+
() => vstorageClient.queryData(`published.agoricNames.instance`),
26+
res => contractName in Object.fromEntries(res),
27+
`${contractName} instance is available`,
28+
);
29+
});
30+
31+
test.after(async t => {
32+
const { deleteTestKeys } = t.context;
33+
deleteTestKeys(accounts);
34+
});
35+
36+
const makeAccountScenario = test.macro({
37+
title: (_, chainName: string) => `Create account on ${chainName}`,
38+
exec: async (t, chainName: string) => {
39+
const config = chainConfig[chainName];
40+
if (!config) return t.fail(`Unknown chain: ${chainName}`);
41+
42+
const {
43+
wallets,
44+
provisionSmartWallet,
45+
makeQueryTool,
46+
retryUntilCondition,
47+
} = t.context;
48+
49+
const vstorageClient = makeQueryTool();
50+
51+
const wallet = accounts[chainNames.indexOf(chainName)];
52+
const wdUser1 = await provisionSmartWallet(wallets[wallet], {
53+
BLD: 100n,
54+
IST: 100n,
55+
});
56+
t.log(`provisioning agoric smart wallet for ${wallets[wallet]}`);
57+
58+
const doOffer = makeDoOffer(wdUser1);
59+
t.log(`${chainName} makeAccount offer`);
60+
const offerId = `${chainName}-makeAccount-${Date.now()}`;
61+
62+
// FIXME we get payouts but not an offer result; it times out
63+
// https://github.com/Agoric/agoric-sdk/issues/9643
64+
// chain logs shows an UNPUBLISHED result
65+
const _offerResult = await doOffer({
66+
id: offerId,
67+
invitationSpec: {
68+
source: 'agoricContract',
69+
instancePath: [contractName],
70+
callPipe: [['makeOrchAccountInvitation']],
71+
},
72+
offerArgs: { chainName },
73+
proposal: {},
74+
});
75+
t.true(_offerResult);
76+
// t.is(await _offerResult, 'UNPUBLISHED', 'representation of continuing offer');
77+
78+
// TODO fix above so we don't have to poll for the offer result to be published
79+
// https://github.com/Agoric/agoric-sdk/issues/9643
80+
const currentWalletRecord = await retryUntilCondition(
81+
() =>
82+
vstorageClient.queryData(`published.wallet.${wallets[wallet]}.current`),
83+
({ offerToPublicSubscriberPaths }) =>
84+
Object.fromEntries(offerToPublicSubscriberPaths)[offerId],
85+
`${offerId} continuing invitation is in vstorage`,
86+
);
87+
88+
const offerToPublicSubscriberMap = Object.fromEntries(
89+
currentWalletRecord.offerToPublicSubscriberPaths,
90+
);
91+
92+
const address = offerToPublicSubscriberMap[offerId]?.account
93+
.split('.')
94+
.pop();
95+
t.log('Got address:', address);
96+
t.regex(
97+
address,
98+
new RegExp(`^${config.expectedAddressPrefix}1`),
99+
`address for ${chainName} is valid`,
100+
);
101+
102+
const latestWalletUpdate = await vstorageClient.queryData(
103+
`published.wallet.${wallets[wallet]}`,
104+
);
105+
t.log('latest wallet update', latestWalletUpdate);
106+
t.like(
107+
latestWalletUpdate.status,
108+
{
109+
id: offerId,
110+
numWantsSatisfied: 1,
111+
result: 'UNPUBLISHED',
112+
error: undefined,
113+
},
114+
'wallet offer satisfied without errors',
115+
);
116+
},
117+
});
118+
119+
test.serial(makeAccountScenario, 'agoric');
120+
test.serial(makeAccountScenario, 'cosmoshub');
121+
test.serial(makeAccountScenario, 'osmosis');

multichain-testing/test/stake-ica.test.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
114114
const queryClient = makeQueryClient(getRestEndpoint());
115115

116116
t.log('Requesting faucet funds');
117-
// XXX fails intermitently until https://github.com/cosmology-tech/starship/issues/417
117+
// XXX fails intermittently until https://github.com/cosmology-tech/starship/issues/417
118118
await creditFromFaucet(address);
119119

120120
const { balances } = await retryUntilCondition(
@@ -136,7 +136,7 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
136136
t.truthy(validatorAddress, 'found a validator to delegate to');
137137
t.log({ validatorAddress }, 'found a validator to delegate to');
138138
const validatorChainAddress = {
139-
address: validatorAddress,
139+
value: validatorAddress,
140140
chainId: scenario.chainId,
141141
encoding: 'bech32',
142142
};
@@ -155,6 +155,19 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
155155
});
156156
t.true(_delegateOfferResult, 'delegate payouts (none) returned');
157157

158+
const latestWalletUpdate = await vstorageClient.queryData(
159+
`published.wallet.${wallets[scenario.wallet]}`,
160+
);
161+
t.log('latest wallet update', latestWalletUpdate);
162+
t.like(
163+
latestWalletUpdate.status,
164+
{
165+
id: delegateOfferId,
166+
error: undefined,
167+
numWantsSatisfied: 1,
168+
},
169+
`${scenario.chain} delegate offer satisfied without errors`,
170+
);
158171
// query remote chain to verify delegations
159172
const { delegation_responses } = await retryUntilCondition(
160173
() => queryClient.queryDelegations(address),

multichain-testing/test/support.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ import { makeDeployBuilder } from '../tools/deploy.js';
1212

1313
const setupRegistry = makeSetupRegistry(makeGetFile({ dirname, join }));
1414

15+
export const chainConfig = {
16+
cosmoshub: {
17+
chainId: 'gaialocal',
18+
denom: 'uatom',
19+
expectedAddressPrefix: 'cosmos',
20+
},
21+
osmosis: {
22+
chainId: 'osmosislocal',
23+
denom: 'uosmo',
24+
expectedAddressPrefix: 'osmo',
25+
},
26+
agoric: {
27+
chainId: 'agoriclocal',
28+
denom: 'ubld',
29+
expectedAddressPrefix: 'agoric',
30+
},
31+
};
32+
33+
export const chainNames = Object.keys(chainConfig);
34+
1535
const makeKeyring = async (
1636
e2eTools: Pick<E2ETools, 'addKey' | 'deleteKey'>,
1737
) => {

multichain-testing/tools/agd-tools.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const makeAgdTools = async (
88
execFileSync,
99
}: Pick<typeof import('child_process'), 'execFile' | 'execFileSync'>,
1010
) => {
11-
const bundleCache = unsafeMakeBundleCache('bundles');
11+
const bundleCache = await unsafeMakeBundleCache('bundles');
1212
const tools = await makeE2ETools(log, bundleCache, {
1313
execFileSync,
1414
execFile,

multichain-testing/yarn.lock

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -671,13 +671,6 @@ __metadata:
671671
languageName: node
672672
linkType: hard
673673

674-
"@pkgr/core@npm:^0.1.0":
675-
version: 0.1.1
676-
resolution: "@pkgr/core@npm:0.1.1"
677-
checksum: 10c0/3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8
678-
languageName: node
679-
linkType: hard
680-
681674
"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2":
682675
version: 1.1.2
683676
resolution: "@protobufjs/aspromise@npm:1.1.2"
@@ -1938,26 +1931,6 @@ __metadata:
19381931
languageName: node
19391932
linkType: hard
19401933

1941-
"eslint-plugin-prettier@npm:^5.1.3":
1942-
version: 5.1.3
1943-
resolution: "eslint-plugin-prettier@npm:5.1.3"
1944-
dependencies:
1945-
prettier-linter-helpers: "npm:^1.0.0"
1946-
synckit: "npm:^0.8.6"
1947-
peerDependencies:
1948-
"@types/eslint": ">=8.0.0"
1949-
eslint: ">=8.0.0"
1950-
eslint-config-prettier: "*"
1951-
prettier: ">=3.0.0"
1952-
peerDependenciesMeta:
1953-
"@types/eslint":
1954-
optional: true
1955-
eslint-config-prettier:
1956-
optional: true
1957-
checksum: 10c0/f45d5fc1fcfec6b0cf038a7a65ddd10a25df4fe3f9e1f6b7f0d5100e66f046a26a2492e69ee765dddf461b93c114cf2e1eb18d4970aafa6f385448985c136e09
1958-
languageName: node
1959-
linkType: hard
1960-
19611934
"eslint-scope@npm:^7.2.2":
19621935
version: 7.2.2
19631936
resolution: "eslint-scope@npm:7.2.2"
@@ -2126,7 +2099,7 @@ __metadata:
21262099
languageName: node
21272100
linkType: hard
21282101

2129-
"fast-diff@npm:^1.1.2, fast-diff@npm:^1.2.0":
2102+
"fast-diff@npm:^1.2.0":
21302103
version: 1.3.0
21312104
resolution: "fast-diff@npm:1.3.0"
21322105
checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29
@@ -3653,24 +3626,6 @@ __metadata:
36533626
languageName: node
36543627
linkType: hard
36553628

3656-
"prettier-linter-helpers@npm:^1.0.0":
3657-
version: 1.0.0
3658-
resolution: "prettier-linter-helpers@npm:1.0.0"
3659-
dependencies:
3660-
fast-diff: "npm:^1.1.2"
3661-
checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab
3662-
languageName: node
3663-
linkType: hard
3664-
3665-
"prettier@npm:^3.2.4":
3666-
version: 3.3.1
3667-
resolution: "prettier@npm:3.3.1"
3668-
bin:
3669-
prettier: bin/prettier.cjs
3670-
checksum: 10c0/c25a709c9f0be670dc6bcb190b622347e1dbeb6c3e7df8b0711724cb64d8647c60b839937a4df4df18e9cfb556c2b08ca9d24d9645eb5488a7fc032a2c4d5cb3
3671-
languageName: node
3672-
linkType: hard
3673-
36743629
"pretty-ms@npm:^9.0.0":
36753630
version: 9.0.0
36763631
resolution: "pretty-ms@npm:9.0.0"
@@ -3885,11 +3840,9 @@ __metadata:
38853840
ava: "npm:^6.1.3"
38863841
eslint: "npm:^8.56.0"
38873842
eslint-config-prettier: "npm:^9.1.0"
3888-
eslint-plugin-prettier: "npm:^5.1.3"
38893843
execa: "npm:^9.2.0"
38903844
fs-extra: "npm:^11.2.0"
38913845
patch-package: "npm:^8.0.0"
3892-
prettier: "npm:^3.2.4"
38933846
starshipjs: "npm:2.0.0"
38943847
tsimp: "npm:^2.0.10"
38953848
tsx: "npm:^4.15.6"
@@ -4241,16 +4194,6 @@ __metadata:
42414194
languageName: node
42424195
linkType: hard
42434196

4244-
"synckit@npm:^0.8.6":
4245-
version: 0.8.8
4246-
resolution: "synckit@npm:0.8.8"
4247-
dependencies:
4248-
"@pkgr/core": "npm:^0.1.0"
4249-
tslib: "npm:^2.6.2"
4250-
checksum: 10c0/c3d3aa8e284f3f84f2f868b960c9f49239b364e35f6d20825a448449a3e9c8f49fe36cdd5196b30615682f007830d46f2ea354003954c7336723cb821e4b6519
4251-
languageName: node
4252-
linkType: hard
4253-
42544197
"tar@npm:^6.1.11, tar@npm:^6.1.2":
42554198
version: 6.2.1
42564199
resolution: "tar@npm:6.2.1"
@@ -4341,13 +4284,6 @@ __metadata:
43414284
languageName: node
43424285
linkType: hard
43434286

4344-
"tslib@npm:^2.6.2":
4345-
version: 2.6.3
4346-
resolution: "tslib@npm:2.6.3"
4347-
checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a
4348-
languageName: node
4349-
linkType: hard
4350-
43514287
"tsx@npm:^4.15.6":
43524288
version: 4.15.6
43534289
resolution: "tsx@npm:4.15.6"

packages/boot/test/bootstrapTests/orchestration.test.ts

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,3 +227,68 @@ test.serial('revise chain info', async t => {
227227
client_id: '07-tendermint-3',
228228
});
229229
});
230+
231+
test('basic-flows', async t => {
232+
const { buildProposal, evalProposal, agoricNamesRemotes, readLatest } =
233+
t.context;
234+
235+
await evalProposal(
236+
buildProposal('@agoric/builders/scripts/orchestration/init-basic-flows.js'),
237+
);
238+
239+
const wd =
240+
await t.context.walletFactoryDriver.provideSmartWallet('agoric1test');
241+
242+
// create a cosmos orchestration account
243+
await wd.executeOffer({
244+
id: 'request-coa',
245+
invitationSpec: {
246+
source: 'agoricContract',
247+
instancePath: ['basicFlows'],
248+
callPipe: [['makeOrchAccountInvitation']],
249+
},
250+
offerArgs: {
251+
chainName: 'cosmoshub',
252+
},
253+
proposal: {},
254+
});
255+
t.like(wd.getCurrentWalletRecord(), {
256+
offerToPublicSubscriberPaths: [
257+
[
258+
'request-coa',
259+
{
260+
account: 'published.basicFlows.cosmos1test',
261+
},
262+
],
263+
],
264+
});
265+
t.like(wd.getLatestUpdateRecord(), {
266+
status: { id: 'request-coa', numWantsSatisfied: 1 },
267+
});
268+
t.is(readLatest('published.basicFlows.cosmos1test'), '');
269+
270+
// create a local orchestration account
271+
await wd.executeOffer({
272+
id: 'request-loa',
273+
invitationSpec: {
274+
source: 'agoricContract',
275+
instancePath: ['basicFlows'],
276+
callPipe: [['makeOrchAccountInvitation']],
277+
},
278+
offerArgs: {
279+
chainName: 'agoric',
280+
},
281+
proposal: {},
282+
});
283+
284+
const publicSubscriberPaths = Object.fromEntries(
285+
wd.getCurrentWalletRecord().offerToPublicSubscriberPaths,
286+
);
287+
t.deepEqual(publicSubscriberPaths['request-loa'], {
288+
account: 'published.basicFlows.agoric1mockVlocalchainAddress',
289+
});
290+
t.like(wd.getLatestUpdateRecord(), {
291+
status: { id: 'request-loa', numWantsSatisfied: 1 },
292+
});
293+
t.is(readLatest('published.basicFlows.agoric1mockVlocalchainAddress'), '');
294+
});

packages/builders/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
"@agoric/deploy-script-support": "^0.10.3",
4646
"@agoric/governance": "^0.10.3",
4747
"@agoric/inter-protocol": "^0.16.1",
48+
"@agoric/orchestration": "^0.1.0",
4849
"@agoric/store": "^0.9.2",
4950
"@agoric/swing-store": "^0.9.1",
5051
"@agoric/swingset-liveslots": "^0.10.2",

0 commit comments

Comments
 (0)