Skip to content

Commit

Permalink
test: continuing offers in orchestrate async-flows (#9679)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
mergify[bot] authored Jul 11, 2024
2 parents 0b6d5f3 + b7a9ed3 commit 12ce494
Show file tree
Hide file tree
Showing 26 changed files with 678 additions and 138 deletions.
2 changes: 1 addition & 1 deletion multichain-testing/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
.yarn/*
!.yarn/patches/*
revise-chain-info*
start-*
start*
121 changes: 121 additions & 0 deletions multichain-testing/test/basic-flows.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import anyTest from '@endo/ses-ava/prepare-endo.js';
import type { TestFn } from 'ava';
import { commonSetup, SetupContextWithWallets } from './support.js';
import { makeDoOffer } from '../tools/e2e-tools.js';
import { chainConfig, chainNames } from './support.js';

const test = anyTest as TestFn<SetupContextWithWallets>;

const accounts = ['user1', 'user2', 'user3']; // one account for each scenario

const contractName = 'basicFlows';
const contractBuilder =
'../packages/builders/scripts/orchestration/init-basic-flows.js';

test.before(async t => {
const { deleteTestKeys, setupTestKeys, ...rest } = await commonSetup(t);
deleteTestKeys(accounts).catch();
const wallets = await setupTestKeys(accounts);
t.context = { ...rest, wallets, deleteTestKeys };

t.log('bundle and install contract', contractName);
await t.context.deployBuilder(contractBuilder);
const vstorageClient = t.context.makeQueryTool();
await t.context.retryUntilCondition(
() => vstorageClient.queryData(`published.agoricNames.instance`),
res => contractName in Object.fromEntries(res),
`${contractName} instance is available`,
);
});

test.after(async t => {
const { deleteTestKeys } = t.context;
deleteTestKeys(accounts);
});

const makeAccountScenario = test.macro({
title: (_, chainName: string) => `Create account on ${chainName}`,
exec: async (t, chainName: string) => {
const config = chainConfig[chainName];
if (!config) return t.fail(`Unknown chain: ${chainName}`);

const {
wallets,
provisionSmartWallet,
makeQueryTool,
retryUntilCondition,
} = t.context;

const vstorageClient = makeQueryTool();

const wallet = accounts[chainNames.indexOf(chainName)];
const wdUser1 = await provisionSmartWallet(wallets[wallet], {
BLD: 100n,
IST: 100n,
});
t.log(`provisioning agoric smart wallet for ${wallets[wallet]}`);

const doOffer = makeDoOffer(wdUser1);
t.log(`${chainName} makeAccount offer`);
const offerId = `${chainName}-makeAccount-${Date.now()}`;

// FIXME we get payouts but not an offer result; it times out
// https://github.com/Agoric/agoric-sdk/issues/9643
// chain logs shows an UNPUBLISHED result
const _offerResult = await doOffer({
id: offerId,
invitationSpec: {
source: 'agoricContract',
instancePath: [contractName],
callPipe: [['makeOrchAccountInvitation']],
},
offerArgs: { chainName },
proposal: {},
});
t.true(_offerResult);
// t.is(await _offerResult, 'UNPUBLISHED', 'representation of continuing offer');

// TODO fix above so we don't have to poll for the offer result to be published
// https://github.com/Agoric/agoric-sdk/issues/9643
const currentWalletRecord = await retryUntilCondition(
() =>
vstorageClient.queryData(`published.wallet.${wallets[wallet]}.current`),
({ offerToPublicSubscriberPaths }) =>
Object.fromEntries(offerToPublicSubscriberPaths)[offerId],
`${offerId} continuing invitation is in vstorage`,
);

const offerToPublicSubscriberMap = Object.fromEntries(
currentWalletRecord.offerToPublicSubscriberPaths,
);

const address = offerToPublicSubscriberMap[offerId]?.account
.split('.')
.pop();
t.log('Got address:', address);
t.regex(
address,
new RegExp(`^${config.expectedAddressPrefix}1`),
`address for ${chainName} is valid`,
);

const latestWalletUpdate = await vstorageClient.queryData(
`published.wallet.${wallets[wallet]}`,
);
t.log('latest wallet update', latestWalletUpdate);
t.like(
latestWalletUpdate.status,
{
id: offerId,
numWantsSatisfied: 1,
result: 'UNPUBLISHED',
error: undefined,
},
'wallet offer satisfied without errors',
);
},
});

test.serial(makeAccountScenario, 'agoric');
test.serial(makeAccountScenario, 'cosmoshub');
test.serial(makeAccountScenario, 'osmosis');
17 changes: 15 additions & 2 deletions multichain-testing/test/stake-ica.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
const queryClient = makeQueryClient(getRestEndpoint());

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

const { balances } = await retryUntilCondition(
Expand All @@ -136,7 +136,7 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
t.truthy(validatorAddress, 'found a validator to delegate to');
t.log({ validatorAddress }, 'found a validator to delegate to');
const validatorChainAddress = {
address: validatorAddress,
value: validatorAddress,
chainId: scenario.chainId,
encoding: 'bech32',
};
Expand All @@ -155,6 +155,19 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
});
t.true(_delegateOfferResult, 'delegate payouts (none) returned');

const latestWalletUpdate = await vstorageClient.queryData(
`published.wallet.${wallets[scenario.wallet]}`,
);
t.log('latest wallet update', latestWalletUpdate);
t.like(
latestWalletUpdate.status,
{
id: delegateOfferId,
error: undefined,
numWantsSatisfied: 1,
},
`${scenario.chain} delegate offer satisfied without errors`,
);
// query remote chain to verify delegations
const { delegation_responses } = await retryUntilCondition(
() => queryClient.queryDelegations(address),
Expand Down
20 changes: 20 additions & 0 deletions multichain-testing/test/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,26 @@ import { makeDeployBuilder } from '../tools/deploy.js';

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

export const chainConfig = {
cosmoshub: {
chainId: 'gaialocal',
denom: 'uatom',
expectedAddressPrefix: 'cosmos',
},
osmosis: {
chainId: 'osmosislocal',
denom: 'uosmo',
expectedAddressPrefix: 'osmo',
},
agoric: {
chainId: 'agoriclocal',
denom: 'ubld',
expectedAddressPrefix: 'agoric',
},
};

export const chainNames = Object.keys(chainConfig);

const makeKeyring = async (
e2eTools: Pick<E2ETools, 'addKey' | 'deleteKey'>,
) => {
Expand Down
2 changes: 1 addition & 1 deletion multichain-testing/tools/agd-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const makeAgdTools = async (
execFileSync,
}: Pick<typeof import('child_process'), 'execFile' | 'execFileSync'>,
) => {
const bundleCache = unsafeMakeBundleCache('bundles');
const bundleCache = await unsafeMakeBundleCache('bundles');
const tools = await makeE2ETools(log, bundleCache, {
execFileSync,
execFile,
Expand Down
66 changes: 1 addition & 65 deletions multichain-testing/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -671,13 +671,6 @@ __metadata:
languageName: node
linkType: hard

"@pkgr/core@npm:^0.1.0":
version: 0.1.1
resolution: "@pkgr/core@npm:0.1.1"
checksum: 10c0/3f7536bc7f57320ab2cf96f8973664bef624710c403357429fbf680a5c3b4843c1dbd389bb43daa6b1f6f1f007bb082f5abcb76bb2b5dc9f421647743b71d3d8
languageName: node
linkType: hard

"@protobufjs/aspromise@npm:^1.1.1, @protobufjs/aspromise@npm:^1.1.2":
version: 1.1.2
resolution: "@protobufjs/aspromise@npm:1.1.2"
Expand Down Expand Up @@ -1938,26 +1931,6 @@ __metadata:
languageName: node
linkType: hard

"eslint-plugin-prettier@npm:^5.1.3":
version: 5.1.3
resolution: "eslint-plugin-prettier@npm:5.1.3"
dependencies:
prettier-linter-helpers: "npm:^1.0.0"
synckit: "npm:^0.8.6"
peerDependencies:
"@types/eslint": ">=8.0.0"
eslint: ">=8.0.0"
eslint-config-prettier: "*"
prettier: ">=3.0.0"
peerDependenciesMeta:
"@types/eslint":
optional: true
eslint-config-prettier:
optional: true
checksum: 10c0/f45d5fc1fcfec6b0cf038a7a65ddd10a25df4fe3f9e1f6b7f0d5100e66f046a26a2492e69ee765dddf461b93c114cf2e1eb18d4970aafa6f385448985c136e09
languageName: node
linkType: hard

"eslint-scope@npm:^7.2.2":
version: 7.2.2
resolution: "eslint-scope@npm:7.2.2"
Expand Down Expand Up @@ -2126,7 +2099,7 @@ __metadata:
languageName: node
linkType: hard

"fast-diff@npm:^1.1.2, fast-diff@npm:^1.2.0":
"fast-diff@npm:^1.2.0":
version: 1.3.0
resolution: "fast-diff@npm:1.3.0"
checksum: 10c0/5c19af237edb5d5effda008c891a18a585f74bf12953be57923f17a3a4d0979565fc64dbc73b9e20926b9d895f5b690c618cbb969af0cf022e3222471220ad29
Expand Down Expand Up @@ -3653,24 +3626,6 @@ __metadata:
languageName: node
linkType: hard

"prettier-linter-helpers@npm:^1.0.0":
version: 1.0.0
resolution: "prettier-linter-helpers@npm:1.0.0"
dependencies:
fast-diff: "npm:^1.1.2"
checksum: 10c0/81e0027d731b7b3697ccd2129470ed9913ecb111e4ec175a12f0fcfab0096516373bf0af2fef132af50cafb0a905b74ff57996d615f59512bb9ac7378fcc64ab
languageName: node
linkType: hard

"prettier@npm:^3.2.4":
version: 3.3.1
resolution: "prettier@npm:3.3.1"
bin:
prettier: bin/prettier.cjs
checksum: 10c0/c25a709c9f0be670dc6bcb190b622347e1dbeb6c3e7df8b0711724cb64d8647c60b839937a4df4df18e9cfb556c2b08ca9d24d9645eb5488a7fc032a2c4d5cb3
languageName: node
linkType: hard

"pretty-ms@npm:^9.0.0":
version: 9.0.0
resolution: "pretty-ms@npm:9.0.0"
Expand Down Expand Up @@ -3885,11 +3840,9 @@ __metadata:
ava: "npm:^6.1.3"
eslint: "npm:^8.56.0"
eslint-config-prettier: "npm:^9.1.0"
eslint-plugin-prettier: "npm:^5.1.3"
execa: "npm:^9.2.0"
fs-extra: "npm:^11.2.0"
patch-package: "npm:^8.0.0"
prettier: "npm:^3.2.4"
starshipjs: "npm:2.0.0"
tsimp: "npm:^2.0.10"
tsx: "npm:^4.15.6"
Expand Down Expand Up @@ -4241,16 +4194,6 @@ __metadata:
languageName: node
linkType: hard

"synckit@npm:^0.8.6":
version: 0.8.8
resolution: "synckit@npm:0.8.8"
dependencies:
"@pkgr/core": "npm:^0.1.0"
tslib: "npm:^2.6.2"
checksum: 10c0/c3d3aa8e284f3f84f2f868b960c9f49239b364e35f6d20825a448449a3e9c8f49fe36cdd5196b30615682f007830d46f2ea354003954c7336723cb821e4b6519
languageName: node
linkType: hard

"tar@npm:^6.1.11, tar@npm:^6.1.2":
version: 6.2.1
resolution: "tar@npm:6.2.1"
Expand Down Expand Up @@ -4341,13 +4284,6 @@ __metadata:
languageName: node
linkType: hard

"tslib@npm:^2.6.2":
version: 2.6.3
resolution: "tslib@npm:2.6.3"
checksum: 10c0/2598aef53d9dbe711af75522464b2104724d6467b26a60f2bdac8297d2b5f1f6b86a71f61717384aa8fd897240467aaa7bcc36a0700a0faf751293d1331db39a
languageName: node
linkType: hard

"tsx@npm:^4.15.6":
version: 4.15.6
resolution: "tsx@npm:4.15.6"
Expand Down
65 changes: 65 additions & 0 deletions packages/boot/test/bootstrapTests/orchestration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,68 @@ test.serial('revise chain info', async t => {
client_id: '07-tendermint-3',
});
});

test('basic-flows', async t => {
const { buildProposal, evalProposal, agoricNamesRemotes, readLatest } =
t.context;

await evalProposal(
buildProposal('@agoric/builders/scripts/orchestration/init-basic-flows.js'),
);

const wd =
await t.context.walletFactoryDriver.provideSmartWallet('agoric1test');

// create a cosmos orchestration account
await wd.executeOffer({
id: 'request-coa',
invitationSpec: {
source: 'agoricContract',
instancePath: ['basicFlows'],
callPipe: [['makeOrchAccountInvitation']],
},
offerArgs: {
chainName: 'cosmoshub',
},
proposal: {},
});
t.like(wd.getCurrentWalletRecord(), {
offerToPublicSubscriberPaths: [
[
'request-coa',
{
account: 'published.basicFlows.cosmos1test',
},
],
],
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-coa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.basicFlows.cosmos1test'), '');

// create a local orchestration account
await wd.executeOffer({
id: 'request-loa',
invitationSpec: {
source: 'agoricContract',
instancePath: ['basicFlows'],
callPipe: [['makeOrchAccountInvitation']],
},
offerArgs: {
chainName: 'agoric',
},
proposal: {},
});

const publicSubscriberPaths = Object.fromEntries(
wd.getCurrentWalletRecord().offerToPublicSubscriberPaths,
);
t.deepEqual(publicSubscriberPaths['request-loa'], {
account: 'published.basicFlows.agoric1mockVlocalchainAddress',
});
t.like(wd.getLatestUpdateRecord(), {
status: { id: 'request-loa', numWantsSatisfied: 1 },
});
t.is(readLatest('published.basicFlows.agoric1mockVlocalchainAddress'), '');
});
1 change: 1 addition & 0 deletions packages/builders/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"@agoric/deploy-script-support": "^0.10.3",
"@agoric/governance": "^0.10.3",
"@agoric/inter-protocol": "^0.16.1",
"@agoric/orchestration": "^0.1.0",
"@agoric/store": "^0.9.2",
"@agoric/swing-store": "^0.9.1",
"@agoric/swingset-liveslots": "^0.10.2",
Expand Down
Loading

0 comments on commit 12ce494

Please sign in to comment.