Skip to content

Commit

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

const emptyAmount = AmountMath.makeEmpty(brand, assetKind);
Expand All @@ -110,6 +113,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 @@ -84,6 +84,14 @@ export {};
* 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 @@ -103,6 +111,7 @@ export {};
* @property {() => DisplayInfo<K>} getDisplayInfo
* Give information to UI on how to display the amount.
* @property {() => Pattern} getAmountShape
* @property {() => BrandAuxData} aux
*/

// /////////////////////////// Issuer //////////////////////////////////////////
Expand Down
20 changes: 13 additions & 7 deletions packages/ERTP/test/unitTests/mathHelpers/mockBrand.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import { Far } from '@endo/marshal';
import { M } from '@agoric/store';
import { AssetKind } from '../../../src/index.js';

const mockAuxData = harden({
name: 'mock',
assetKind: AssetKind.NAT,
displayInfo: { assetKind: AssetKind.NAT },
amountShape: M.any(),
});

/** @type {Brand<AssetKind>} */
export const mockBrand = Far('brand', {
// eslint-disable-next-line no-unused-vars
isMyIssuer: async allegedIssuer => false,
getAllegedName: () => 'mock',
getAmountShape: () => {},
getDisplayInfo: () => ({
assetKind: AssetKind.NAT,
}),
getAllegedName: () => mockAuxData.name,
isMyIssuer: async _allegedIssuer => false,
getDisplayInfo: () => mockAuxData.displayInfo,
getAmountShape: () => mockAuxData.amountShape,
aux: () => mockAuxData,
});
154 changes: 32 additions & 122 deletions packages/ERTP/test/unitTests/mathHelpers/test-natMathHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ import { mockBrand } from './mockBrand.js';
// AmountMath so that we can test that any duplication is handled
// correctly.

const otherAuxData = harden({
name: 'somename',
assetKind: AssetKind.NAT,
displayInfo: { assetKind: AssetKind.NAT },
amountShape: M.any(),
});

const otherBrand = Far('otherBrand', {
getAllegedName: () => otherAuxData.name,
isMyIssuer: async _allegedIssuer => false,
getDisplayInfo: () => otherAuxData.displayInfo,
getAmountShape: () => otherAuxData.amountShape,
aux: () => otherAuxData,
});

test('natMathHelpers make', t => {
t.deepEqual(m.make(mockBrand, 4n), { brand: mockBrand, value: 4n });
// @ts-expect-error deliberate invalid arguments for testing
Expand Down Expand Up @@ -62,12 +77,7 @@ test('natMathHelpers coerce', t => {
m.coerce(
mockBrand,
harden({
brand: Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
brand: otherBrand,
value: 4n,
}),
),
Expand Down Expand Up @@ -186,39 +196,14 @@ test('natMathHelpers isGTE', t => {
});

test('natMathHelpers isGTE mixed brands', t => {
t.throws(
() =>
m.isGTE(
m.make(
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
5n,
),
m.make(mockBrand, 3n),
),
{
message: /Brands in left .* and right .* should match but do not/,
},
);
t.throws(() => m.isGTE(m.make(otherBrand, 5n), m.make(mockBrand, 3n)), {
message: /Brands in left .* and right .* should match but do not/,
});
});

test(`natMathHelpers isGTE - brands don't match objective brand`, t => {
t.throws(
() =>
m.isGTE(
m.make(mockBrand, 5n),
m.make(mockBrand, 3n),
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
),
() => m.isGTE(m.make(mockBrand, 5n), m.make(mockBrand, 3n), otherBrand),
{
message: /amount's brand .* did not match expected brand .*/,
},
Expand All @@ -237,39 +222,14 @@ test('natMathHelpers isEqual', t => {
});

test('natMathHelpers isEqual mixed brands', t => {
t.throws(
() =>
m.isEqual(
m.make(
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
4n,
),
m.make(mockBrand, 4n),
),
{
message: /Brands in left .* and right .* should match but do not/,
},
);
t.throws(() => m.isEqual(m.make(otherBrand, 4n), m.make(mockBrand, 4n)), {
message: /Brands in left .* and right .* should match but do not/,
});
});

test(`natMathHelpers isEqual - brands don't match objective brand`, t => {
t.throws(
() =>
m.isEqual(
m.make(mockBrand, 4n),
m.make(mockBrand, 4n),
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
),
() => m.isEqual(m.make(mockBrand, 4n), m.make(mockBrand, 4n), otherBrand),
{
message: /amount's brand .* did not match expected brand .*/,
},
Expand All @@ -285,39 +245,14 @@ test('natMathHelpers add', t => {
});

test('natMathHelpers add mixed brands', t => {
t.throws(
() =>
m.add(
m.make(
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
5n,
),
m.make(mockBrand, 9n),
),
{
message: /Brands in left .* and right .* should match but do not/,
},
);
t.throws(() => m.add(m.make(otherBrand, 5n), m.make(mockBrand, 9n)), {
message: /Brands in left .* and right .* should match but do not/,
});
});

test(`natMathHelpers add - brands don't match objective brand`, t => {
t.throws(
() =>
m.add(
m.make(mockBrand, 5n),
m.make(mockBrand, 9n),
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
),
() => m.add(m.make(mockBrand, 5n), m.make(mockBrand, 9n), otherBrand),
{
message: /amount's brand .* did not match expected brand .*/,
},
Expand All @@ -333,39 +268,14 @@ test('natMathHelpers subtract', t => {
});

test('natMathHelpers subtract mixed brands', t => {
t.throws(
() =>
m.subtract(
m.make(
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
6n,
),
m.make(mockBrand, 1n),
),
{
message: /Brands in left .* and right .* should match but do not/,
},
);
t.throws(() => m.subtract(m.make(otherBrand, 6n), m.make(mockBrand, 1n)), {
message: /Brands in left .* and right .* should match but do not/,
});
});

test(`natMathHelpers subtract brands don't match brand`, t => {
t.throws(
() =>
m.subtract(
m.make(mockBrand, 6n),
m.make(mockBrand, 1n),
Far('otherBrand', {
getAllegedName: () => 'somename',
isMyIssuer: async () => false,
getDisplayInfo: () => ({ assetKind: AssetKind.NAT }),
getAmountShape: () => M.any(),
}),
),
() => m.subtract(m.make(mockBrand, 6n), m.make(mockBrand, 1n), otherBrand),
{
message: /amount's brand .* did not match expected brand .*/,
},
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 { HandledPromise, E } from '@endo/eventual-send';
import { Remotable } from '@endo/marshal';

const { defineProperty } = Object;

/* eslint-disable jsdoc/require-returns-check */
/**
Expand Down Expand Up @@ -42,3 +46,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;
};
Loading

0 comments on commit dc67a20

Please sign in to comment.