@agoric/swingset-liveslots 0.10.3-dev-129bb20.0 → 0.10.3-dev-4841c87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agoric/swingset-liveslots",
3
- "version": "0.10.3-dev-129bb20.0+129bb20",
3
+ "version": "0.10.3-dev-4841c87.0+4841c87",
4
4
  "description": "SwingSet ocap support layer",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -19,8 +19,8 @@
19
19
  "postpack": "git clean -f ':!src/types-index.d.ts' '*.d.ts*' '*.tsbuildinfo'"
20
20
  },
21
21
  "dependencies": {
22
- "@agoric/internal": "0.3.3-dev-129bb20.0+129bb20",
23
- "@agoric/store": "0.9.3-dev-129bb20.0+129bb20",
22
+ "@agoric/internal": "0.3.3-dev-4841c87.0+4841c87",
23
+ "@agoric/store": "0.9.3-dev-4841c87.0+4841c87",
24
24
  "@endo/env-options": "^1.1.8",
25
25
  "@endo/errors": "^1.2.10",
26
26
  "@endo/eventual-send": "^1.3.1",
@@ -34,7 +34,7 @@
34
34
  "@endo/promise-kit": "^1.1.10"
35
35
  },
36
36
  "devDependencies": {
37
- "@agoric/kmarshal": "0.1.1-dev-129bb20.0+129bb20",
37
+ "@agoric/kmarshal": "0.1.1-dev-4841c87.0+4841c87",
38
38
  "ava": "^5.3.0"
39
39
  },
40
40
  "files": [
@@ -68,5 +68,5 @@
68
68
  "typeCoverage": {
69
69
  "atLeast": 75.24
70
70
  },
71
- "gitHead": "129bb208d14fe7b03fed190c8a507ed1c7269721"
71
+ "gitHead": "4841c8738923019bcbd3e4d41900381ba677d55f"
72
72
  }
package/src/types.d.ts CHANGED
@@ -27,9 +27,14 @@ export type MeterControl = {
27
27
  unmetered: any;
28
28
  };
29
29
  export type LiveSlotsOptions = {
30
- enableDisavow?: boolean;
31
- relaxDurabilityRules?: boolean;
32
- allowStateShapeChanges?: boolean;
30
+ enableDisavow?: boolean | undefined;
31
+ relaxDurabilityRules?: boolean | undefined;
32
+ /**
33
+ * somewhat misnamed; this actually
34
+ * relates to *arbitrary* changes (per #10200, a non-true value still permits
35
+ * backwards-compatible addition of new optional fields)
36
+ */
37
+ allowStateShapeChanges?: boolean | undefined;
33
38
  };
34
39
  export type SwingSetCapData = import("@endo/marshal").CapData<string>;
35
40
  export type Message = {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["types.js"],"names":[],"mappings":"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"}
package/src/types.js CHANGED
@@ -22,11 +22,12 @@ export {};
22
22
  */
23
23
 
24
24
  /**
25
- * @typedef {{
26
- * enableDisavow?: boolean,
27
- * relaxDurabilityRules?: boolean,
28
- * allowStateShapeChanges?: boolean,
29
- * }} LiveSlotsOptions
25
+ * @typedef {object} LiveSlotsOptions
26
+ * @property {boolean} [enableDisavow]
27
+ * @property {boolean} [relaxDurabilityRules]
28
+ * @property {boolean} [allowStateShapeChanges] somewhat misnamed; this actually
29
+ * relates to *arbitrary* changes (per #10200, a non-true value still permits
30
+ * backwards-compatible addition of new optional fields)
30
31
  *
31
32
  * @typedef {import('@endo/marshal').CapData<string>} SwingSetCapData
32
33
  *
@@ -1 +1 @@
1
- {"version":3,"file":"virtualObjectManager.d.ts","sourceRoot":"","sources":["virtualObjectManager.js"],"names":[],"mappings":"AA2TO,kDApDI,GAAC,OACD,OAAO,wBAAwB,EAAE,uBAAuB,oBAExD,MAAM,MAAM,iBAEZ,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,sBAGnC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,iBACxB,GAAC,aAED,UAAU,MAAM,CAAC,eACjB,YAAY,MAAM,CAAC,sCACnB,GAAC,qBAED,OAAO,YAAY,EAAE,gBAAgB,yBACrC;IAAE,OAAO,EAAE,yBAAc,CAAC;IAAC,OAAO,EAAE,yBAAc,CAAA;CAAE;;iEAnItC,GAAG,kHAC0B,GAAG;sEAGjB,GAAG,0HAE1B,GAAG;0FAKc,GAAG,kHAEpB,GAAG;+FAIX,GAAG,0HACiB,GAAG;0BAsiCnB,MAAM,KACJ,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqI/B;mCAEa,UAAU,CAAC,OAAO,wBAAwB,CAAC;+BA/zChB,eAAe;iCAAf,eAAe;uCAFpB,4BAA4B;uCAD5B,4BAA4B"}
1
+ {"version":3,"file":"virtualObjectManager.d.ts","sourceRoot":"","sources":["virtualObjectManager.js"],"names":[],"mappings":"AAiWO,kDApDI,GAAC,OACD,OAAO,wBAAwB,EAAE,uBAAuB,oBAExD,MAAM,MAAM,iBAEZ,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,sBAGnC,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,iBACxB,GAAC,aAED,UAAU,MAAM,CAAC,eACjB,YAAY,MAAM,CAAC,sCACnB,GAAC,qBAED,OAAO,YAAY,EAAE,gBAAgB,yBACrC;IAAE,OAAO,EAAE,yBAAc,CAAC;IAAC,OAAO,EAAE,yBAAc,CAAA;CAAE;;iEA3KtB,GAAG,kHA+C5C,GA5CG;sEAIe,GAAE,0HAEb,GAAG;0FAK4C,GAAG,kHACU,GAAG;+FAKhC,GAAG,0HACgB,GAAG;0BA6lC/C,MAAM,KACJ,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqI/B;mCAEa,UAAU,CAAC,OAAO,wBAAwB,CAAC;+BAt3ChB,eAAe;iCAAf,eAAe;uCAFpB,4BAA4B;uCAD5B,4BAA4B"}
@@ -7,6 +7,7 @@ import { assertPattern, mustMatch } from '@agoric/store';
7
7
  import { defendPrototype, defendPrototypeKit } from '@endo/exo/tools.js';
8
8
  import { Far, passStyleOf } from '@endo/marshal';
9
9
  import { Nat } from '@endo/nat';
10
+ import { kindOf } from '@endo/patterns';
10
11
  import { parseVatSlot, makeBaseRef } from './parseVatSlots.js';
11
12
  import { enumerateKeysWithPrefix } from './vatstore-iterators.js';
12
13
  import { makeCache } from './cache.js';
@@ -20,6 +21,8 @@ import {
20
21
  * @import {DefineKindOptions} from '@agoric/swingset-liveslots'
21
22
  * @import {ClassContextProvider, KitContextProvider} from '@endo/exo'
22
23
  * @import {ToCapData, FromCapData} from '@endo/marshal';
24
+ * @import {Pattern} from '@endo/patterns';
25
+ * @import {SwingSetCapData} from './types.js';
23
26
  */
