Skip to content

Commit

Permalink
test(a3p): migrate wallet related test to z:acceptance
Browse files Browse the repository at this point in the history
  • Loading branch information
Jorge-Lopes committed Sep 18, 2024
1 parent 0c8c6c4 commit 6afaba3
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
true
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#! false node --ignore-this-line
/* global E */

This comment has been minimized.

Copy link
@dckc

dckc Sep 18, 2024

consider using the core eval eslint environment for this

IOU a pointer
cc @turadg

This comment has been minimized.

Copy link
@dckc

dckc Sep 18, 2024

This comment has been minimized.

Copy link
@Jorge-Lopes

Jorge-Lopes Sep 19, 2024

Author Collaborator

Thank you for the feedback, @dckc.

Please correct me if I'm misunderstanding, but isn't the reference declared on line 4 (/// <reference types="@agoric/vats/src/core/core-eval-env"/>) already responsible for setting up the CoreEval environment?

This comment has been minimized.

Copy link
@dckc

dckc Sep 19, 2024

Ah. yes. on review, I see that's right.


/// <reference types="@agoric/vats/src/core/core-eval-env"/>
/// <reference types="@agoric/vats/src/core/types-ambient"/>

/**
* Send a payment by looking up deposit facet via namesByAddress.
*
* see ./post.test.js
*
* @param {BootstrapPowers} powers
*/
const sendIt = async powers => {
const addr = '{{ADDRESS}}';
const {
consume: { namesByAddress, zoe },
instance: {
consume: { reserve },
},
} = powers;
const pf = E(zoe).getPublicFacet(reserve);
const anInvitation = await E(pf).makeAddCollateralInvitation();
const addressDepositFacet = E(namesByAddress).lookup(addr, 'depositFacet');
await E(addressDepositFacet).receive(anInvitation);
};

sendIt;
97 changes: 97 additions & 0 deletions a3p-integration/proposals/z:acceptance/scripts/exitOffer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Note: limit imports to node modules for portability
import { parseArgs, promisify } from 'node:util';
import { execFile } from 'node:child_process';
import { writeFile, mkdtemp, rm } from 'node:fs/promises';
import { join } from 'node:path';

const options = /** @type {const} */ ({
id: { type: 'string' },
from: { type: 'string' },
bin: { type: 'string', default: '/usr/src/agoric-sdk/node_modules/.bin' },
});

const Usage = `
Try to exit an offer, reclaiming any associated payments.
node exitOffer.js --id ID --from FROM [--bin PATH]
Options:
--id <offer id>
--from <address or key name>
--bin <path to agoric and agd> default: ${options.bin.default}
`;

const badUsage = () => {
const reason = new Error(Usage);
reason.name = 'USAGE';
throw reason;
};

const { stringify: q } = JSON;
// limited to JSON data: no remotables/promises; no undefined.
const toCapData = data => ({ body: `#${q(data)}`, slots: [] });

const { entries } = Object;
/**
* @param {Record<string, string>} obj - e.g. { color: 'blue' }
* @returns {string[]} - e.g. ['--color', 'blue']
*/
const flags = obj =>
entries(obj)
.map(([k, v]) => [`--${k}`, v])
.flat();

const execP = promisify(execFile);

const showAndRun = (file, args) => {
console.log('$', file, ...args);
return execP(file, args);
};

const withTempFile = async (tail, fn) => {
const tmpDir = await mkdtemp('offers-');
const tmpFile = join(tmpDir, tail);
try {
const result = await fn(tmpFile);
return result;
} finally {
await rm(tmpDir, { recursive: true, force: true }).catch(err =>
console.error(err),
);
}
};

const doAction = async (action, from) => {
await withTempFile('offer.json', async tmpOffer => {
await writeFile(tmpOffer, q(toCapData(action)));

const out = await showAndRun('agoric', [
'wallet',
...flags({ 'keyring-backend': 'test' }),
'send',
...flags({ offer: tmpOffer, from }),
]);
return out.stdout;
});
};

const main = async (argv, env) => {
const { values } = parseArgs({ args: argv.slice(2), options });
const { id: offerId, from, bin } = values;
(offerId && from) || badUsage();

env.PATH = `${bin}:${env.PATH}`;
const action = { method: 'tryExitOffer', offerId };
const out = await doAction(action, from);
console.log(out);
};

main(process.argv, process.env).catch(e => {
if (e.name === 'USAGE' || e.code === 'ERR_PARSE_ARGS_UNKNOWN_OPTION') {
console.error(e.message);
} else {
console.error(e);
}
process.exit(1);
});
3 changes: 3 additions & 0 deletions a3p-integration/proposals/z:acceptance/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,6 @@ yarn ava valueVow.test.js
echo ACCEPTANCE TESTING state sync
./state-sync-snapshots-test.sh
./genesis-test.sh

echo ACCEPTANCE TESTING wallet
yarn ava wallet.test.js
78 changes: 78 additions & 0 deletions a3p-integration/proposals/z:acceptance/wallet.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import test from 'ava';
import { $ } from 'execa';
import { execFileSync } from 'node:child_process';
import { readFile, writeFile } from 'node:fs/promises';
import {
makeAgd,
waitForBlock,
getUser,
evalBundles,
agoric,
} from '@agoric/synthetic-chain';

const SUBMISSION_DIR = 'invitation-test-submission';

/**
* @param {string} fileName base file name without .tjs extension
* @param {Record<string, string>} replacements
*/
const replaceTemplateValuesInFile = async (fileName, replacements) => {
let script = await readFile(`${fileName}.tjs`, 'utf-8');
for (const [template, value] of Object.entries(replacements)) {
script = script.replaceAll(`{{${template}}}`, value);
}
await writeFile(`${fileName}.js`, script);
};

test.serial(`send invitation via namesByAddress`, async t => {
const addr = await getUser('gov1');

await replaceTemplateValuesInFile(`${SUBMISSION_DIR}/send-script`, {
ADDRESS: addr,
});

await evalBundles(SUBMISSION_DIR);

await waitForBlock(2); // enough time for invitation to arrive?
const update = await agoric.follow('-lF', `:published.wallet.${addr}`);

This comment has been minimized.

Copy link
@dckc

dckc Sep 18, 2024

waitForBlock(2) is racy.

If there's no better sync mechanism, consider a "retry until it's true" loop.

https://github.com/Agoric/agoric-sdk/blob/f291362e4bc62ec31552cef29ec1a5f5cfbf7bd7/packages/internal/src/utils.js#L268

This comment has been minimized.

Copy link
@anilhelvaci

anilhelvaci Sep 24, 2024

Collaborator

Could you please give an example of how waitForBlock(N) can be racy?

This comment has been minimized.

Copy link
@dckc

dckc Sep 24, 2024

  1. submit an offer
  2. waitForBlock(3) "should be enough"
  3. check the result of the offer - it fails, because the chain got busy and it took 5 blocks for the offer to complete

there's a race between waitForBlocks(3) and the completion of the offer.

This comment has been minimized.

Copy link
@anilhelvaci

anilhelvaci Sep 24, 2024

Collaborator

Thanks!

t.is(update.updated, 'balance');
t.notDeepEqual(update.currentAmount.value, []);
t.log('balance value', update.currentAmount.value);
t.log('balance brand', update.currentAmount.brand);
// XXX agoric follow returns brands as strings
t.regex(update.currentAmount.brand, /Invitation/);
});

test.serial('exitOffer tool reclaims stuck payment', async t => {
const offerId = 'bad-invitation-15';
const from = 'gov1';

const showAndExec = (file, args, opts) => {
console.log('$', file, ...args);
return execFileSync(file, args, opts);
};

// @ts-expect-error string is not assignable to Buffer
const agd = makeAgd({ execFileSync: showAndExec }).withOpts({
keyringBackend: 'test',
});

const addr = await agd.lookup(from);
t.log(from, 'addr', addr);

const getBalance = async target => {
const { balances } = await agd.query(['bank', 'balances', addr]);
const { amount } = balances.find(({ denom }) => denom === target);
return Number(amount);
};

const before = await getBalance('uist');
t.log('uist balance before:', before);

await $`node ./scripts/exitOffer.js --id ${offerId} --from ${from}`;

await waitForBlock(2);
const after = await getBalance('uist');
t.log('uist balance after:', after);
t.true(after > before);
});

0 comments on commit 6afaba3

Please sign in to comment.