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

document contract; rename to avoid "game" #38

Closed
wants to merge 2 commits into from
Closed
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
2 changes: 1 addition & 1 deletion contract/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"docker:make": "docker compose exec agd make -C /workspace/contract",
"make:help": "make list",
"start": "yarn docker:make clean start-contract print-key",
"build": "agoric run scripts/build-game1-start.js",
"build": "agoric run scripts/build-contract-start.js",
"test": "ava --verbose",
"lint": "eslint '**/*.js'",
"lint:fix": "eslint --fix '**/*.js'"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,19 @@
*/

import { makeHelpers } from '@agoric/deploy-script-support';
import { getManifestForGame1 } from '../src/start-game1-proposal.js';
import { getManifest } from '../src/start-offer-items.js';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const game1ProposalBuilder = async ({ publishRef, install }) => {
export const proposalBuilder = async ({ publishRef, install }) => {
return harden({
sourceSpec: '../src/start-game1-proposal.js',
sourceSpec: '../src/start-offer-items.js',
getManifestCall: [
getManifestForGame1.name,
getManifest.name,
{
game1Ref: publishRef(
install(
'../src/gameAssetContract.js',
'../bundles/bundle-game1.js',
{ persist: true },
),
offerItemsRef: publishRef(
install('../src/offerItems.js', '../bundles/bundle-offerItems.js', {
persist: true,
}),
),
},
],
Expand All @@ -30,5 +28,5 @@ export const game1ProposalBuilder = async ({ publishRef, install }) => {
/** @type {DeployScriptFunction} */
export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('start-game1', game1ProposalBuilder);
await writeCoreProposal('start-contract', proposalBuilder);
};
70 changes: 0 additions & 70 deletions contract/src/gameAssetContract.js

This file was deleted.

137 changes: 137 additions & 0 deletions contract/src/offerItems.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/**
* @file Contract to mint and sell a few Item NFTs at a time.
*
* We declare variables (including functions) before using them,
* so you may want to skip ahead and come back to some details.
* @see {start} for the main contract entrypoint
*
* @see {@link https://docs.agoric.com/guides/zoe/|Zoe Overview} for a walk-thru of this contract
* @see {@link https://docs.agoric.com/guides/js-programming/hardened-js.html|Hardened JavaScript}
* for background on `harden` and `assert`.
*/
// @ts-check

import { Far } from '@endo/far';
import { M, getCopyBagEntries, mustMatch } from '@endo/patterns';
import { AmountMath, AssetKind } from '@agoric/ertp/src/amountMath.js';
import { AmountShape } from '@agoric/ertp/src/typeGuards.js';
import { atomicRearrange } from '@agoric/zoe/src/contractSupport/atomicTransfer.js';
import '@agoric/zoe/exported.js';

const { Fail, quote: q } = assert;

// #region bag utilities
/** @type { (xs: bigint[]) => bigint } */
const sum = xs => xs.reduce((acc, x) => acc + x, 0n);

/**
* @param {import('@endo/patterns').CopyBag} bag
* @returns {bigint[]}
*/
const bagCounts = bag => {
const entries = getCopyBagEntries(bag);
return entries.map(([_k, ct]) => ct);
};
// #endregion
Comment on lines +23 to +35

Choose a reason for hiding this comment

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

#region doesn't seem helpful in this context. I'd drop the comments or move the code to an import.

Copy link
Member Author

Choose a reason for hiding this comment

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

feedback from @kbennett2000 was that #region is a well-known idiom from Visual Studio, so it was worthwhile. Do you two want to arm-wrestle and tell me how it comes out? :)

An import is what I might normally do, but that means the contract is no longer one file. That's OK if a walk-thru can assume readers grok sum() and bagCount() without looking at them. In fact, it could be a benefit to let folks know that they can split up their work.

Choose a reason for hiding this comment

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

Do you two want to arm-wrestle and tell me how it comes out?

I will cede the ground. @kbennett2000 knows more than I do about how developers read code.


/**
* This contract is parameterized by terms for price and,
* optionally, a maximum number of items sold for that price (default: 3).
*
* @typedef {{
* price: Amount;
* maxItems?: bigint;
* }} ItemTerms
*/

/**
* Start a contract that
* - creates a new non-fungible asset type for Items, and
* - handles offers to buy up to `maxItems` items at a time.
*
* As is typical in Zoe contracts, the flow is:
* 1. contract does internal setup
* 2. client uses a public facet method -- {@link makeBuyItemsInvitation} in this case --
* to make an invitation.
* 3. client makes an offer using the invitation, along with
* a proposal (with give and want) and payments. Zoe escrows the payments, and then
* 4. Zoe invokes the offer handler specified in step 2 -- here {@link buyItemsHandler}.
*
* @param {ZCF<ItemTerms>} zcf
*/
export const start = async zcf => {
const { price, maxItems = 3n } = zcf.getTerms();

Choose a reason for hiding this comment

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

In the 3n case, is the intent that price is enough for all three items, or were you expecting it to be price * quantity?

Copy link
Member Author

Choose a reason for hiding this comment

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

price is enough for all 3

// Static types aren't enforced on callers of start(),
// so use patterns to check supplied terms at runtime.
mustMatch(price, AmountShape);
mustMatch(maxItems, M.bigint());

/**
* a new ERTP mint for items, accessed thru the Zoe Contract Facet.
* Note: `makeZCFMint` makes the associated brand and issuer available
* in the contract's terms.
*
* AssetKind.COPY_BAG can express non-fungible (or rather: semi-fungible)
* amounts such as: 3 potions and 1 map.
*/
const itemMint = await zcf.makeZCFMint('Item', AssetKind.COPY_BAG);
const { brand: itemBrand } = itemMint.getIssuerRecord();

/**
* a pattern to constrain proposals given to {@link buyItemsHandler}
*
* The `Pay` amount must use the brand from the `price` term.
* The `Items` amount must use the `Item` brand and a bag value.
*/
const proposalShape = harden({
give: { Pay: { brand: price.brand, value: M.any() } },
want: { Items: { brand: itemBrand, value: M.bag() } },
exit: M.any(),
});

/** a seat for allocating proceeds of sales */
const proceeds = zcf.makeEmptySeatKit().zcfSeat;

/** @param {ZCFSeat} buyerSeat */
const buyItemsHandler = buyerSeat => {
// give and want are guaranteed by Zoe to match proposalShape
const { give, want } = buyerSeat.getProposal();

AmountMath.isGTE(give.Pay, price) ||
Fail`${q(give.Pay)} below price of ${q(price)}}`;

sum(bagCounts(want.Items.value)) <= maxItems ||
Fail`max ${q(maxItems)} items allowed: ${q(want.Items)}`;

const newItems = itemMint.mintGains(want);
atomicRearrange(
zcf,
harden([
// price from buyer to proceeds
[buyerSeat, proceeds, { Pay: price }],
// new items to buyer
[newItems, buyerSeat, want],
]),
);

buyerSeat.exit(true);
return 'trade complete';
};

/**
* Make an invitation to buy items.
*
* Proposal Keywords used in offers using these invitations:
* - give: `Pay`
* - want: `Items`
*/
const makeBuyItemsInvitation = () =>
zcf.makeInvitation(buyItemsHandler, 'buy items', undefined, proposalShape);

// Mark the publicFacet Far, i.e. reachable from outside the contract
const publicFacet = Far('Items Public Facet', {
makeBuyItemsInvitation,
});
return harden({ publicFacet });
};
harden(start);
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { E } from '@endo/far';
import { makeMarshal } from '@endo/marshal';
import { AmountMath } from '@agoric/ertp/src/amountMath.js';

console.warn('start-game1-proposal.js module evaluating');
console.warn('start-offer-items.js module evaluating');

const { Fail } = assert;

Expand Down Expand Up @@ -41,85 +41,84 @@ const publishBrandInfo = async (chainStorage, board, brand) => {
*
* @param {BootstrapPowers} permittedPowers
*/
export const startGameContract = async permittedPowers => {
console.error('startGameContract()...');
export const startOfferItemsContract = async permittedPowers => {
console.error('startOfferItemsContract()...');
const {
consume: { board, chainStorage, startUpgradable, zoe },
brand: {
consume: { IST: istBrandP },
// @ts-expect-error dynamic extension to promise space
produce: { Place: producePlaceBrand },
produce: { OfferItems: produceBrand },
},
issuer: {
consume: { IST: istIssuerP },
// @ts-expect-error dynamic extension to promise space
produce: { Place: producePlaceIssuer },
produce: { OfferItems: produceIssuer },
},
installation: {
consume: { game1: game1InstallationP },
consume: { offerItems: installationP },
},
instance: {
// @ts-expect-error dynamic extension to promise space
produce: { game1: produceInstance },
produce: { offerItems1: produceInstance },
},
} = permittedPowers;

const istIssuer = await istIssuerP;
const istBrand = await istBrandP;

// NOTE: joinPrice could be configurable
const terms = { joinPrice: AmountMath.make(istBrand, 25n * CENT) };
const terms = { price: AmountMath.make(istBrand, 25n * CENT) };

// agoricNames gets updated each time; the promise space only once XXXXXXX
const installation = await game1InstallationP;
const installation = await installationP;

const { instance } = await E(startUpgradable)({
installation,
issuerKeywordRecord: { Price: istIssuer },
label: 'game1',
issuerKeywordRecord: { Pay: istIssuer },
label: 'offer-items1',
terms,
});
console.log('CoreEval script: started game contract', instance);
console.log('CoreEval script: started offer-items contract', instance);
const {
brands: { Place: brand },
issuers: { Place: issuer },
brands: { Pay: brand },
issuers: { Item: issuer },
} = await E(zoe).getTerms(instance);

console.log('CoreEval script: share via agoricNames:', brand);

produceInstance.reset();
produceInstance.resolve(instance);

producePlaceBrand.reset();
producePlaceIssuer.reset();
producePlaceBrand.resolve(brand);
producePlaceIssuer.resolve(issuer);
produceBrand.reset();
produceIssuer.reset();
produceBrand.resolve(brand);
produceIssuer.resolve(issuer);

await publishBrandInfo(chainStorage, board, brand);
console.log('game1 (re)installed');
console.log('offerItems1 (re)started');
};

/** @type { import("@agoric/vats/src/core/lib-boot").BootstrapManifest } */
const gameManifest = {
[startGameContract.name]: {
const contractManifest = {
[startOfferItemsContract.name]: {
consume: {
agoricNames: true,
board: true, // to publish boardAux info for game NFT
chainStorage: true, // to publish boardAux info for game NFT
startUpgradable: true, // to start contract and save adminFacet
zoe: true, // to get contract terms, including issuer/brand
},
installation: { consume: { game1: true } },
issuer: { consume: { IST: true }, produce: { Place: true } },
brand: { consume: { IST: true }, produce: { Place: true } },
instance: { produce: { game1: true } },
installation: { consume: { offerItems: true } },
issuer: { consume: { IST: true }, produce: { OfferItems: true } },
brand: { consume: { IST: true }, produce: { OfferItems: true } },
instance: { produce: { offerItems1: true } },
},
};
harden(gameManifest);
harden(contractManifest);

export const getManifestForGame1 = ({ restoreRef }, { game1Ref }) => {
export const getManifest = ({ restoreRef }, { game1Ref }) => {
return harden({
manifest: gameManifest,
manifest: contractManifest,
installations: {
game1: restoreRef(game1Ref),
},
Expand Down
2 changes: 1 addition & 1 deletion contract/test/test-bundle-source.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { E, passStyleOf } from '@endo/far';
import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js';

const myRequire = createRequire(import.meta.url);
const contractPath = myRequire.resolve(`../src/gameAssetContract.js`);
const contractPath = myRequire.resolve(`../src/offerItems.js`);

test('bundleSource() bundles the contract for use with zoe', async t => {
const bundle = await bundleSource(contractPath);
Expand Down
Loading