Skip to content

Commit

Permalink
feat(vat-data): virtual exos are -nternally revocable
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Jul 6, 2023
1 parent 9d7072b commit cdf3450
Show file tree
Hide file tree
Showing 4 changed files with 173 additions and 17 deletions.
5 changes: 3 additions & 2 deletions packages/swingset-liveslots/src/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { Fail } from '@agoric/assert';
/**
* @callback CacheDelete
* @param {string} key
* @returns {void}
* @returns {boolean}
*
* @callback CacheFlush
* @returns {void}
Expand Down Expand Up @@ -82,8 +82,9 @@ export function makeCache(readBacking, writeBacking, deleteBacking) {
},
delete: key => {
assert.typeof(key, 'string');
stash.delete(key);
const result = stash.delete(key);
dirtyKeys.add(key);
return result;
},
flush: () => {
const keys = [...dirtyKeys.keys()];
Expand Down
16 changes: 13 additions & 3 deletions packages/swingset-liveslots/src/virtualObjectManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -920,15 +920,25 @@ export const makeVirtualObjectManager = (
const makeContext = (baseRef, state) => {
// baseRef came from valToSlot, so must be in slotToVal
const val = requiredValForSlot(baseRef);
const revoke = () => contextCache.delete(baseRef);
// val is either 'self' or the facet record
if (multifaceted) {
return harden({ state, facets: val });
return harden({
state,
facets: val,
revoke,
});
} else {
return harden({ state, self: val });
return harden({
state,
self: val,
revoke,
});
}
};

// The contextCache holds the {state,self} or {state,facets} "context"
// The contextCache holds the
// { state, self, revoke } or { state, facets, revoke } "context"
// object, needed by behavior functions. We keep this in a (per-crank)
// cache because creating one requires knowledge of the state property
// names, which requires a DB read. The property names are fixed at
Expand Down
43 changes: 31 additions & 12 deletions packages/vat-data/src/exo-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,13 @@ export const makeExoUtils = VatData => {
);
harden(prepareKindMulti);

/**
* TODO reuse types ClassContext, ClassContextKit, etc from @endo/exo
*
* @callback RevokeExo
* @returns {boolean}
*/

