Skip to content

Commit cb1eea1

Browse files
authored
feat: allow users to pass offerArgs with their offer (#3578)
* feat: allow users to pass offerArgs with their offer
1 parent 9808cfe commit cb1eea1

File tree

10 files changed

+191
-11
lines changed

10 files changed

+191
-11
lines changed

packages/zoe/src/contractFacet/types.js

+1
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@
202202
/**
203203
* @callback OfferHandler
204204
* @param {ZCFSeat} seat
205+
* @param {Object=} offerArgs
205206
* @returns {any}
206207
*/
207208

packages/zoe/src/contractFacet/zcfZygote.js

+11-9
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,17 @@ export const makeZCFZygote = (
264264
handleOffer: (invitationHandle, zoeSeatAdmin, seatData) => {
265265
const zcfSeat = makeZCFSeat(zoeSeatAdmin, seatData);
266266
const offerHandler = takeOfferHandler(invitationHandle);
267-
const offerResultP = E(offerHandler)(zcfSeat).catch(reason => {
268-
if (reason === undefined) {
269-
const newErr = new Error(
270-
`If an offerHandler throws, it must provide a reason of type Error, but the reason was undefined. Please fix the contract code to specify a reason for throwing.`,
271-
);
272-
throw zcfSeat.fail(newErr);
273-
}
274-
throw zcfSeat.fail(reason);
275-
});
267+
const offerResultP = E(offerHandler)(zcfSeat, seatData.offerArgs).catch(
268+
reason => {
269+
if (reason === undefined) {
270+
const newErr = new Error(
271+
`If an offerHandler throws, it must provide a reason of type Error, but the reason was undefined. Please fix the contract code to specify a reason for throwing.`,
272+
);
273+
throw zcfSeat.fail(newErr);
274+
}
275+
throw zcfSeat.fail(reason);
276+
},
277+
);
276278
const exitObj = makeExitObj(seatData.proposal, zcfSeat);
277279
/** @type {HandleOfferResult} */
278280
return harden({ offerResultP, exitObj });

packages/zoe/src/internal-types.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
* @property {Notifier<Allocation>} notifier
1515
* @property {Allocation} initialAllocation
1616
* @property {SeatHandle} seatHandle
17+
* @property {Object=} offerArgs
1718
*/
1819

1920
/**
@@ -75,7 +76,9 @@
7576
* @property {() => void} assertAcceptingOffers
7677
* @property {(invitationHandle: InvitationHandle,
7778
* initialAllocation: Allocation,
78-
* proposal: ProposalRecord) => UserSeat } makeUserSeat
79+
* proposal: ProposalRecord,
80+
* offerArgs: Object=,
81+
* ) => UserSeat } makeUserSeat
7982
* @property {MakeNoEscrowSeat} makeNoEscrowSeat
8083
* @property {() => Instance} getInstance
8184
* @property {() => Object} getPublicFacet

packages/zoe/src/zoeService/offer/offer.js

+17
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// @ts-check
2+
import { passStyleOf } from '@agoric/marshal';
23

34
import { cleanProposal } from '../../cleanProposal';
45
import { burnInvitation } from './burnInvitation';
@@ -8,6 +9,8 @@ import '@agoric/store/exported';
89
import '../../../exported';
910
import '../internal-types';
1011

12+
const { details: X, quote: q } = assert;
13+
1114
/**
1215
* @param {Issuer} invitationIssuer
1316
* @param {GetInstanceAdmin} getInstanceAdmin
@@ -26,16 +29,29 @@ export const makeOffer = (
2629
invitation,
2730
uncleanProposal = harden({}),
2831
paymentKeywordRecord = harden({}),
32+
offerArgs = undefined,
2933
) => {
3034
const { instanceHandle, invitationHandle } = await burnInvitation(
3135
invitationIssuer,
3236
invitation,
3337
);
3438
// AWAIT ///
39+
3540
const instanceAdmin = getInstanceAdmin(instanceHandle);
3641
instanceAdmin.assertAcceptingOffers();
3742

3843
const proposal = cleanProposal(uncleanProposal, getAssetKindByBrand);
44+
45+
if (offerArgs !== undefined) {
46+
const passStyle = passStyleOf(offerArgs);
47+
assert(
48+
passStyle === 'copyRecord',
49+
X`offerArgs must be a pass-by-copy record, but instead was a ${q(
50+
passStyle,
51+
)}: ${offerArgs}`,
52+
);
53+
}
54+
3955
const initialAllocation = await depositPayments(
4056
proposal,
4157
paymentKeywordRecord,
@@ -47,6 +63,7 @@ export const makeOffer = (
4763
invitationHandle,
4864
initialAllocation,
4965
proposal,
66+
offerArgs,
5067
);
5168
// AWAIT ///
5269
return userSeat;

packages/zoe/src/zoeService/startInstance.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,12 @@ export const makeStartInstance = (
9292
zoeSeatAdmins.forEach(zoeSeatAdmin => zoeSeatAdmin.fail(reason));
9393
},
9494
stopAcceptingOffers: () => (acceptingOffers = false),
95-
makeUserSeat: (invitationHandle, initialAllocation, proposal) => {
95+
makeUserSeat: (
96+
invitationHandle,
97+
initialAllocation,
98+
proposal,
99+
offerArgs = undefined,
100+
) => {
96101
const offerResultPromiseKit = makePromiseKit();
97102
handlePKitWarning(offerResultPromiseKit);
98103
const exitObjPromiseKit = makePromiseKit();
@@ -116,6 +121,7 @@ export const makeStartInstance = (
116121
initialAllocation,
117122
notifier,
118123
seatHandle,
124+
offerArgs,
119125
});
120126

121127
zoeSeatAdmins.add(zoeSeatAdmin);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @ts-check
2+
3+
/** @type {ContractStartFn} */
4+
const start = zcf => {
5+
/** @type {OfferHandler} */
6+
const handler = (_seat, offerArgs) => {
7+
assert.typeof(offerArgs.myArg, 'string');
8+
return offerArgs.myArg;
9+
};
10+
const creatorInvitation = zcf.makeInvitation(handler, 'creatorInvitation');
11+
return harden({ creatorInvitation });
12+
};
13+
harden(start);
14+
export { start };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { E } from '@agoric/eventual-send';
2+
import { Far } from '@agoric/marshal';
3+
4+
export function buildRootObject(vatPowers, vatParameters) {
5+
const { contractBundles: cb } = vatParameters;
6+
return Far('root', {
7+
async bootstrap(vats, devices) {
8+
const vatAdminSvc = await E(vats.vatAdmin).createVatAdminService(
9+
devices.vatAdmin,
10+
);
11+
const zoe = await E(vats.zoe).buildZoe(vatAdminSvc);
12+
const installations = {
13+
offerArgsUsageContract: await E(zoe).install(cb.offerArgsUsageContract),
14+
};
15+
16+
const aliceP = E(vats.alice).build(zoe, installations);
17+
await E(aliceP).offerArgsUsageTest();
18+
},
19+
});
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/* global __dirname */
2+
3+
// TODO Remove babel-standalone preinitialization
4+
// https://github.com/endojs/endo/issues/768
5+
import '@agoric/babel-standalone';
6+
// eslint-disable-next-line import/no-extraneous-dependencies
7+
import '@agoric/install-ses';
8+
// eslint-disable-next-line import/no-extraneous-dependencies
9+
import test from 'ava';
10+
// eslint-disable-next-line import/no-extraneous-dependencies
11+
import { buildVatController, buildKernelBundles } from '@agoric/swingset-vat';
12+
import bundleSource from '@agoric/bundle-source';
13+
14+
const CONTRACT_FILES = ['offerArgsUsageContract'];
15+
16+
test.before(async t => {
17+
const start = Date.now();
18+
const kernelBundles = await buildKernelBundles();
19+
const step2 = Date.now();
20+
const contractBundles = {};
21+
await Promise.all(
22+
CONTRACT_FILES.map(async settings => {
23+
let bundleName;
24+
let contractPath;
25+
if (typeof settings === 'string') {
26+
bundleName = settings;
27+
contractPath = settings;
28+
} else {
29+
({ bundleName, contractPath } = settings);
30+
}
31+
const source = `${__dirname}/../../${contractPath}`;
32+
const bundle = await bundleSource(source);
33+
contractBundles[bundleName] = bundle;
34+
}),
35+
);
36+
const step3 = Date.now();
37+
38+
const vats = {};
39+
await Promise.all(
40+
['alice', 'zoe'].map(async name => {
41+
const source = `${__dirname}/vat-${name}.js`;
42+
const bundle = await bundleSource(source);
43+
vats[name] = { bundle };
44+
}),
45+
);
46+
const bootstrapSource = `${__dirname}/bootstrap.js`;
47+
vats.bootstrap = {
48+
bundle: await bundleSource(bootstrapSource),
49+
parameters: { contractBundles }, // argv will be added to this
50+
};
51+
const config = { bootstrap: 'bootstrap', vats };
52+
config.defaultManagerType = 'xs-worker';
53+
54+
const step4 = Date.now();
55+
const ktime = `${(step2 - start) / 1000}s kernel`;
56+
const ctime = `${(step3 - step2) / 1000}s contracts`;
57+
const vtime = `${(step4 - step3) / 1000}s vats`;
58+
const ttime = `${(step4 - start) / 1000}s total`;
59+
console.log(`bundling: ${ktime}, ${ctime}, ${vtime}, ${ttime}`);
60+
61+
t.context.data = { kernelBundles, config };
62+
});
63+
64+
async function main(t, argv) {
65+
const { kernelBundles, config } = t.context.data;
66+
const controller = await buildVatController(config, argv, { kernelBundles });
67+
await controller.run();
68+
return controller.dump();
69+
}
70+
71+
const expected = ['offerArgs.myArg was accessed in the contract'];
72+
73+
test.serial('private args usage', async t => {
74+
const dump = await main(t);
75+
t.deepEqual(dump.log, expected);
76+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { E } from '@agoric/eventual-send';
2+
import { Far } from '@agoric/marshal';
3+
4+
const build = async (log, zoe, installations) => {
5+
return Far('build', {
6+
offerArgsUsageTest: async () => {
7+
const { creatorInvitation } = await E(zoe).startInstance(
8+
installations.offerArgsUsageContract,
9+
);
10+
11+
const offerArgs = harden({
12+
myArg: 'offerArgs.myArg was accessed in the contract',
13+
});
14+
15+
const userSeat = await E(zoe).offer(
16+
creatorInvitation,
17+
undefined,
18+
undefined,
19+
offerArgs,
20+
);
21+
const offerResult = await E(userSeat).getOfferResult();
22+
log(offerResult);
23+
},
24+
});
25+
};
26+
27+
export function buildRootObject(vatPowers) {
28+
return Far('root', {
29+
build: (...args) => build(vatPowers.testLog, ...args),
30+
});
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Far } from '@agoric/marshal';
2+
3+
// noinspection ES6PreferShortImport
4+
import { makeZoe } from '../../../src/zoeService/zoe';
5+
6+
export function buildRootObject(_vatPowers) {
7+
return Far('root', {
8+
buildZoe: vatAdminSvc => makeZoe(vatAdminSvc),
9+
});
10+
}

0 commit comments

Comments
 (0)