Skip to content

Commit

Permalink
WIP: launch using contractStarter
Browse files Browse the repository at this point in the history
failing with:

Error#1: unsupported source: continuing
  at Alleged:
  Offers.executeOffer (file:///home/connolly/projects/ag-power-tools/contract/test/wallet-tools.js:107:15)

 - contractStarter: add install method, separate from starting
   - require give: { Fee } amount in proposal hapes
   - add label support
   - payouts: Handles rather than Started (factor out depositHandles)
   - test: starterSam can install
 - launchIt: contract per launch, using creatorFacet
   - test: launcherLarry subsumes creatorCathy
  • Loading branch information
dckc committed Jan 15, 2024
1 parent d9e5098 commit 23ca000
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 182 deletions.
100 changes: 83 additions & 17 deletions contract/src/contractStarter.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,18 @@ export const privateArgsShape = meta.privateArgsShape;
* }} LimitedAccess
*/

export const InstallOptsShape = M.splitRecord(
{ bundleID: M.string() },
{ label: M.string() },
);

/**
* @typedef {{
* bundleID: string,
* label?: string // XXX call it bundleLabel?
* }} InstallOpts
*/

/**
* @see {ZoeService.startInstance}
*/
Expand Down Expand Up @@ -109,7 +121,7 @@ export const StartOptionsShape = M.and(
* issuerKeywordRecord: Record<string, Issuer>,
* customTerms: StartParams<SF>['terms'],
* privateArgs: StartParams<SF>['privateArgs'],
* instanceLabel: string,
* instanceLabel: string, // XXX add bundleLabel?
* permit: Record<keyof LimitedAccess, string | true>,
* }>} StartOptions
*/
Expand Down Expand Up @@ -137,6 +149,60 @@ export const start = (zcf, limitedPowers, _baggage) => {

const pubMarshaller = E(board).getPublishingMarshaller();

const InstallProposalShape = M.splitRecord({
give: { Fee: M.gte(prices.installBundleID) },
// TODO: want: { Started: StartedAmountPattern }
});

/**
* @param {ZCFSeat} seat
* @param {string} description
* @param {{ installation: unknown, instance?: unknown }} handles
*/
const depositHandles = async (seat, description, handles) => {
const handlesInDetails = zcf.makeInvitation(
noHandler,
description,
handles,
NoProposalShape,
);
const amt = await E(invitationIssuerP).getAmountOf(handlesInDetails);
await depositToSeat(
zcf,
seat,
{ Handles: amt },
{ Handles: handlesInDetails },
);
};

/**
* @param {ZCFSeat} seat
* @param {InstallOpts} opts
*/
const installHandler = async (seat, opts) => {
mustMatch(opts, InstallOptsShape);

atomicRearrange(
zcf,
harden([[seat, fees, { Fee: prices.installBundleID }]]),
);

const { bundleID, label } = opts;
const installation = await E(zoe).installBundleID(bundleID, label);

await depositHandles(seat, 'installed', { installation });
seat.exit();
return harden(`${opts.label} installed`);
};

const makeInstallInvitation = () =>
zcf.makeInvitation(
installHandler,
'install',
undefined,
InstallProposalShape,
);

// NOTE: opts could be moved to offerArgs to
// save one layer of closure, but
// this way makes the types more discoverable via publicFacet
Expand All @@ -161,12 +227,17 @@ export const start = (zcf, limitedPowers, _baggage) => {
...keys(opts.permit || {}).map(k => prices[k]),
]);

const StartProposalShape = M.splitRecord({
give: { Fee: M.gte(Fee) },
// TODO: want: { Started: StartedAmountPattern }
});

/** @param {ZCFSeat} seat */
const handleStart = async seat => {
atomicRearrange(zcf, harden([[seat, fees, { Fee }]]));
const installation = await ('installation' in opts
? opts.installation
: E(zoe).installBundleID(opts.bundleID));
: E(zoe).installBundleID(opts.bundleID, opts.instanceLabel));

const { issuerKeywordRecord, customTerms, privateArgs, instanceLabel } =
opts;
Expand All @@ -182,7 +253,7 @@ export const start = (zcf, limitedPowers, _baggage) => {
{ ...privateArgs, ...powers },
instanceLabel,
);
// WARNING: adminFacet is dropped
// TODO: WARNING: adminFacet is dropped
const { instance, creatorFacet } = it;

const itsTerms = await E(zoe).getTerms(instance);
Expand All @@ -196,26 +267,21 @@ export const start = (zcf, limitedPowers, _baggage) => {
await E(creatorFacet).initStorageNode(itsStorage);
}

const handlesInDetails = zcf.makeInvitation(
noHandler,
'started',
{ instance, installation },
NoProposalShape,
);
const amt = await E(invitationIssuerP).getAmountOf(handlesInDetails);
await depositToSeat(
zcf,
seat,
{ Started: amt },
{ Started: handlesInDetails },
);
await depositHandles(seat, 'started', { instance, installation });
seat.exit();
return harden({ invitationMakers: creatorFacet });
};
return zcf.makeInvitation(handleStart, 'start');

return zcf.makeInvitation(
handleStart,
'start',
undefined,
StartProposalShape,
);
};

const publicFacet = Far('PublicFacet', {
makeInstallInvitation,
makeStartInvitation,
});