24
27
 
25
28
  const {
@@ -241,12 +244,47 @@ const insistDurableCapdata = (vrm, what, capdata, valueFor) => {
241
244
  }
242
245
  };
243
246
 
244
- const insistSameCapData = (oldCD, newCD) => {
245
- // NOTE: this assumes both were marshalled with the same format
246
- // (e.g. smallcaps vs pre-smallcaps). To somewhat tolerate new
247
- // formats, we'd need to `serialize(unserialize(oldCD))`.
247
+ const isUndefinedPatt = patt =>
248
+ patt === undefined ||
249
+ (kindOf(patt) === 'match:kind' && patt.payload === 'undefined');
250
+
251
+ /**
252
+ * Assert that a new stateShape either matches the old, or only differs in the
253
+ * addition of new clearly-optional top-level fields as conveyed by an
254
+ * [Endo `M.or`]{@link https://endojs.github.io/endo/interfaces/_endo_patterns.PatternMatchers.html#or}
255
+ * Pattern with at least one `undefined` alternative.
256
+ *
257
+ * @param {SwingSetCapData} oldCD
258
+ * @param {SwingSetCapData} newCD
259
+ * @param {{ newShape: Record<string, Pattern>, serialize: ToCapData<string>, unserialize: FromCapData<string> }} powers
260
+ */
261
+ const insistCompatibleShapeCapData = (
262
+ oldCD,
263
+ newCD,
264
+ { newShape, serialize, unserialize },
265
+ ) => {
248
266
  if (oldCD.body !== newCD.body) {
249
- Fail`durable Kind stateShape mismatch (body)`;
267
+ // Allow introduction of any clearly-optional new field at top level.
268
+ const oldShape =
269
+ unserialize(oldCD) ||
270
+ Fail`durable Kind stateShape mismatch (no old shape)`;
271
+ passStyleOf(oldShape) === 'copyRecord' ||
272
+ Fail`durable Kind stateShape mismatch (invalid old shape)`;
273
+ assertPattern(oldShape);
274
+ for (const [name, oldPatt] of Object.entries(oldShape)) {
275
+ // Assert presence and CapData shape, but save slots for their own clause.
276
+ (Object.hasOwn(newShape, name) &&
277
+ serialize(newShape[name]).body === serialize(oldPatt).body) ||
278
+ Fail`durable Kind stateShape mismatch (body ${name})`;
279
+ }
280
+ for (const [name, newPatt] of Object.entries(newShape)) {
281
+ if (Object.hasOwn(oldShape, name)) continue;
282
+ kindOf(newPatt) === 'match:or' ||
283
+ Fail`durable Kind stateShape mismatch (body new field ${name})`;
284
+ // @ts-expect-error A "match:or" pattern has a `payload` array of Patterns
285
+ newPatt.payload.some(patt => isUndefinedPatt(patt)) ||
286
+ Fail`durable Kind stateShape mismatch (body ${name})`;
287
+ }
250
288
  }
251
289
  if (oldCD.slots.length !== newCD.slots.length) {
252
290
  Fail`durable Kind stateShape mismatch (slots.length)`;
@@ -532,7 +570,7 @@ export const makeVirtualObjectManager = (
532
570
  * tag: string,
533
571
  * unfaceted?: boolean,
534
572
  * facets?: string[],
535
- * stateShapeCapData?: import('./types.js').SwingSetCapData
573
+ * stateShapeCapData?: SwingSetCapData
536
574
  * }} DurableKindDescriptor
537
575
  */
538
576
 
@@ -803,7 +841,11 @@ export const makeVirtualObjectManager = (
803
841
 
804
842
  const oldStateShapeSlots = oldShapeCD ? oldShapeCD.slots : [];
805
843
  if (oldShapeCD && !allowStateShapeChanges) {
806
- insistSameCapData(oldShapeCD, newShapeCD);
844
+ insistCompatibleShapeCapData(oldShapeCD, newShapeCD, {
845
+ newShape: /** @type {Record<string, Pattern>} */ (stateShape),
846
+ serialize,
847
+ unserialize,
848
+ });
807
849
  }
808
850
  const newStateShapeSlots = newShapeCD.slots;
809
851
  vrm.updateReferenceCounts(oldStateShapeSlots, newStateShapeSlots);
@@ -859,9 +901,11 @@ export const makeVirtualObjectManager = (
859
901
  const baseRef = getBaseRef(this);
860
902
  const record = dataCache.get(baseRef);
861
903
  assert(record !== undefined);
862
- const { valueMap, capdatas } = record;
904
+ const { capdatas, valueMap } = record;
863
905
  if (!valueMap.has(prop)) {
864
- const value = harden(unserialize(capdatas[prop]));
906
+ const value = hasOwn(capdatas, prop)
907
+ ? harden(unserialize(capdatas[prop]))
908
+ : undefined;
865
909
  checkStatePropertyValue(value, prop);
866
910
  valueMap.set(prop, value);
867
911
  }
@@ -877,12 +921,19 @@ export const makeVirtualObjectManager = (
877
921
  }
878
922
  const record = dataCache.get(baseRef); // mutable
879
923
  assert(record !== undefined);
880
- const oldSlots = record.capdatas[prop].slots;
924
+ const { capdatas, valueMap } = record;
925
+ const oldSlots = hasOwn(capdatas, prop) ? capdatas[prop].slots : [];
881
926
  const newSlots = capdata.slots;
882
927
  vrm.updateReferenceCounts(oldSlots, newSlots);
883
- record.capdatas[prop] = capdata; // modify in place ..
884
- record.valueMap.set(prop, value);
885
- dataCache.set(baseRef, record); // .. but mark as dirty
928
+ // modify in place, but mark as dirty
929
+ defineProperty(capdatas, prop, {
930
+ value: capdata,
931
+ writable: true,
932
+ enumerable: true,
933
+ configurable: false,
934
+ });
935
+ valueMap.set(prop, value);
936
+ dataCache.set(baseRef, record);
886
937
  },
887
938
  enumerable: true,
888
939
  configurable: false,
@@ -1073,7 +1124,12 @@ export const makeVirtualObjectManager = (
1073
1124
  }
1074
1125
  // eslint-disable-next-line github/array-foreach
1075
1126
  valueCD.slots.forEach(vrm.addReachableVref);
1076
- capdatas[prop] = valueCD;
1127
+ defineProperty(capdatas, prop, {
1128
+ value: valueCD,
1129
+ writable: true,
1130
+ enumerable: true,
1131
+ configurable: false,
1132
+ });
1077
1133
  valueMap.set(prop, value);
1078
1134
  }
1079
1135
  // dataCache contents remain mutable: state setter modifies in-place
package/test/util.d.ts CHANGED
@@ -12,7 +12,7 @@ export function buildDispatch(onDispatchCallback?: (d: unknown) => void): {
12
12
  * @param {unknown} result
13
13
  */
14
14
  export function makeMessage(target: unknown, method: string | symbol, args?: any[], result?: unknown): unknown[];
15
- export function makeStartVat(vatParameters: any): any[];
15
+ export function makeStartVat(vatParametersCapData?: import("@endo/marshal").CapData<string>): (string | import("@endo/marshal").CapData<string>)[];
16
16
  export function makeBringOutYourDead(): string[];
17
17
  export function makeResolutions(resolutions: any): any[];
18
18
  export function makeResolve(target: any, result: any): any[];
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.js"],"names":[],"mappings":"AAKA;;GAEG;AACH,mDAFW,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI;;;EAwC9B;AAED;;;;;GAKG;AACH,oCALW,OAAO,UACP,MAAM,GAAG,MAAM,SACf,GAAG,EAAE,WACL,OAAO,aAOjB;AAED,wDAEC;AAED,iDAEC;AAED,yDAGC;AAED,6DAGC;AAED,2EAIC;AAED,qEAGC;AAED,uEAGC;AAED,uEAGC;;qBAjGoB,eAAe"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.js"],"names":[],"mappings":"AAKA;;GAEG;AACH,mDAFW,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI;;;EAwC9B;AAED;;;;;GAKG;AACH,oCALW,OAAO,UACP,MAAM,GAAG,MAAM,SACf,GAAG,EAAE,WACL,OAAO,aAOjB;AAED,mJAEC;AAED,iDAEC;AAED,yDAGC;AAED,6DAGC;AAED,2EAIC;AAED,qEAGC;AAED,uEAGC;AAED,uEAGC;;qBAjGoB,eAAe"}
package/test/util.js CHANGED
@@ -59,8 +59,8 @@ export function makeMessage(target, method, args = [], result = null) {
59
59
  return vatDeliverObject;
60
60
  }
61
61
 
62
- export function makeStartVat(vatParameters) {
63
- return harden(['startVat', vatParameters]);
62
+ export function makeStartVat(vatParametersCapData = kser(undefined)) {
63
+ return harden(['startVat', vatParametersCapData]);
64
64
  }
65
65
 
66
66
  export function makeBringOutYourDead() {
@@ -1,8 +1,9 @@
1
1
  // @ts-nocheck
2
2
  import test from 'ava';
3
3
 
4
+ import { Fail } from '@endo/errors';
4
5
  import { Far } from '@endo/marshal';
5
- import { kser, kslot } from '@agoric/kmarshal';
6
+ import { kser, kslot, kunser } from '@agoric/kmarshal';
6
7
  import { M } from '@agoric/store';
7
8
  import { makeLiveSlots } from '../../src/liveslots.js';
8
9
  import { buildSyscall } from '../liveslots-helpers.js';
@@ -10,160 +11,176 @@ import { makeStartVat, makeMessage } from '../util.js';
10
11
  import { makeMockGC } from '../mock-gc.js';
11
12
  import { makeFakeVirtualStuff } from '../../tools/fakeVirtualSupport.js';
12
13
 
13
- function makeGenericRemotable(typeName) {
14
- return Far(typeName, {
15
- aMethod() {
16
- return 'whatever';
17
- },
18
- });
19
- }
20
- const eph1 = makeGenericRemotable('ephemeral1');
21
- const eph2 = makeGenericRemotable('ephemeral2');
14
+ /** @import {VatOneResolution} from '../../src/types.js'; */
15
+ /** @import {VatData} from '../../src/vatDataTypes.js'; */
16
+
17
+ let lastPnum = 100;
18
+ /**
19
+ * Send a "message" syscall into a liveslots instance and return the
20
+ * unserialized result, or an error if the result is unsettled or rejected.
21
+ *
22
+ * @param {ReturnType<typeof makeLiveSlots>} ls
23
+ * @param {ReturnType<typeof buildSyscall>['log']} syscallLog
24
+ * @param {[target: unknown, method: string | symbol, args?: unknown[]]} message
25
+ */
26
+ const dispatchForResult = async (ls, syscallLog, message) => {
27
+ const oldSyscallCount = syscallLog.length;
28
+ lastPnum += 1;
29
+ const resultVpid = `p-${lastPnum}`;
30
+ const vdo = makeMessage(...message.concat(undefined).slice(0, 3), resultVpid);
31
+ await ls.dispatch(vdo);
32
+ const newSyscalls = syscallLog.slice(oldSyscallCount);
33
+ /** @type {VatOneResolution[]} */
34
+ const newResolutions = newSyscalls.flatMap(vso =>
35
+ vso.type === 'resolve' ? vso.resolutions : [],
36
+ );
37
+ const [_vpid, isRejection, capdata] =
38
+ newResolutions.find(resolution => resolution[0] === resultVpid) ||
39
+ Fail`unsettled by syscalls ${syscallLog.slice(oldSyscallCount)}`;
40
+ if (isRejection) throw Error('rejected', { cause: kunser(capdata) });
41
+ return kunser(capdata);
42
+ };
22
43
 
23
- const init = value => ({ value });
24
- const behavior = {
25
- set: ({ state }, value) => (state.value = value),
44
+ const eph1 = Far('ephemeral1');
45
+ const eph2 = Far('ephemeral2');
46
+
47
+ const initHolder = fields => ({ ...fields });
48
+ const holderMethods = {
49
+ get: ({ state }, ...fields) =>
50
+ // We require fields to be explicit because they are currently defined on
51
+ // the state *prototype*.
52
+ Object.fromEntries(
53
+ fields.flatMap(key => (key in state ? [[key, state[key]]] : [])),
54
+ ),
55
+ set: ({ state }, fields) => {
56
+ Object.assign(state, fields);
57
+ },
26
58
  };
59
+ /**
60
+ * Define a virtual or durable kind for getting and setting state constrained
61
+ * by the provided shape.
62
+ *
63
+ * @template {VatData.defineKind | VatData.defineDurableKind} D
64
+ * @param {D} defineKind
65
+ * @param {Parameters<D>[0]} kindIdentifier
66
+ * @param {import('@endo/patterns').Pattern} stateShape
67
+ */
68
+ const defineHolder = (defineKind, kindIdentifier, stateShape) =>
69
+ defineKind(kindIdentifier, initHolder, holderMethods, { stateShape });
27
70
 
28
71
  // virtual/durable Kinds can specify a 'stateShape', which should be
29
- // enforced, both during initialization and subsequent state changes
30
-
31
- test('constrain state shape', t => {
72
+ // enforced at both initialization and subsequent state changes
73
+ const testStateShape = test.macro((t, valueShape, config) => {
74
+ const { goodValues, badValues, throwsExpectation } = config;
32
75
  const { vom } = makeFakeVirtualStuff();
33
76
  const { defineKind } = vom;
34
- const any = { value: M.any() };
35
- const number = { value: M.number() };
36
- const string = { value: M.string() };
37
- const remotable = { value: M.remotable() };
38
- const eph = { value: eph1 };
39
-
40
- // M.any() allows anything
41
- const makeA = defineKind('kindA', init, behavior, { stateShape: any });
42
- makeA(eph1);
43
- makeA(1);
44
- makeA('string');
45
- const a = makeA(1);
46
- a.set(eph1);
47
- a.set(2);
48
- a.set('other string');
49
-
50
- // M.number() requires a number
51
- const numberFail = { message: /Must be a number/ };
52
- const makeB = defineKind('kindB', init, behavior, { stateShape: number });
53
- t.throws(() => makeB(eph1), numberFail);
54
- const b = makeB(1);
55
- t.throws(() => makeB('string'), numberFail);
56
- t.throws(() => b.set(eph1), numberFail);
57
- t.throws(() => b.set('string'), numberFail);
58
-
59
- // M.string() requires a string
60
- const stringFail = { message: /Must be a string/ };
61
- const makeC = defineKind('kindC', init, behavior, { stateShape: string });
62
- t.throws(() => makeC(eph1), stringFail);
63
- const c = makeC('string');
64
- t.throws(() => makeC(1), stringFail);
65
- t.throws(() => c.set(eph1), stringFail);
66
- t.throws(() => c.set(2), stringFail);
67
-
68
- // M.remotable() requires any Remotable
69
- const remotableFail = { message: /Must be a remotable/ };
70
- const makeD = defineKind('kindD', init, behavior, { stateShape: remotable });
71
- const d = makeD(eph1);
72
- makeD(eph2);
73
- t.throws(() => makeD(1), remotableFail);
74
- t.throws(() => makeD('string'), remotableFail);
75
- d.set(eph2);
76
- t.throws(() => d.set(2), remotableFail);
77
- t.throws(() => d.set('string'), remotableFail);
78
-
79
- // using a specific Remotable object requires that exact object
80
- const eph1Fail = { message: /Must be:.*Alleged: ephemeral1/ };
81
- const makeE = defineKind('kindE', init, behavior, { stateShape: eph });
82
- const e = makeE(eph1);
83
- t.throws(() => makeE(eph2), eph1Fail);
84
- t.throws(() => makeE(1), eph1Fail);
85
- t.throws(() => makeE('string'), eph1Fail);
86
- e.set(eph1);
87
- t.throws(() => e.set(eph2), eph1Fail);
88
- t.throws(() => e.set(2), eph1Fail);
89
- t.throws(() => e.set('string'), eph1Fail);
77
+ const makeHolder = defineHolder(defineKind, 'kindTag', { value: valueShape });
78
+ const instance = goodValues.map(value => makeHolder({ value })).at(-1);
79
+ for (const value of goodValues) {
80
+ instance.set({ value });
81
+ }
82
+ for (const value of badValues || []) {
83
+ t.throws(() => makeHolder({ value }), throwsExpectation);
84
+ t.throws(() => instance.set({ value }), throwsExpectation);
85
+ }
86
+ t.pass();
87
+ });
88
+ test('constrain state shape - M.any()', testStateShape, M.any(), {
89
+ goodValues: [eph1, eph2, 1, 2, 'string'],
90
+ });
91
+ test('constrain state shape - M.number()', testStateShape, M.number(), {
92
+ goodValues: [1],
93
+ badValues: [eph1, 'string'],
94
+ throwsExpectation: { message: /Must be a number/ },
95
+ });
96
+ test('constrain state shape - M.string()', testStateShape, M.string(), {
97
+ goodValues: ['string'],
98
+ badValues: [eph1, 1, 2],
99
+ throwsExpectation: { message: /Must be a string/ },
100
+ });
101
+ test('constrain state shape - M.remotable()', testStateShape, M.remotable(), {
102
+ goodValues: [eph1, eph2],
103
+ badValues: ['string', 1, 2],
104
+ throwsExpectation: { message: /Must be a remotable/ },
105
+ });
106
+ test('constrain state shape - specific remotable', testStateShape, eph1, {
107
+ goodValues: [eph1],
108
+ badValues: ['string', 1, 2],
109
+ throwsExpectation: { message: /Must be:.*Alleged: ephemeral1/ },
90
110
  });
91
111
 
92
112
  // durable Kinds serialize and store their stateShape, which must
93
113
  // itself be durable
94
-
95
114
  test('durable state shape', t => {
96
115
  // note: relaxDurabilityRules defaults to true in fake tools
97
116
  const { vom } = makeFakeVirtualStuff({ relaxDurabilityRules: false });
98
117
  const { makeKindHandle, defineDurableKind } = vom;
99
118
 
100
- const make = (which, stateShape) => {
101
- const kh = makeKindHandle(`kind${which}`);
102
- return defineDurableKind(kh, init, behavior, { stateShape });
119
+ let kindNumber = 0;
120
+ const defineNextKind = valueShape => {
121
+ kindNumber += 1;
122
+ const kh = makeKindHandle(`kind${kindNumber}`);
123
+ return defineHolder(defineDurableKind, kh, { value: valueShape });
103
124
  };
104
125
 
105
- const makeKind1 = make(1);
106
- makeKind1();
126
+ const makeKind1 = defineNextKind();
127
+ makeKind1({ value: undefined });
107
128
 
108
- const makeKind2 = make(2);
129
+ const makeKind2 = defineNextKind();
109
130
  makeKind2();
110
131
 
111
- const makeKind3 = make(3, { value: M.any() });
132
+ const makeKind3 = defineNextKind(M.any());
112
133
  const obj3 = makeKind3();
113
134
 
114
- const makeKind4 = make(4, { value: M.string() });
115
- const obj4 = makeKind4('string');
135
+ const makeKind4 = defineNextKind(M.string());
136
+ const obj4 = makeKind4({ value: 'string' });
116
137
 
117
- const makeKind5 = make(5, { value: M.remotable() });
138
+ const makeKind5 = defineNextKind(M.remotable());
118
139
  const durableValueFail = { message: /value for "value" is not durable/ };
119
- t.throws(() => makeKind5(eph1), durableValueFail);
140
+ t.throws(() => makeKind5({ value: eph1 }), durableValueFail);
120
141
 
121
142
  const durableShapeFail = { message: /stateShape.*is not durable: slot 0 of/ };
122
- t.throws(() => make(6, { value: eph1 }), durableShapeFail);
143
+ t.throws(() => defineNextKind(eph1), durableShapeFail);
123
144
 
124
- const makeKind7 = make(7, { value: obj4 }); // obj4 is durable
125
- makeKind7(obj4);
145
+ const makeKind7 = defineNextKind(obj4); // obj4 is durable
146
+ makeKind7({ value: obj4 });
126
147
  const specificRemotableFail = { message: /kind3.*Must be:.*kind4/ };
127
- t.throws(() => makeKind7(obj3), specificRemotableFail);
148
+ t.throws(() => makeKind7({ value: obj3 }), specificRemotableFail);
128
149
  });
129
150
 
130
151
  // durable Kinds maintain refcounts on their serialized stateShape
131
-
132
152
  test('durable stateShape refcounts', async t => {
133
153
  const kvStore = new Map();
134
154
  const { syscall: sc1 } = buildSyscall({ kvStore });
135
155
  const gcTools = makeMockGC();
136
156
 
137
- function build1(vatPowers, _vp, baggage) {
138
- const { VatData } = vatPowers;
139
- const { makeKindHandle, defineDurableKind } = VatData;
140
-
141
- return Far('root', {
142
- accept: _standard1 => 0, // assign it a vref
143
- create: standard1 => {
144
- const kh = makeKindHandle('shaped');
145
- baggage.init('kh', kh);
146
- const stateShape = { value: standard1 };
147
- defineDurableKind(kh, init, behavior, { stateShape });
148
- },
149
- });
150
- }
151
-
152
- const makeNS1 = () => ({ buildRootObject: build1 });
153
- const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, makeNS1);
154
- const startVat1 = makeStartVat(kser());
155
- await ls1.dispatch(startVat1);
156
- const rootA = 'o+0';
157
-
158
- const standard1Vref = 'o-1';
159
- await ls1.dispatch(makeMessage(rootA, 'accept', []));
160
- t.falsy(ls1.testHooks.getReachableRefCount(standard1Vref));
157
+ const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, () => {
158
+ const buildRootObject = (vatPowers, _vatParameters, baggage) => {
159
+ const { VatData } = vatPowers;
160
+ const { makeKindHandle, defineDurableKind } = VatData;
161
+
162
+ return Far('root', {
163
+ acceptRef: _ref => {}, // assigns a vref
164
+ defineDurableKind: valueShape => {
165
+ const kh = makeKindHandle('shaped');
166
+ baggage.init('kh', kh);
167
+ defineHolder(defineDurableKind, kh, { value: valueShape });
168
+ },
169
+ });
170
+ };
171
+ return { buildRootObject };
172
+ });
173
+ await ls1.dispatch(makeStartVat());
174
+ const root = 'o+0';
161
175
 
162
- await ls1.dispatch(makeMessage(rootA, 'create', [kslot(standard1Vref)]));
176
+ // accepting a ref but doing nothing with it does not increment its refcount
177
+ const vref1 = 'o-1';
178
+ await ls1.dispatch(makeMessage(root, 'acceptRef', [kslot(vref1)]));
179
+ t.is(ls1.testHooks.getReachableRefCount(vref1), 0);
163
180
 
164
- // using our 'standard1' object in stateShape causes its refcount to
165
- // be incremented
166
- t.is(ls1.testHooks.getReachableRefCount(standard1Vref), 1);
181
+ // ...but using it in stateShape does
182
+ await ls1.dispatch(makeMessage(root, 'defineDurableKind', [kslot(vref1)]));
183
+ t.is(ls1.testHooks.getReachableRefCount(vref1), 1);
167
184
 
168
185
  // ------
169
186
 
@@ -171,128 +188,202 @@ test('durable stateShape refcounts', async t => {
171
188
  const clonedStore = new Map(kvStore);
172
189
  const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
173
190
 
174
- function build2(vatPowers, vatParameters, baggage) {
175
- const { VatData } = vatPowers;
176
- const { defineDurableKind } = VatData;
177
- const { standard2 } = vatParameters;
178
- const kh = baggage.get('kh');
179
- const stateShape = { value: standard2 };
180
- defineDurableKind(kh, init, behavior, { stateShape });
181
-
182
- return Far('root', {});
183
- }
184
-
185
191
  // to test refcount increment/decrement, we need to override the
186
192
  // usual rule that the new version must exactly match the original
187
193
  // stateShape
188
- const options = { allowStateShapeChanges: true };
189
- const makeNS2 = () => ({ buildRootObject: build2 });
190
- const ls2 = makeLiveSlots(
191
- sc2,
192
- 'vatA',
193
- {},
194
- options,
195
- gcTools,
196
- undefined,
197
- makeNS2,
198
- );
199
-
200
- const standard2Vref = 'o-2';
201
- const vp = { standard2: kslot(standard2Vref) };
202
- const startVat2 = makeStartVat(kser(vp));
203
- await ls2.dispatch(startVat2);
204
-
205
- // redefining the durable kind, with a different 'standard' object,
206
- // will decrement the standard1 refcount, and increment that of
207
- // standard2
194
+ const opts = { allowStateShapeChanges: true };
195
+ const ls2 = makeLiveSlots(sc2, 'vatA', {}, opts, gcTools, undefined, () => {
196
+ const buildRootObject = (vatPowers, vatParameters, baggage) => {
197
+ const { VatData } = vatPowers;
198
+ const { defineDurableKind } = VatData;
199
+ const { valueShape } = vatParameters;
200
+ const kh = baggage.get('kh');
201
+ defineHolder(defineDurableKind, kh, { value: valueShape });
202
+
203
+ return Far('root', {});
204
+ };
205
+ return { buildRootObject };
206
+ });
208
207
 
209
- t.falsy(ls2.testHooks.getReachableRefCount(standard1Vref));
210
- t.is(ls2.testHooks.getReachableRefCount(standard2Vref), 1);
208
+ // redefining the durable kind's stateShape to replace its vref1 reference
209
+ // with a vref2 reference will decrement the former refcount and increment the
210
+ // latter
211
+ const vref2 = 'o-2';
212
+ const vatParameters = { valueShape: kslot(vref2) };
213
+ await ls2.dispatch(makeStartVat(kser(vatParameters)));
214
+ t.is(ls2.testHooks.getReachableRefCount(vref1), 0);
215
+ t.is(ls2.testHooks.getReachableRefCount(vref2), 1);
211
216
  });
212
217
 
213
- test('durable stateShape must match', async t => {
214
- const kvStore = new Map();
215
- const { syscall: sc1 } = buildSyscall({ kvStore });
218
+ test('durable stateShape must match or extend', async t => {
219
+ const store1 = new Map();
220
+ const { syscall: sc1, log: log1 } = buildSyscall({ kvStore: store1 });
216
221
  const gcTools = makeMockGC();
217
222
 
218
- function build1(vatPowers, _vp, baggage) {
219
- const { VatData } = vatPowers;
220
- const { makeKindHandle, defineDurableKind } = VatData;
221
-
222
- return Far('root', {
223
- create: (obj1, obj2) => {
224
- const kh = makeKindHandle('shaped');
225
- baggage.init('kh', kh);
226
- const stateShape = { x: obj1, y: obj2 };
227
- defineDurableKind(kh, init, behavior, { stateShape });
228
- },
229
- });
230
- }
231
-
232
- const makeNS1 = () => ({ buildRootObject: build1 });
233
- const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, makeNS1);
234
- const startVat1 = makeStartVat(kser());
235
- await ls1.dispatch(startVat1);
236
- const rootA = 'o+0';
223
+ const fields = ['x', 'x2', 'y']; // but "x2" is not initially present
224
+ const ls1 = makeLiveSlots(sc1, 'vatA', {}, {}, gcTools, undefined, () => {
225
+ const buildRootObject = (vatPowers, _vatParameters, baggage) => {
226
+ const { VatData } = vatPowers;
227
+ const { makeKindHandle, defineDurableKind } = VatData;
228
+
229
+ return Far('root', {
230
+ makeShapedDurable: (x, y) => {
231
+ const kh = makeKindHandle('shaped');
232
+ baggage.init('kh', kh);
233
+ const makeInstance = defineHolder(defineDurableKind, kh, { x, y });
234
+ return makeInstance(harden({ x, y }));
235
+ },
236
+ });
237
+ };
238
+ return { buildRootObject };
239
+ });
240
+ await ls1.dispatch(makeStartVat());
241
+ const root = 'o+0';
237
242
 
238
243
  const vref1 = 'o-1';
239
244
  const vref2 = 'o-2';
240
- await ls1.dispatch(
241
- makeMessage(rootA, 'create', [kslot(vref1), kslot(vref2)]),
245
+ const passables = new Map([
246
+ [vref1, kslot(vref1, vref1)],
247
+ [vref2, kslot(vref2, vref2)],
248
+ ]);
249
+ const instance1 = await dispatchForResult(ls1, log1, [
250
+ root,
251
+ 'makeShapedDurable',
252
+ [passables.get(vref1), passables.get(vref2)],
253
+ ]);
254
+ const objRef = instance1.getKref();
255
+ const state1 = await dispatchForResult(ls1, log1, [objRef, 'get', fields]);
256
+ t.deepEqual(
257
+ kser(state1),
258
+ kser({ x: passables.get(vref1), y: passables.get(vref2) }),
242
259
  );
243
260
 
244
- // the first version's state is { x: vref1, y: vref2 }
245
-
246
261
  // ------
247
262
 
248
263
  // Simulate upgrade by starting from the non-empty kvStore.
249
- const clonedStore = new Map(kvStore);
250
- const { syscall: sc2 } = buildSyscall({ kvStore: clonedStore });
264
+ const store2 = new Map(store1);
265
+ const { syscall: sc2, log: log2 } = buildSyscall({ kvStore: store2 });
251
266
 
252
- function build2(vatPowers, vatParameters, baggage) {
253
- const { VatData } = vatPowers;
254
- const { defineDurableKind } = VatData;
255
- const { obj1, obj2 } = vatParameters;
256
- const kh = baggage.get('kh');
257
- // several shapes that are not compatible
258
- const shape1 = { x: obj1, y: M.any() };
259
- const shape2 = { x: obj1 };
260
- const shape3 = { x: obj1, y: obj2, z: M.string() };
261
- const shape4 = { x: M.or(obj1, M.string()), y: obj2 };
262
- const shape5 = { x: obj2, y: obj1 }; // wrong slots
263
- const trial = shape => {
264
- t.throws(
265
- () => defineDurableKind(kh, init, behavior, { stateShape: shape }),
266
- { message: /durable Kind stateShape mismatch/ },
267
- );
267
+ // we do *not* override allowStateShapeChanges
268
+ // const opts = { allowStateShapeChanges: true };
269
+ const opts = undefined;
270
+ const stateShapeMismatch = { message: /durable Kind stateShape mismatch/ };
271
+ const ls2 = makeLiveSlots(sc2, 'vatA', {}, opts, gcTools, undefined, () => {
272
+ const buildRootObject = (vatPowers, vatParameters, baggage) => {
273
+ const { VatData } = vatPowers;
274
+ const { x, y } = vatParameters;
275
+ const kh = baggage.get('kh');
276
+ const redefineDurableKind = stateShape =>
277
+ defineHolder(VatData.defineDurableKind, kh, stateShape);
278
+ // several shapes that are not compatible
279
+ const badShapes = [
280
+ { x, y: M.any() },
281
+ { x },
282
+ { x, y, z: M.string() },
283
+ { x: M.or(x, M.string()), y },
284
+ { x: y, y: x }, // wrong slots
285
+ ];
286
+ for (const stateShape of badShapes) {
287
+ t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
288
+ }
289
+ // the correct shape
290
+ redefineDurableKind({ x, y });
291
+
292
+ return Far('root', {});
268
293
  };
269
- trial(shape1);
270
- trial(shape2);
271
- trial(shape3);
272
- trial(shape4);
273
- trial(shape5);
274
- const stateShape = { x: obj1, y: obj2 }; // the correct shape
275
- defineDurableKind(kh, init, behavior, { stateShape });
276
- t.pass();
277
-
278
- return Far('root', {});
279
- }
294
+ return { buildRootObject };
295
+ });
280
296
 
281
- // we do *not* override allowStateShapeChanges
282
- // const options = { allowStateShapeChanges: true };
283
- const options = undefined;
284
- const makeNS2 = () => ({ buildRootObject: build2 });
285
- const ls2 = makeLiveSlots(
286
- sc2,
287
- 'vatA',
288
- {},
289
- options,
290
- gcTools,
291
- undefined,
292
- makeNS2,
293
- );
297
+ const vatParameters2 = { x: passables.get(vref1), y: passables.get(vref2) };
298
+ await ls2.dispatch(makeStartVat(kser(vatParameters2)));
299
+
300
+ const state2 = await dispatchForResult(ls2, log2, [objRef, 'get', fields]);
301
+ t.deepEqual(kser(state2), kser(state1));
294
302
 
295
- const vp = { obj1: kslot(vref1), obj2: kslot(vref2) };
296
- const startVat2 = makeStartVat(kser(vp));
297
- await ls2.dispatch(startVat2);
303
+ // ------
304
+
305
+ // Now upgrade again, first to add a new optional stateShape field x2 and then
306
+ // to preserve the new shape.
307
+ const store3 = new Map(store2);
308
+ const { syscall: sc3, log: log3 } = buildSyscall({ kvStore: store3 });
309
+ const ls3 = makeLiveSlots(sc3, 'vatA', {}, {}, gcTools, undefined, () => {
310
+ const buildRootObject = ({ VatData }, vatParameters, baggage) => {
311
+ const { x, y } = vatParameters;
312
+ const kh = baggage.get('kh');
313
+ const redefineDurableKind = stateShape =>
314
+ defineHolder(VatData.defineDurableKind, kh, stateShape);
315
+ const badShapes = [{ x, x2: x, y }];
316
+ for (const stateShape of badShapes) {
317
+ t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
318
+ }
319
+ redefineDurableKind({ x, x2: M.or(M.undefined(), x), y });
320
+ return Far('root', {});
321
+ };
322
+ return { buildRootObject };
323
+ });
324
+ const vatParameters3 = { x: passables.get(vref1), y: passables.get(vref2) };
325
+ await ls3.dispatch(makeStartVat(kser(vatParameters3)));
326
+
327
+ const state3 = await dispatchForResult(ls3, log3, [objRef, 'get', fields]);
328
+ t.deepEqual(kser(state3), kser({ ...state1, x2: undefined }));
329
+ const makeVerificationCase = (x, x2, y, throwsMessage) => [
330
+ `{ x: ${x}, x2: ${x2}, y: ${y} }`,
331
+ { x: passables.get(x), x2: passables.get(x2), y: passables.get(y) },
332
+ throwsMessage,
333
+ ];
334
+ const verifications = [
335
+ makeVerificationCase(vref1, vref2, vref2, 'rejected'),
336
+ makeVerificationCase(vref1, vref1, vref2),
337
+ makeVerificationCase(vref1, undefined, vref2),
338
+ ];
339
+ let lastState = state3;
340
+ for (const [label, value, message] of verifications) {
341
+ const tryUpdate = () =>
342
+ dispatchForResult(ls3, log3, [objRef, 'set', [value]]);
343
+ await (message
344
+ ? t.throwsAsync(tryUpdate, { message }, `${label} ${message}`)
345
+ : tryUpdate());
346
+ const state = await dispatchForResult(ls3, log3, [objRef, 'get', fields]);
347
+ const expectState = message ? lastState : value;
348
+ t.deepEqual(kser(state), kser(expectState), `${label}`);
349
+ lastState = state;
350
+ }
351
+
352
+ const store4 = new Map(store3);
353
+ const { syscall: sc4, log: log4 } = buildSyscall({ kvStore: store4 });
354
+ const ls4 = makeLiveSlots(sc4, 'vatA', {}, {}, gcTools, undefined, () => {
355
+ const buildRootObject = ({ VatData }, vatParameters, baggage) => {
356
+ const { x, y } = vatParameters;
357
+ const kh = baggage.get('kh');
358
+ const redefineDurableKind = stateShape =>
359
+ defineHolder(VatData.defineDurableKind, kh, stateShape);
360
+ const badShapes = [
361
+ // Removing optionality from x2.
362
+ { x, x2: x, y },
363
+ // Equivalent but not *equal* to M.or(M.undefined(), x).
364
+ { x, x2: M.or(undefined, x), y },
365
+ { x, x2: M.or(x, M.undefined()), y },
366
+ ];
367
+ for (const stateShape of badShapes) {
368
+ t.throws(() => redefineDurableKind(stateShape), stateShapeMismatch);
369
+ }
370
+ redefineDurableKind({ x, x2: M.or(M.undefined(), x), y });
371
+ return Far('root', {});
372
+ };
373
+ return { buildRootObject };
374
+ });
375
+ const vatParameters4 = { x: passables.get(vref1), y: passables.get(vref2) };
376
+ await ls4.dispatch(makeStartVat(kser(vatParameters4)));
377
+
378
+ for (const [label, value, message] of verifications) {
379
+ const tryUpdate = () =>
380
+ dispatchForResult(ls4, log4, [objRef, 'set', [value]]);
381
+ await (message
382
+ ? t.throwsAsync(tryUpdate, { message }, `${label} ${message}`)
383
+ : tryUpdate());
384
+ const state = await dispatchForResult(ls4, log4, [objRef, 'get', fields]);
385
+ const expectState = message ? lastState : value;
386
+ t.deepEqual(kser(state), kser(expectState), `${label}`);
387
+ lastState = state;
388
+ }
298
389
  });