Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: use limited publishBrandInfo power, not all of chainStorage #61

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion contract/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,12 @@ install-bundles: bundles/bundle-list

build-proposal: bundles/bundle-list

bundles/bundle-list $(SCRIPT) $(PERMIT):
bundles/bundle-list $(SCRIPT) $(PERMIT): ./scripts/build-contract-deployer.js ./scripts/build-proposal.sh
./scripts/build-proposal.sh

./scripts/build-contract-deployer.js: offer-up-proposal.js

offer-up-proposal.js: platform-goals/boardAux.js platform-goals/marshal-produce.js

clean:
@rm -rf $(SCRIPT) $(PERMIT) bundles/
8 changes: 5 additions & 3 deletions contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
"lint:fix": "eslint --fix '**/*.js'"
},
"devDependencies": {
"agoric": "^0.21.2-u12.0",
"@agoric/deploy-script-support": "^0.10.4-u12.0",
"@endo/bundle-source": "^2.8.0",
"@agoric/eslint-config": "dev",
"@endo/bundle-source": "^2.8.0",
"@endo/eslint-plugin": "^0.5.2",
"@endo/init": "^0.5.60",
"@endo/promise-kit": "0.2.56",
"@endo/ses-ava": "^0.2.44",
"@jessie.js/eslint-plugin": "^0.4.0",
"@typescript-eslint/eslint-plugin": "^6.7.0",
"@typescript-eslint/parser": "^6.7.0",
"agoric": "^0.21.2-u12.0",
"ava": "^5.3.0",
"eslint": "^8.47.0",
"eslint-config-airbnb-base": "^15.0.0",
Expand All @@ -45,8 +45,10 @@
"typescript": "~5.2.2"
},
"dependencies": {
"@agoric/zoe": "^0.26.3-u12.0",
"@agoric/ertp": "^0.16.3-u12.0",
"@agoric/store": "0.9.2",
"@agoric/zoe": "^0.26.3-u12.0",
"@agoric/zone": "0.2.2",
"@endo/far": "^0.2.22",
"@endo/marshal": "^0.8.9",
"@endo/patterns": "^0.2.5"
Expand Down
54 changes: 20 additions & 34 deletions contract/src/offer-up-proposal.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,51 @@
// @ts-check
import { E } from '@endo/far';
import { makeMarshal } from '@endo/marshal';
import { AmountMath } from '@agoric/ertp/src/amountMath.js';

console.warn('start proposal module evaluating');

const { Fail } = assert;
import { manifest as boardAuxManifest } from './platform-goals/boardAux.js';
import { manifest as endoManifest } from './platform-goals/marshal-produce.js';

// vstorage paths under published.*
const BOARD_AUX = 'boardAux';
export { produceBoardAuxManager } from './platform-goals/boardAux.js';
export { produceEndoModules } from './platform-goals/marshal-produce.js';

const marshalData = makeMarshal(_val => Fail`data only`);
console.warn('start proposal module evaluating');

const IST_UNIT = 1_000_000n;
const CENT = IST_UNIT / 100n;

/** @template {string} T @typedef {import('./platform-goals/core-types').AssetsSpace<T>} AssetsSpace */
/** @template {string} T @typedef {import('./platform-goals/core-types').ContractSpace<T>} ContractSpace */

/**
* Make a storage node for auxilliary data for a value on the board.
*
* @param {ERef<StorageNode>} chainStorage
* @param {string} boardId
* @typedef {AssetsSpace<'Item'>
* & ContractSpace<'offerUp'>
* } OfferUpPowers
*/
const makeBoardAuxNode = async (chainStorage, boardId) => {
const boardAux = E(chainStorage).makeChildNode(BOARD_AUX);
return E(boardAux).makeChildNode(boardId);
};

const publishBrandInfo = async (chainStorage, board, brand) => {
const [id, displayInfo] = await Promise.all([
E(board).getId(brand),
E(brand).getDisplayInfo(),
]);
const node = makeBoardAuxNode(chainStorage, id);
const aux = marshalData.toCapData(harden({ displayInfo }));
await E(node).setValue(JSON.stringify(aux));
};

/**
* Core eval script to start contract
*
* @param {BootstrapPowers} permittedPowers
* @param {import('./platform-goals/core-types').BootstrapPowers
* & OfferUpPowers
* & import('./platform-goals/boardAux').BoardAuxPowers
* } permittedPowers
*/
export const startOfferUpContract = async permittedPowers => {
console.error('startOfferUpContract()...');
const {
consume: { board, chainStorage, startUpgradable, zoe },
consume: { brandAuxPublisher, startUpgradable, zoe },
brand: {
consume: { IST: istBrandP },
// @ts-expect-error dynamic extension to promise space
produce: { Item: produceItemBrand },
},
issuer: {
consume: { IST: istIssuerP },
// @ts-expect-error dynamic extension to promise space
produce: { Item: produceItemIssuer },
},
installation: {
consume: { offerUp: offerUpInstallationP },
},
instance: {
// @ts-expect-error dynamic extension to promise space
produce: { offerUp: produceInstance },
},
} = permittedPowers;
Expand All @@ -79,6 +65,7 @@ export const startOfferUpContract = async permittedPowers => {
terms,
});
console.log('CoreEval script: started contract', instance);
/** @type {StandardTerms} */
const {
brands: { Item: brand },
issuers: { Item: issuer },
Expand All @@ -94,7 +81,7 @@ export const startOfferUpContract = async permittedPowers => {
produceItemBrand.resolve(brand);
produceItemIssuer.resolve(issuer);

await publishBrandInfo(chainStorage, board, brand);
await E(brandAuxPublisher).publishBrandInfo(brand);
console.log('offerUp (re)started');
};

Expand All @@ -103,8 +90,7 @@ const offerUpManifest = {
[startOfferUpContract.name]: {
consume: {
agoricNames: true,
board: true, // to publish boardAux info for NFT brand
chainStorage: true, // to publish boardAux info for NFT brand
brandAuxPublisher: true, // to publish displayInfo of NFT brand
startUpgradable: true, // to start contract and save adminFacet
zoe: true, // to get contract terms, including issuer/brand
},
Expand All @@ -118,7 +104,7 @@ harden(offerUpManifest);

export const getManifestForOfferUp = ({ restoreRef }, { offerUpRef }) => {
return harden({
manifest: offerUpManifest,
manifest: { ...offerUpManifest, ...boardAuxManifest, ...endoManifest },
installations: {
offerUp: restoreRef(offerUpRef),
},
Expand Down
114 changes: 114 additions & 0 deletions contract/src/platform-goals/boardAux.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// @ts-check
import { E, Far } from '@endo/far';

const { Fail } = assert;

// vstorage paths under published.*
const BOARD_AUX = 'boardAux';

/**
* @param {import('@agoric/zone').Zone} zone
* @param {Marshaller} marshalData
* @param {{
* board: ERef<import('./core-types').Board>;
* chainStorage: ERef<StorageNode>;
* }} powers
*/
export const makeBoardAuxManager = (zone, marshalData, powers) => {
const { board, chainStorage } = powers;
const store = zone.mapStore('boardAux');
const boardAux = E(chainStorage).makeChildNode(BOARD_AUX);

const formatValue = value => {
const aux = marshalData.toCapData(value);
// max length?
return JSON.stringify(aux);
};

const boardAuxNode = key =>
E.when(E(board).getId(key), boardId => E(boardAux).makeChildNode(boardId));

const init = async (key, value) => {
store.init(key, value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init is only allowed if the key does not already exist. Get, set and * delete are only allowed if the key does already exist.

await E(boardAuxNode(key)).setValue(formatValue(value));
};

const update = async (key, value) => {
if (store.has(key)) {
store.set(key, value);
} else {
store.init(key, value);
}
await E(boardAuxNode(key)).setValue(formatValue(value));
};

/**
* Publish displayInfo of a brand to vstorage under its boardId.
*
* Works only once per brand.
* @see {BoardAuxAdmin} to over-write aux info
*
* @param {Brand} brand
*/
const publishBrandInfo = brand =>
E.when(
Promise.all([E(brand).getAllegedName(), E(brand).getDisplayInfo()]),
([allegedName, displayInfo]) =>
init(brand, harden({ allegedName, displayInfo })),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is goofy; I don't think this collection is keyed by brand

);

return harden({
brandAuxPublisher: Far('BrandAuxPublisher', { publishBrandInfo }),
boardAuxTOFU: Far('BoardAuxTOFU', { publishBrandInfo, init }),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does TOFU mean?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trust On First Use.

Maybe should be renamed to "first come, first served".
IOU (and the rest of the consumers of this code) better documentation.

boardAuxAdmin: Far('BoardAuxAdmin', { publishBrandInfo, init, update }),
});
};
/** @typedef {ReturnType<typeof makeBoardAuxManager>} BoardAuxManager */

/** @typedef {BoardAuxManager['brandAuxPublisher']} BrandAuxPublisher */
/** @typedef {BoardAuxManager['boardAuxTOFU']} BoardAuxTOFU */
/** @typedef {BoardAuxManager['boardAuxAdmin']} BoardAuxAdmin */

/**
* @typedef {import('./core-types').PromiseSpaceOf<{
* brandAuxPublisher: BrandAuxPublisher;
* boardAuxTOFU: BoardAuxTOFU;
* boardAuxAdmin: BoardAuxAdmin;
* }>} BoardAuxPowers
*/

/**
* @param {import('./core-types').BootstrapPowers
* & import('./marshal-produce').Endo1Space
* & BoardAuxPowers
* } powers
*/
export const produceBoardAuxManager = async powers => {
const { zone } = powers;
const { board, chainStorage, endo1 } = powers.consume;
const { marshal } = await endo1;
const { makeMarshal } = marshal;
const marshalData = makeMarshal(_val => Fail`data only`);

const mgr = makeBoardAuxManager(zone, marshalData, { board, chainStorage });
powers.produce.brandAuxPublisher.reset();
powers.produce.boardAuxTOFU.reset();
powers.produce.boardAuxAdmin.reset();
powers.produce.brandAuxPublisher.resolve(mgr.brandAuxPublisher);
powers.produce.boardAuxTOFU.resolve(mgr.boardAuxTOFU);
powers.produce.boardAuxAdmin.resolve(mgr.boardAuxAdmin);
};

export const permit = {
zone: true,
consume: { board: true, chainStorage: true, endo1: true },
produce: {
brandAuxPublisher: true,
boardAuxTOFU: true,
boardAuxAdmin: true,
},
};

export const manifest = {
[produceBoardAuxManager.name]: permit,
};
58 changes: 58 additions & 0 deletions contract/src/platform-goals/core-types.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { Zone } from '@agoric/zone';

// XXX how to import types from @agoric/vats?
// cf. https://github.com/Agoric/agoric-sdk/blob/master/packages/vats/src/core/types-ambient.d.ts
type Board = { getId: (key: unknown) => string };

type WellKnown = {
asset: 'BLD' | 'IST';
contract: 'VaultFactory' | 'FeeDistributor';
};

// TODO: include AssetKind { IST: 'nat', ... }
type AssetsSpace<T extends string> = {
brand: PromiseSpaceOf<Record<T, Brand>>;
issuer: PromiseSpaceOf<Record<T, Issuer>>;
};

// TODO: include contract start function types
type ContractSpace<T extends string> = {
installation: PromiseSpaceOf<Record<T, Installation>>;
instance: PromiseSpaceOf<Record<T, Instance>>;
};

type WellKnownSpaces = AssetsSpace<WellKnown['asset']> &
ContractSpace<WellKnown['contract']>;

type BootstrapSpace = PromiseSpaceOf<{
chainStorage: StorageNode;
board: Board;
startUpgradable: (...args: unknown[]) => any;
zoe: ZoeService;
}>;

type BootstrapPowers = BootstrapSpace &
WellKnownSpaces & {
zone: Zone;
};

type Producer<T> = {
resolve: (v: ERef<T>) => void;
reject: (r: unknown) => void;
reset: (reason?: unknown) => void;
};

/**
* @template B - Bidirectional
* @template C - Consume only
* @template P - Produce only
*/
type PromiseSpaceOf<B, C = {}, P = {}> = {
consume: { [K in keyof (B & C)]: Promise<(B & C)[K]> };
produce: { [K in keyof (B & P)]: Producer<(B & P)[K]> };
};

type BootstrapManifestPermit =
| true
| string
| { [key: string]: BootstrapManifestPermit };
34 changes: 34 additions & 0 deletions contract/src/platform-goals/marshal-produce.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// @ts-check
import * as marshal from '@endo/marshal';
import * as patterns from '@endo/patterns';

/**
* @typedef {{
* endo1: {
* marshal: typeof import('@endo/marshal');
* patterns: typeof import('@endo/patterns');
* }
* }} Endo1Modules
Comment on lines +7 to +11
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is cool! Why endo1 instead of endo?

Copy link
Member Author

@dckc dckc Feb 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cuz it doesn't have all of the endo modules; just the ones needed by this 1st use case

but that reminds me... I should get input from endo folks such as @kriskowal

* @typedef {import('./core-types').PromiseSpaceOf<Endo1Modules>} Endo1Space
*/

/**
* Make @endo/marshal, @endo/patterns available to CoreEval scripts.
*
* @param {import('./core-types').BootstrapPowers & Endo1Space} permittedPowers
*/
export const produceEndoModules = permittedPowers => {
const { produce } = permittedPowers;
const endo = { marshal, patterns };
produce.endo1.resolve(endo);
};

/** @type {import('./core-types').BootstrapManifestPermit} */
export const permit = {
/** @type {Record<keyof Endo1Modules, true>} */
produce: { endo1: true },
};

export const manifest = {
[produceEndoModules.name]: permit,
};
Loading