// TODO interfaceGuard type https://github.com/Agoric/agoric-sdk/issues/6206
/**
* @template {(...args: any) => any} I init state function
Expand All @@ -90,11 +97,13 @@ export const makeExoUtils = VatData => {
* @param {I} init
* @param {T & ThisType<{
* self: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} methods
* @param {DefineKindOptions<{
* self: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} [options]
* @returns {(...args: Parameters<I>) => (T & RemotableBrand<{}, T>)}
*/
Expand All @@ -115,11 +124,13 @@ export const makeExoUtils = VatData => {
* @param {I} init
* @param {T & ThisType<{
* facets: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }> } facets
* @param {DefineKindOptions<{
* facets: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} [options]
* @returns {(...args: Parameters<I>) => (T & RemotableBrand<{}, T>)}
*/
Expand All @@ -146,11 +157,13 @@ export const makeExoUtils = VatData => {
* @param {I} init
* @param {T & ThisType<{
* self: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} methods
* @param {DefineKindOptions<{
* self: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} [options]
* @returns {(...args: Parameters<I>) => (T & RemotableBrand<{}, T>)}
*/
Expand All @@ -177,11 +190,13 @@ export const makeExoUtils = VatData => {
* @param {I} init
* @param {T & ThisType<{
* facets: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }> } facets
* @param {DefineKindOptions<{
* facets: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} [options]
* @returns {(...args: Parameters<I>) => (T & RemotableBrand<{}, T>)}
*/
Expand Down Expand Up @@ -209,11 +224,13 @@ export const makeExoUtils = VatData => {
* @param {I} init
* @param {T & ThisType<{
* self: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} methods
* @param {DefineKindOptions<{
* self: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} [options]
* @returns {(...args: Parameters<I>) => (T & RemotableBrand<{}, T>)}
*/
Expand Down Expand Up @@ -244,11 +261,13 @@ export const makeExoUtils = VatData => {
* @param {I} init
* @param {T & ThisType<{
* facets: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }> } facets
* @param {DefineKindOptions<{
* facets: T,
* state: ReturnType<I>
* state: ReturnType<I>,
* revoke: RevokeExo,
* }>} [options]
* @returns {(...args: Parameters<I>) => (T & RemotableBrand<{}, T>)}
*/
Expand Down
126 changes: 126 additions & 0 deletions packages/vat-data/test/test-revoke-virtual.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// Modeled on test-revoke-heap-classes.js

import { test } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
import { M } from '@agoric/store';
import {
defineVirtualExoClass,
defineVirtualExoClassKit,
} from '../src/exo-utils.js';

const { apply } = Reflect;

const UpCounterI = M.interface('UpCounter', {
incr: M.call()
// TODO M.number() should not be needed to get a better error message
.optional(M.and(M.number(), M.gte(0)))
.returns(M.number()),
done: M.call().returns(M.boolean()),
});

const DownCounterI = M.interface('DownCounter', {
decr: M.call()
// TODO M.number() should not be needed to get a better error message
.optional(M.and(M.number(), M.gte(0)))
.returns(M.number()),
});

test('test revoke defineVirtualExoClass', t => {
const makeUpCounter = defineVirtualExoClass(
'UpCounter',
UpCounterI,
/** @param {number} x */
(x = 0) => ({ x }),
{
incr(y = 1) {
const { state } = this;
state.x += y;
return state.x;
},
done() {
const { revoke } = this;
return revoke();
},
},
);
const upCounter = makeUpCounter(3);
t.is(upCounter.incr(5), 8);
t.is(upCounter.done(), true);
t.throws(() => upCounter.incr(1), {
message:
'"In \\"incr\\" method of (UpCounter)" may only be applied to a valid instance: "[Alleged: UpCounter]"',
});
});

test('test revoke defineVirtualExoClassKit', t => {
const makeCounterKit = defineVirtualExoClassKit(
'Counter',
{ up: UpCounterI, down: DownCounterI },
/** @param {number} x */
(x = 0) => ({ x }),
{
up: {
incr(y = 1) {
const { state } = this;
state.x += y;
return state.x;
},
done() {
const { revoke } = this;
return revoke();
},
},
down: {
decr(y = 1) {
const { state } = this;
state.x -= y;
return state.x;
},
},
},
);
const { up: upCounter, down: downCounter } = makeCounterKit(3);
t.is(upCounter.incr(5), 8);
t.is(downCounter.decr(), 7);
t.is(upCounter.done(), true);
t.throws(() => upCounter.incr(3), {
message:
'"In \\"incr\\" method of (Counter up)" may only be applied to a valid instance: "[Alleged: Counter up]"',
});
t.throws(() => downCounter.decr(), {
message:
'"In \\"decr\\" method of (Counter down)" may only be applied to a valid instance: "[Alleged: Counter down]"',
});
});

test('test virtual facet cross-talk', t => {
const makeCounterKit = defineVirtualExoClassKit(
'Counter',
{ up: UpCounterI, down: DownCounterI },
/** @param {number} x */
(x = 0) => ({ x }),
{
up: {
incr(y = 1) {
const { state } = this;
state.x += y;
return state.x;
},
done() {
const { revoke } = this;
return revoke();
},
},
down: {
decr(y = 1) {
const { state } = this;
state.x -= y;
return state.x;
},
},
},
);
const { up: upCounter, down: downCounter } = makeCounterKit(3);
t.throws(() => apply(upCounter.incr, downCounter, [2]), {
message: 'illegal cross-facet access',
});
});

0 comments on commit cdf3450

Please sign in to comment.