Skip to content

Commit

Permalink
test(e2e): sendAnywhere contract (#9898)
Browse files Browse the repository at this point in the history
refs: #9193
closes: #9901

## Description
Adds an e2e test for `sendAnywhere.contract.js`, ensuring `zoeTools.localTransfer()` and `localOrchestrationAccount.transfer()` are working as expected.

Fixes a bug in `zoeTools.transfer()` where a vow chain was broken (we used `Promise.all()` on an array of vows instead of `vowTools.allVows()`.

Also satisfies this requirement in #9193:
- As new user of an orchestration contract, I need to “deposit” (move) my fungible funds into a place controlled by orchestration so it can do things with it.
 - for assets available through vbank starting on Agoric in a smart-wallet

### Security Considerations
n/a

### Scaling Considerations
n/a

### Documentation Considerations
n/a

### Testing Considerations
Includes tests for paths where bugs were discovered.

### Upgrade Considerations
n/a
  • Loading branch information
mergify[bot] authored Aug 16, 2024
2 parents 7f12e80 + c3c11be commit e6c4837
Show file tree
Hide file tree
Showing 12 changed files with 169 additions and 29 deletions.
2 changes: 1 addition & 1 deletion a3p-integration/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"private": true,
"agoricSyntheticChain": {
"fromTag": "latest"
"fromTag": "use-upgrade-15"
},
"scripts": {
"build": "yarn run build:sdk && yarn run build:submissions && yarn run build:synthetic-chain",
Expand Down
5 changes: 2 additions & 3 deletions multichain-testing/test/auto-stake-it.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ test.before(async t => {

t.log('bundle and install contract', contractName);
await t.context.deployBuilder(contractBuilder);
const vstorageClient = t.context.makeQueryTool();
const { vstorageClient } = t.context;
await t.context.retryUntilCondition(
() => vstorageClient.queryData(`published.agoricNames.instance`),
res => contractName in Object.fromEntries(res),
Expand Down Expand Up @@ -91,7 +91,7 @@ const autoStakeItScenario = test.macro({
exec: async (t, chainName: string) => {
const {
wallets,
makeQueryTool,
vstorageClient,
provisionSmartWallet,
retryUntilCondition,
} = t.context;
Expand Down Expand Up @@ -174,7 +174,6 @@ const autoStakeItScenario = test.macro({
});

// FIXME https://github.com/Agoric/agoric-sdk/issues/9643
const vstorageClient = makeQueryTool();
const currentWalletRecord = await retryUntilCondition(
() =>
vstorageClient.queryData(`published.wallet.${agoricUserAddr}.current`),
Expand Down
6 changes: 2 additions & 4 deletions multichain-testing/test/basic-flows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ test.before(async t => {

t.log('bundle and install contract', contractName);
await t.context.deployBuilder(contractBuilder);
const vstorageClient = t.context.makeQueryTool();
const { vstorageClient } = t.context;
await t.context.retryUntilCondition(
() => vstorageClient.queryData(`published.agoricNames.instance`),
res => contractName in Object.fromEntries(res),
Expand All @@ -45,12 +45,10 @@ const makeAccountScenario = test.macro({
const {
wallets,
provisionSmartWallet,
makeQueryTool,
vstorageClient,
retryUntilCondition,
} = t.context;

const vstorageClient = makeQueryTool();

const agoricAddr = wallets[chainName];
const wdUser1 = await provisionSmartWallet(agoricAddr, {
BLD: 100n,
Expand Down
137 changes: 137 additions & 0 deletions multichain-testing/test/send-anywhere.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import anyTest from '@endo/ses-ava/prepare-endo.js';
import type { TestFn } from 'ava';
import { makeDoOffer } from '../tools/e2e-tools.js';
import {
commonSetup,
SetupContextWithWallets,
chainConfig,
} from './support.js';
import { createWallet } from '../tools/wallet.js';
import { AmountMath } from '@agoric/ertp';
import { makeQueryClient } from '../tools/query.js';
import type { Amount } from '@agoric/ertp/src/types.js';

const test = anyTest as TestFn<SetupContextWithWallets>;

const accounts = ['osmosis1', 'osmosis2', 'cosmoshub1', 'cosmoshub2'];

const contractName = 'sendAnywhere';
const contractBuilder =
'../packages/builders/scripts/testing/start-sendAnywhere.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;
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 sendAnywhereScenario = test.macro({
title: (_, chainName: string, acctIdx: number) =>
`sendAnywhere ${chainName}${acctIdx}`,
exec: async (t, chainName: string, acctIdx: number) => {
const config = chainConfig[chainName];
if (!config) return t.fail(`Unknown chain: ${chainName}`);

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

t.log('Create a receiving wallet for the sendAnywhere transfer');
const chain = useChain(chainName).chain;

t.log('Create an agoric smart wallet to initiate sendAnywhere transfer');
const agoricAddr = wallets[`${chainName}${acctIdx}`];
const wdUser1 = await provisionSmartWallet(agoricAddr, {
BLD: 100_000n,
IST: 100_000n,
});
t.log(`provisioning agoric smart wallet for ${agoricAddr}`);

const doOffer = makeDoOffer(wdUser1);

const brands = await vstorageClient.queryData(
'published.agoricNames.brand',
);
const istBrand = Object.fromEntries(brands).IST;

const apiUrl = await useChain(chainName).getRestEndpoint();
const queryClient = makeQueryClient(apiUrl);
t.log(`Made ${chainName} query client`);

const doSendAnywhere = async (amount: Amount) => {
t.log(`Sending ${amount.value} ${amount.brand}.`);
const wallet = await createWallet(chain.bech32_prefix);
const receiver = {
chainId: chain.chain_id,
value: (await wallet.getAccounts())[0].address,
encoding: 'bech32',
};
t.log('Will send payment to:', receiver);
t.log(`${chainName} offer`);
const offerId = `${chainName}-makeSendInvitation-${Date.now()}`;
await doOffer({
id: offerId,
invitationSpec: {
source: 'agoricContract',
instancePath: [contractName],
callPipe: [['makeSendInvitation']],
},
offerArgs: { destAddr: receiver.value, chainName },
proposal: { give: { Send: amount } },
});

const { balances } = await retryUntilCondition(
() => queryClient.queryBalances(receiver.value),
({ balances }) => 'amount' in balances[0],
`${receiver.value} ${amount.value} balance available from sendAnywhere`,
);

t.log(`${receiver.value} Balances`, balances);
t.like(balances, [
{
// XXX consider verifying uist hash
amount: String(amount.value),
},
]);
};

const makeRandomValue = (min: number, max: number) =>
BigInt(Math.floor(Math.random() * (max - min + 1)) + min);
// send 3 offers from each account. different values help distinguish
// one offer/result from another.
const offerAmounts = [
makeRandomValue(1, 33),
makeRandomValue(34, 66),
makeRandomValue(67, 100),
];
console.log(`${agoricAddr} offer amounts:`, offerAmounts);

for (const value of offerAmounts) {
await doSendAnywhere(AmountMath.make(istBrand, value));
}
},
});

test.serial(sendAnywhereScenario, 'osmosis', 1);
test.serial(sendAnywhereScenario, 'osmosis', 2);
test.serial(sendAnywhereScenario, 'cosmoshub', 1);
test.serial(sendAnywhereScenario, 'cosmoshub', 2);
4 changes: 1 addition & 3 deletions multichain-testing/test/smart-wallet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,11 @@ test.after(async t => {
});

test('provision smart wallet', async t => {
const { wallets, provisionSmartWallet, makeQueryTool, useChain } = t.context;
const { wallets, provisionSmartWallet, vstorageClient, useChain } = t.context;

const wallet = await provisionSmartWallet(wallets.user1, { BLD: 100n });
t.log('wallet', wallet);

const vstorageClient = makeQueryTool();

const walletCurrent = await vstorageClient.queryData(
`published.wallet.${wallets.user1}.current`,
);
Expand Down
3 changes: 1 addition & 2 deletions multichain-testing/test/stake-ica.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,14 @@ const stakeScenario = test.macro(async (t, scenario: StakeIcaScenario) => {
const {
wallets,
provisionSmartWallet,
makeQueryTool,
vstorageClient,
retryUntilCondition,
useChain,
deployBuilder,
} = t.context;

t.log('bundle and install contract', scenario);
await deployBuilder(scenario.builder);
const vstorageClient = makeQueryTool();
await retryUntilCondition(
() => vstorageClient.queryData(`published.agoricNames.instance`),
res => scenario.contractName in Object.fromEntries(res),
Expand Down
15 changes: 11 additions & 4 deletions multichain-testing/tools/e2e-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,9 @@ export const provisionSmartWallet = async (
chainId = 'agoriclocal',
whale = 'faucet',
progress = console.log,
q = makeQueryKit(makeVStorage(lcd)).query,
},
) => {
const { query: q } = makeQueryKit(makeVStorage(lcd));

// TODO: skip this query if balances is {}
const vbankEntries = await q.queryData('published.agoricNames.vbankAsset');
const byName = Object.fromEntries(
Expand Down Expand Up @@ -507,16 +506,24 @@ export const makeE2ETools = async (

const copyFiles = makeCopyFiles({ execFileSync, log });

const vstorageClient = makeQueryKit(vstorage).query;

return {
makeQueryTool: () => makeQueryKit(vstorage).query,
vstorageClient,
installBundles,
runCoreEval: buildAndRunCoreEval,
/**
* @param {string} address
* @param {Record<string, bigint>} amount
*/
provisionSmartWallet: (address, amount) =>
provisionSmartWallet(address, amount, { agd, blockTool, lcd, delay }),
provisionSmartWallet(address, amount, {
agd,
blockTool,
lcd,
delay,
q: vstorageClient,
}),
/**
* @param {string} name
* @param {EnglishMnemonic | string} mnemonic
Expand Down
7 changes: 7 additions & 0 deletions packages/builders/scripts/testing/start-sendAnywhere.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ export const startSendAnywhere = async ({
// @ts-expect-error unknown instance
produce: { sendAnywhere: produceInstance },
},
issuer: {
consume: { IST },
},
}) => {
trace(startSendAnywhere.name);

Expand All @@ -67,6 +70,7 @@ export const startSendAnywhere = async ({
const { instance } = await E(startUpgradable)({
label: 'sendAnywhere',
installation: sendAnywhere,
issuerKeywordRecord: { Stable: await IST },
privateArgs,
});
produceInstance.resolve(instance);
Expand Down Expand Up @@ -94,6 +98,9 @@ export const getManifest = ({ restoreRef }, { installationRef }) => {
instance: {
produce: { sendAnywhere: true },
},
issuer: {
consume: { IST: true },
},
},
},
installations: {
Expand Down
1 change: 1 addition & 0 deletions packages/orchestration/src/examples/sendAnywhere.flows.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,6 @@ export const sendIt = async (
chainId,
},
);
seat.exit();
};
harden(sendIt);
2 changes: 1 addition & 1 deletion packages/orchestration/src/exos/packet-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ export const preparePacketTools = (zone, vowTools) => {
}),
utils: M.interface('utils', {
subscribeToTransfers: M.call().returns(M.promise()),
unsubscribeFromTransfers: M.call().returns(M.promise()),
unsubscribeFromTransfers: M.call().returns(M.undefined()),
incrPendingPatterns: M.call().returns(Vow$(M.undefined())),
decrPendingPatterns: M.call().returns(Vow$(M.undefined())),
}),
Expand Down
6 changes: 4 additions & 2 deletions packages/orchestration/src/utils/zoe-tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,15 @@ export const makeZoeTools = (zone, { zcf, vowTools }) => {

// Now all the `give` are accessible, so we can move them to the localAccount`

const promises = Object.entries(give).map(async ([kw, _amount]) => {
const depositVs = Object.entries(give).map(async ([kw, _amount]) => {
const pmt = await E(userSeat).getPayout(kw);
// TODO arrange recovery on upgrade of pmt?
return localAccount.deposit(pmt);
});
await Promise.all(promises);
await vowTools.when(vowTools.allVows(depositVs));
// TODO remove userSeat from baggage
// TODO reject non-vbank issuers
// TODO recover failed deposits
},
);

Expand Down
10 changes: 1 addition & 9 deletions packages/orchestration/test/examples/swapExample.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,7 @@ const contractFile = `${dirname}/../../src/examples/swapExample.contract.js`;
type StartFn =
typeof import('@agoric/orchestration/src/examples/swapExample.contract.js').start;

/* Not sure why it is failing. Possibly relevant symptoms.
```
----- ComosOrchestrationAccountHolder.6 3 TODO: handle brand { brand: Object [Alleged: IST brand] {}, value: 10000000n }
REJECTED at top of event loop (Error#20)
Error#20: {"type":1,"data":"CmgKIy9jb3Ntb3Muc3Rha2luZy52MWJldGExLk1zZ0RlbGVnYXRlEkEKGFVOUEFSU0FCTEVfQ0hBSU5fQUREUkVTUxISYWdvcmljMXZhbG9wZXJmdWZ1GhEKBXVmbGl4EggxMDAwMDAwMA==","memo":""}
at parseTxPacket (file:///Users/markmiller/src/ongithub/agoric/agoric-sdk/packages/orchestration/src/utils/packet.js:87:14)
```
*/
test.failing('start', async t => {
test('start', async t => {
const {
bootstrap,
brands: { ist },
Expand Down

0 comments on commit e6c4837

Please sign in to comment.