Skip to content

Commit

Permalink
fix: cheap auxdata
Browse files Browse the repository at this point in the history
  • Loading branch information
erights committed Oct 1, 2022
1 parent 1313d36 commit 0664fa8
Show file tree
Hide file tree
Showing 9 changed files with 120 additions and 23 deletions.
10 changes: 10 additions & 0 deletions packages/ERTP/src/paymentLedger.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ export const vivifyPaymentLedger = (
getAmountShape() {
return amountShape;
},
aux() {
return brandAuxData;
},
});

const emptyAmount = AmountMath.makeEmpty(brand, assetKind);
Expand All @@ -112,6 +115,13 @@ export const vivifyPaymentLedger = (
elementShape,
);

const brandAuxData = harden({
name,
assetKind,
displayInfo,
amountShape,
});

const { IssuerI, MintI, PaymentI, PurseIKit } = makeIssuerInterfaces(
brand,
assetKind,
Expand Down
8 changes: 8 additions & 0 deletions packages/ERTP/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,19 @@ export const DisplayInfoShape = M.partial(

// //////////////////////// Interfaces /////////////////////////////////////////

export const BrandAuxDataShape = harden({
name: M.string(),
assetKind: AssetKindShape,
displayInfo: DisplayInfoShape,
amountShape: M.pattern(),
});

export const BrandI = M.interface('Brand', {
isMyIssuer: M.callWhen(M.await(IssuerShape)).returns(M.boolean()),
getAllegedName: M.call().returns(M.string()),
getDisplayInfo: M.call().returns(DisplayInfoShape),
getAmountShape: M.call().returns(M.pattern()),
aux: M.call().returns(BrandAuxDataShape),
});

/**
Expand Down
9 changes: 9 additions & 0 deletions packages/ERTP/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@
* AssetKind.SET or AssetKind.COPY_SET (non-fungible)
*/

/**
* @typedef {object} BrandAuxData
* @property {string} name
* @property {AssetKind} assetKind
* @property {DisplayInfo} displayInfo
* @property {Pattern} amountShape
*/

/**
* @template {AssetKind} [K=AssetKind]
* @typedef {object} Brand
Expand All @@ -113,6 +121,7 @@
* @property {() => DisplayInfo} getDisplayInfo
* Give information to UI on how to display the amount.
* @property {() => Pattern} getAmountShape
* @property {() => BrandAuxData} aux
*/

// /////////////////////////// Issuer //////////////////////////////////////////
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ test('natMathHelpers coerce', t => {
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
aux: () =>
harden({
name: 'somename',
assetKind: AssetKind.NAT,
displayInfo: { assetKind: AssetKind.NAT },
amountShape: M.any(),
}),
}),
value: 4n,
}),
Expand Down
53 changes: 53 additions & 0 deletions packages/SwingSet/src/lib/capdata.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { assert, details as X } from '@agoric/assert';
import { E } from '@endo/eventual-send';
import { Remotable } from '@endo/marshal';

const { defineProperty } = Object;

/* eslint-disable jsdoc/require-returns-check */
/**
Expand Down Expand Up @@ -43,3 +47,52 @@ export function extractSingleSlot(data) {
}
return null;
}

// TODO Move someplace more reusable; perhaps even @endo/marshal
export const makeRemotePresence = (iface, fulfilledHandler) => {
let remotePresence;
const p = new HandledPromise((_res, _rej, resolveWithPresence) => {
remotePresence = resolveWithPresence(fulfilledHandler);
let auxData;
let hasAuxData = false;

/**
* A remote presence has an `aux()` method that either returns a promise
* or a non-promise. The first time it is called, it does an eventual-send
* of an `aux()` message to the far object it designates, remembers that
* promise as its auxdata, and returns that. It also watches that promise
* to react to its fulfillment. Until that reaction, every time its
* `aux()` is called, it will return that same promise. Once it reacts
* to the promise being fulfilled, if that ever happens, then the
* fulfillment becomes its new auxdata which gets returned from then on.
*
* @template T
* @returns {ERef<T>}
*/
const aux = () => {
if (hasAuxData) {
return auxData;
}
auxData = E(remotePresence).aux();
hasAuxData = true;
// TODO Also watch for a rejection. If rejected with the special
// value indicating the promise was broken by upgrade, then ask again.
// To keep the return promise valid across that upgrade-polling
// requires more bookkeeping, to keep the returned promise distinct
// from the promise for the result of the send.
E.when(auxData, value => (auxData = value));
return auxData;
};
defineProperty(remotePresence, 'aux', {
value: aux,
writable: false,
enumerable: false,
configurable: false,
});
// Use Remotable rather than Far to make a remote from a presence
Remotable(iface, undefined, remotePresence);
});

harden(p);
return remotePresence;
};
29 changes: 11 additions & 18 deletions packages/SwingSet/src/liveslots/liveslots.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
makeVatSlot,
parseVatSlot,
} from '../lib/parseVatSlots.js';
import { insistCapData } from '../lib/capdata.js';
import { insistCapData, makeRemotePresence } from '../lib/capdata.js';
import { extractMethod, legibilizeMethod } from '../lib/kdebug.js';
import { insistMessage } from '../lib/message.js';
import { makeVirtualReferenceManager } from './virtualReferences.js';
Expand Down Expand Up @@ -377,22 +377,17 @@ function build(
},
};

