Skip to content

Commit 6cd9b9a

Browse files
committed
test: add tests for patterns in makeInvitation()
correct one typeGuard improve jsdoc Also noticed that the types documented for getProposalShapeForInvitation were incorrect. fixed that with Agoric/documentation#1258
1 parent 2f3ca19 commit 6cd9b9a

File tree

6 files changed

+197
-17
lines changed

6 files changed

+197
-17
lines changed

packages/zoe/src/contracts/coveredCall.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Fail, q } from '@endo/errors';
2-
import { M, mustMatch } from '@agoric/store';
2+
33
// Eventually will be importable from '@agoric/zoe-contract-support'
44
import { swapExact } from '../contractSupport/index.js';
55
import { isAfterDeadlineExitRule } from '../typeGuards.js';
@@ -69,11 +69,6 @@ const start = zcf => {
6969

7070
/** @type {OfferHandler} */
7171
const makeOption = sellSeat => {
72-
mustMatch(
73-
sellSeat.getProposal(),
74-
M.splitRecord({ exit: { afterDeadline: M.any() } }),
75-
'exit afterDeadline',
76-
);
7772
const sellSeatExitRule = sellSeat.getProposal().exit;
7873
if (!isAfterDeadlineExitRule(sellSeatExitRule)) {
7974
throw Fail`the seller must have an afterDeadline exitRule, but instead had ${q(

packages/zoe/src/typeGuards.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ export const ZoeServiceI = M.interface('ZoeService', {
367367
}),
368368
getInvitationDetails: M.call(M.eref(InvitationShape)).returns(M.any()),
369369
getProposalShapeForInvitation: M.call(InvitationHandleShape).returns(
370-
M.opt(ProposalShape),
370+
M.opt(M.pattern()),
371371
),
372372
});
373373

packages/zoe/src/zoeService/internal-types.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,6 @@
114114
* @returns {Promise<BundleCap>}
115115
*/
116116

117-
/**
118-
* @callback GetProposalShapeForInvitation
119-
* @param {InvitationHandle} invitationHandle
120-
* @returns {Pattern | undefined}
121-
*/
122-
123117
/**
124118
* @typedef ZoeStorageManager
125119
* @property {MakeZoeInstanceStorageManager} makeZoeInstanceStorageManager
@@ -138,7 +132,7 @@
138132
* @property {GetInstallationForInstance} getInstallationForInstance
139133
* @property {GetInstanceAdmin} getInstanceAdmin
140134
* @property {UnwrapInstallation} unwrapInstallation
141-
* @property {GetProposalShapeForInvitation} getProposalShapeForInvitation
135+
* @property {(invitationHandle: InvitationHandle) => Pattern | undefined} getProposalShapeForInvitation
142136
*/
143137

144138
/**

packages/zoe/src/zoeService/types-ambient.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@
3939
* @property {GetInstance} getInstance
4040
* @property {GetInstallation} getInstallation
4141
* @property {GetInvitationDetails} getInvitationDetails
42-
* Return an object with the instance, installation, description, invitation
43-
* handle, and any custom properties specific to the contract.
42+
* Return an object with the instance, installation, description, invitation
43+
* handle, and any custom properties specific to the contract.
4444
* @property {GetFeeIssuer} getFeeIssuer
4545
* @property {GetConfiguration} getConfiguration
4646
* @property {GetBundleIDFromInstallation} getBundleIDFromInstallation
4747
* @property {(invitationHandle: InvitationHandle) => Pattern | undefined} getProposalShapeForInvitation
48+
* Return the pattern (if any) associated with the invitationHandle that a
49+
* proposal is required to match to be accepted by zoe.offer().
4850
*/
4951

5052
/**

packages/zoe/test/unitTests/contracts/coveredCall.test.js

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import path from 'path';
44

55
import bundleSource from '@endo/bundle-source';
66
import { E } from '@endo/eventual-send';
7-
import { Far } from '@endo/marshal';
7+
import { deeplyFulfilled, Far } from '@endo/marshal';
88
import { AmountMath, AssetKind } from '@agoric/ertp';
99
import { claim } from '@agoric/ertp/src/legacy-payment-helpers.js';
1010
import { keyEQ } from '@agoric/store';
@@ -1072,3 +1072,49 @@ test('zoe - coveredCall non-fungible', async t => {
10721072
t.deepEqual(bobCcPurse.getCurrentAmount().value, ['GrowlTiger']);
10731073
t.deepEqual(bobRpgPurse.getCurrentAmount().value, []);
10741074
});
1075+
1076+
test('zoe - coveredCall - bad proposal shape', async t => {
1077+
const { moolaKit, simoleanKit, moola, zoe, vatAdminState } = setup();
1078+
1079+
// Bundle and install the contract.
1080+
const bundle = await bundleSource(coveredCallRoot);
1081+
vatAdminState.installBundle('b1-coveredcall', bundle);
1082+
const coveredCallInstallation =
1083+
await E(zoe).installBundleID('b1-coveredcall');
1084+
1085+
// Start an instance.
1086+
const issuerKeywordRecord = harden({
1087+
UnderlyingAsset: moolaKit.issuer,
1088+
StrikePrice: simoleanKit.issuer,
1089+
});
1090+
const { creatorInvitation } = await E(zoe).startInstance(
1091+
coveredCallInstallation,
1092+
issuerKeywordRecord,
1093+
);
1094+
1095+
// Make an unacceptable proposal.
1096+
const badProposal = harden({
1097+
give: { UnderlyingAsset: moola(3n) },
1098+
exit: { waived: null },
1099+
});
1100+
const payments = harden({
1101+
UnderlyingAsset: moolaKit.mint.mintPayment(moola(3n)),
1102+
});
1103+
const badSeat = await E(zoe).offer(creatorInvitation, badProposal, payments);
1104+
await t.throwsAsync(
1105+
() => E(badSeat).getOfferResult(),
1106+
{
1107+
message:
1108+
/the seller must have an afterDeadline exitRule, but instead had {"waived":null}/,
1109+
},
1110+
'A bad proposal shape must be rejected',
1111+
);
1112+
1113+
// The payment must be returned.
1114+
const payouts = await deeplyFulfilled(E(badSeat).getPayouts());
1115+
t.deepEqual(payouts, payments);
1116+
t.deepEqual(
1117+
await moolaKit.issuer.getAmountOf(payouts.UnderlyingAsset),
1118+
moola(3n),
1119+
);
1120+
});
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
2+
3+
import path from 'path';
4+
5+
import { E } from '@endo/eventual-send';
6+
import bundleSource from '@endo/bundle-source';
7+
8+
import { M } from '@endo/patterns';
9+
import { AmountShape } from '@agoric/ertp';
10+
import { makeZoeForTest } from '../../../tools/setup-zoe.js';
11+
import { setup } from '../setupBasicMints.js';
12+
import { makeFakeVatAdmin } from '../../../tools/fakeVatAdmin.js';
13+
14+
const dirname = path.dirname(new URL(import.meta.url).pathname);
15+
16+
const contractRoot = `${dirname}/zcfTesterContract.js`;
17+
18+
test(`ProposalShapes mismatch`, async t => {
19+
const { moolaIssuer, simoleanIssuer, moola, moolaMint } = setup();
20+
let testJig;
21+
const setJig = jig => {
22+
testJig = jig;
23+
};
24+
const { admin: fakeVatAdminSvc, vatAdminState } = makeFakeVatAdmin(setJig);
25+
/** @type {ZoeService} */
26+
const zoe = makeZoeForTest(fakeVatAdminSvc);
27+
28+
// pack the contract
29+
const bundle = await bundleSource(contractRoot);
30+
// install the contract
31+
vatAdminState.installBundle('b1-zcftester', bundle);
32+
const installation = await E(zoe).installBundleID('b1-zcftester');
33+
34+
// Alice creates an instance
35+
const issuerKeywordRecord = harden({
36+
Pixels: moolaIssuer,
37+
Money: simoleanIssuer,
38+
});
39+
40+
await E(zoe).startInstance(installation, issuerKeywordRecord);
41+
42+
// The contract uses the testJig so the contractFacet
43+
// is available here for testing purposes
44+
/** @type {ZCF} */
45+
// @ts-expect-error cast
46+
const zcf = testJig.zcf;
47+
48+
const boring = () => {
49+
return 'ok';
50+
};
51+
52+
const proposalShape = M.splitRecord({
53+
give: { B: AmountShape },
54+
exit: { deadline: M.any() },
55+
});
56+
const invitation = await zcf.makeInvitation(
57+
boring,
58+
'seat1',
59+
{},
60+
proposalShape,
61+
);
62+
const { handle } = await E(zoe).getInvitationDetails(invitation);
63+
const shape = await E(zoe).getProposalShapeForInvitation(handle);
64+
t.deepEqual(shape, proposalShape);
65+
66+
const proposal = harden({
67+
give: { B: moola(5n) },
68+
exit: { onDemand: null },
69+
});
70+
71+
const fiveMoola = moolaMint.mintPayment(moola(5n));
72+
await t.throwsAsync(
73+
() =>
74+
E(zoe).offer(invitation, proposal, {
75+
B: fiveMoola,
76+
}),
77+
{
78+
message:
79+
'"seat1" proposal: exit: {"onDemand":null} - Must have missing properties ["deadline"]',
80+
},
81+
);
82+
t.falsy(vatAdminState.getHasExited());
83+
// The moola was not deposited.
84+
t.true(await E(moolaIssuer).isLive(fiveMoola));
85+
});
86+
87+
test(`ProposalShapes matched`, async t => {
88+
const { moolaIssuer, simoleanIssuer } = setup();
89+
let testJig;
90+
const setJig = jig => {
91+
testJig = jig;
92+
};
93+
const { admin: fakeVatAdminSvc, vatAdminState } = makeFakeVatAdmin(setJig);
94+
/** @type {ZoeService} */
95+
const zoe = makeZoeForTest(fakeVatAdminSvc);
96+
97+
// pack the contract
98+
const bundle = await bundleSource(contractRoot);
99+
// install the contract
100+
vatAdminState.installBundle('b1-zcftester', bundle);
101+
const installation = await E(zoe).installBundleID('b1-zcftester');
102+
103+
// Alice creates an instance
104+
const issuerKeywordRecord = harden({
105+
Pixels: moolaIssuer,
106+
Money: simoleanIssuer,
107+
});
108+
109+
await E(zoe).startInstance(installation, issuerKeywordRecord);
110+
111+
// The contract uses the testJig so the contractFacet
112+
// is available here for testing purposes
113+
/** @type {ZCF} */
114+
// @ts-expect-error cast
115+
const zcf = testJig.zcf;
116+
117+
const boring = () => {
118+
return 'ok';
119+
};
120+
121+
const proposalShape = M.splitRecord({ exit: { onDemand: null } });
122+
const invitation = await zcf.makeInvitation(
123+
boring,
124+
'seat',
125+
{},
126+
proposalShape,
127+
);
128+
const { handle } = await E(zoe).getInvitationDetails(invitation);
129+
const shape = await E(zoe).getProposalShapeForInvitation(handle);
130+
t.deepEqual(shape, proposalShape);
131+
132+
// onDemand is the default
133+
const seat = await E(zoe).offer(invitation);
134+
135+
const result = await E(seat).getOfferResult();
136+
t.is(result, 'ok', `userSeat1 offer result`);
137+
138+
t.falsy(await E(seat).hasExited());
139+
await E(seat).tryExit();
140+
t.true(await E(seat).hasExited());
141+
const payouts = await E(seat).getPayouts();
142+
t.deepEqual(payouts, {});
143+
});

0 commit comments

Comments
 (0)