Expand Down
154 changes: 63 additions & 91 deletions contract/src/launchIt.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,120 +18,92 @@ import { atomicRearrange } from '@agoric/zoe/src/contractSupport/index.js';

const { Fail, quote: q } = assert;

const KeywordShape = M.string();

/** @type {import('./types').ContractMeta} */
export const meta = {
customTermsShape: M.splitRecord(
{ name: M.string(), supplyQty: M.bigint() },
{ assetKind: AssetKindShape, displayInfo: DisplayInfoShape },
),
};
export const { customTermsShape } = meta;
/**
* @typedef {{
* name: Keyword,
* name: string,
* assetKind?: AssetKind,
* displayInfo?: DisplayInfo,
* }} LaunchOpts
* }} LaunchTerms
*/
const LaunchOptShape = M.splitRecord(
{ name: KeywordShape, supplyQty: M.bigint() },
{ assetKind: AssetKindShape, displayInfo: DisplayInfoShape },
);

const LaunchProposalShape = harden({
give: {},
want: { Deposit: AmountShape },
exit: {
afterDeadline: { timer: TimerServiceShape, deadline: TimestampRecordShape },
},
});

/**
* This contract is limited to fungible assets.
*
* TODO: charge for launching?
*
* @param {ZCF} zcf
* @param {ZCF<LaunchTerms>} zcf
* @param {unknown} _privateArgs
* @param {import('@agoric/vat-data').Baggage} baggage
*/
export const start = async (zcf, _privateArgs, baggage) => {
// TODO: consider moving minting to separate contract
// though... then we have the add issuer problem.

/**
* @typedef {{
* proposal: Proposal,
* mint: ZCFMint,
* seats: { creator: ZCFSeat, lockup: ZCFSeat, deposits: ZCFSeat },
* }} PoolDetail
*
*/

/**
* @param {ZCFSeat} creator
* @param {LaunchOpts} opts
* @throws if name is already used (ISSUE: how are folks supposed to know???)
*/
const launchHandler = async (creator, opts) => {
mustMatch(opts, LaunchOptShape);
const { name, assetKind = 'nat', displayInfo = {} } = opts;

const proposal = creator.getProposal();

// TODO: charge for launching?
const mint = await zcf.makeZCFMint(name, assetKind, displayInfo);

const { zcfSeat: lockup } = zcf.makeEmptySeatKit();
const { zcfSeat: deposits } = zcf.makeEmptySeatKit();
const { name, assetKind = 'nat', displayInfo = {}, brands } = zcf.getTerms();
mustMatch(brands, M.splitRecord({ Deposit: BrandShape }));
const { zcfSeat: deposits } = zcf.makeEmptySeatKit();

/** @type {PoolDetail} */
const detail = harden({
proposal,
mint,
seats: { creator, lockup, deposits },
});
const key = pools.getSize();
pools.init(key, detail);
// const invitationMakers = { TODO: {} };
// ISSUE: how does the brand get to the board so clients can make offers?
// ISSUE: how can clients make offers if issuer is not in agoricNames?
return key;
};

const zone = makeDurableZone(baggage);
const pools = zone.mapStore('pools', {
keyShape: M.number(),
// valueShape: PoolDetailShape,
const DepositAmountShape = { brand: brands.Deposit, value: M.nat() };
const SubscribeProposalShape = harden({
give: { Deposit: DepositAmountShape },
});

const makeSubscribeInvitation = poolKey => {
/** @type {PoolDetail} */
const pool = pools.get(poolKey);
const { deposits } = pool.seats;

/** @type {OfferHandler} */
const subscribeHandler = subscriber => {
const { give } = subscriber.getProposal();
atomicRearrange(zcf, [[subscriber, deposits, give]]);
};
/** @type {OfferHandler} */
const subscribeHandler = subscriber => {
const { give } = subscriber.getProposal();
atomicRearrange(zcf, [[subscriber, deposits, give]]);
};

const { Deposit } = pool.proposal.want;
const proposalShape = harden({
give: { Deposit: { brand: Deposit.brand, value: M.nat() } },
});
const makeSubscribeInvitation = () => {
return zcf.makeInvitation(
subscribeHandler,
'subscribe',
undefined,
proposalShape,
SubscribeProposalShape,
);
};

return {
publicFacet: Far('PF', {
makeLaunchInvitation: () =>
zcf.makeInvitation(
launchHandler,
'launch',
undefined,
LaunchProposalShape,
),
makeSubscribeInvitation,
}),
const publicFacet = Far('PF', { makeSubscribeInvitation });

const mint = await zcf.makeZCFMint(name, assetKind, displayInfo);
const { zcfSeat: lockup } = zcf.makeEmptySeatKit();

const LaunchProposalShape = M.splitRecord(
{
exit: {
afterDeadline: {
timer: TimerServiceShape,
deadline: TimestampRecordShape,
},
},
},
{
want: { Deposit: DepositAmountShape },
},
);

/**
* @param {ZCFSeat} creator
*/
const launchHandler = async creator => {
const proposal = creator.getProposal();

// const invitationMakers = { TODO: {} };
return name;
};

const creatorFacet = Far('CF', {
Launch: () =>
zcf.makeInvitation(
launchHandler,
'launch',
undefined,
LaunchProposalShape,
),
});

return { publicFacet, creatorFacet };
};
Loading

0 comments on commit 23ca000

Please sign in to comment.