let remotePresence;
const p = new HandledPromise((_res, _rej, resolveWithPresence) => {
// Use Remotable rather than Far to make a remote from a presence
remotePresence = Remotable(
iface,
undefined,
resolveWithPresence(fulfilledHandler),
);
// remote === presence, actually
const remotePresence = makeRemotePresence(iface, fulfilledHandler);

// remote === presence, actually

// todo: mfig says resolveWithPresence
// gives us a Presence, Remotable gives us a Remote. I think that
// implies we have a lot of renaming to do, 'makeRemote' instead of
// 'makeImportedPresence', etc. I'd like to defer that for a later
// cleanup/renaming pass.
}); // no unfulfilledHandler
// todo: mfig says resolveWithPresence
// gives us a Presence, Remotable gives us a Remote. I think that
// implies we have a lot of renaming to do, 'makeRemote' instead of
// 'makeImportedPresence', etc. I'd like to defer that for a later
// cleanup/renaming pass.

// no unfulfilledHandler

// The call to resolveWithPresence performs the forwarding logic
// immediately, so by the time we reach here, E(presence).foo() will use
Expand All @@ -404,8 +399,6 @@ function build(
// `HandledPromise.resolve(presence)`. So we must harden it now, for
// safety, to prevent it from being used as a communication channel
// between isolated objects that share a reference to the Presence.
harden(p);

// Up at the application level, presence~.foo(args) starts by doing
// HandledPromise.resolve(presence), which retrieves it, and then does
// p.eventualSend('foo', [args]), which uses the fulfilledHandler.
Expand Down
9 changes: 5 additions & 4 deletions packages/zoe/src/issuerStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,16 @@ export const provideIssuerStorage = zcfBaggage => {
assertInstantiated();
const brandP = E(issuerP).getBrand();
const brandIssuerMatchP = E(brandP).isMyIssuer(issuerP);
const displayInfoP = E(brandP).getDisplayInfo();
/** @type {[Issuer,Brand,boolean,DisplayInfo]} */
const [issuer, brand, brandIssuerMatch, displayInfo] = await Promise.all([
const brandAuxDataP = E(brandP).aux();
/** @type {[Issuer,Brand,boolean,BrandAuxData]} */
const [issuer, brand, brandIssuerMatch, brandAuxData] = await Promise.all([
issuerP,
brandP,
brandIssuerMatchP,
displayInfoP,
brandAuxDataP,
]);
// AWAIT /////
const { displayInfo } = brandAuxData;

if (issuerToIssuerRecord.has(issuer)) {
return issuerToIssuerRecord.get(issuer);
Expand Down
8 changes: 8 additions & 0 deletions packages/zoe/test/unitTests/test-zoe.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { E } from '@endo/eventual-send';
import { makePromiseKit } from '@endo/promise-kit';
import { passStyleOf, Far } from '@endo/marshal';
import { getMethodNames } from '@agoric/internal';
import { M } from '@agoric/store';

// eslint-disable-next-line import/no-extraneous-dependencies
import bundleSource from '@endo/bundle-source';
Expand Down Expand Up @@ -173,6 +174,13 @@ test(`E(zoe).startInstance - bad issuer, makeEmptyPurse throws`, async t => {
// eslint-disable-next-line no-use-before-define
isMyIssuer: i => i === badIssuer,
getDisplayInfo: () => ({ decimalPlaces: 6, assetKind: AssetKind.NAT }),
aux: () =>
harden({
name: 'bogusBrand',
assetKind: AssetKind.NAT,
displayInfo: { decimalPlaces: 6, assetKind: AssetKind.NAT },
amountShape: M.any(),
}),
});
const badIssuer = Far('issuer', {
makeEmptyPurse: async () => {
Expand Down
10 changes: 9 additions & 1 deletion packages/zoe/test/unitTests/zcf/test-zcf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import { Far } from '@endo/marshal';
import { AssetKind, AmountMath } from '@agoric/ertp';
import { E } from '@endo/eventual-send';
import { getMethodNames } from '@agoric/internal';
import { makeOffer } from '../makeOffer.js';
import { M } from '@agoric/store';

import { makeOffer } from '../makeOffer.js';
import { setup } from '../setupBasicMints.js';
import buildManualTimer from '../../../tools/manualTimer.js';

Expand Down Expand Up @@ -205,6 +206,13 @@ test(`zcf.saveIssuer - bad issuer, makeEmptyPurse throws`, async t => {
// eslint-disable-next-line no-use-before-define
isMyIssuer: i => i === badIssuer,
getDisplayInfo: () => ({ decimalPlaces: 6, assetKind: AssetKind.NAT }),
aux: () =>
harden({
name: 'bogusBrand',
assetKind: AssetKind.NAT,
displayInfo: { decimalPlaces: 6, assetKind: AssetKind.NAT },
amountShape: M.any(),
}),
});
const badIssuer = Far('issuer', {
makeEmptyPurse: async () => {
Expand Down

0 comments on commit 0664fa8

Please sign in to comment.