From 7003b66021cd0b274ed3e1f9a085463a3293a849 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 29 Nov 2023 00:34:20 -0600 Subject: [PATCH 01/31] test(zoe): snippets for learning zoe lessons 1, 2, 3 --- snippets/zoe/contracts/test-zoe-hello.js | 32 ++++++++++++++++++++++++ snippets/zoe/src/01-hello.js | 16 ++++++++++++ snippets/zoe/src/02-state.js | 16 ++++++++++++ snippets/zoe/src/03-access.js | 20 +++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 snippets/zoe/contracts/test-zoe-hello.js create mode 100644 snippets/zoe/src/01-hello.js create mode 100644 snippets/zoe/src/02-state.js create mode 100644 snippets/zoe/src/03-access.js diff --git a/snippets/zoe/contracts/test-zoe-hello.js b/snippets/zoe/contracts/test-zoe-hello.js new file mode 100644 index 000000000..8fc76b137 --- /dev/null +++ b/snippets/zoe/contracts/test-zoe-hello.js @@ -0,0 +1,32 @@ +// TODO: convince prettier that harden() is a global +/* global harden */ + +import '@endo/init'; +import { E } from '@endo/far'; +import test from 'ava'; +import { start as startHello } from '../src/01-hello.js'; +import { start as startState } from '../src/02-state.js'; +import { start as startAccess } from '../src/03-access.js'; + +const mockZcf = harden({}); + +test('contract greet greets by name', async t => { + const { publicFacet } = startHello(mockZcf); + const actual = await E(publicFacet).greet('Bob'); + t.is(actual, 'Hello, Bob!'); +}); + +test('state', async t => { + const { publicFacet } = startState(mockZcf); + t.is(await E(publicFacet).get(), 'Hello, World!'); + await E(publicFacet).set(2); + t.is(await E(publicFacet).get(), 2); +}); + +test('access', async t => { + const { publicFacet, creatorFacet } = startAccess(mockZcf); + t.is(await E(publicFacet).get(), 'Hello, World!'); + await t.throwsAsync(E(publicFacet).set(2), { message: /no method/ }); + await E(creatorFacet).set(2); + t.is(await E(publicFacet).get(), 2); +}); diff --git a/snippets/zoe/src/01-hello.js b/snippets/zoe/src/01-hello.js new file mode 100644 index 000000000..d8ea3ffb5 --- /dev/null +++ b/snippets/zoe/src/01-hello.js @@ -0,0 +1,16 @@ +import { Far } from '@endo/far'; + +// A Zoe contract is a module that exports a start function +// that defines the contract's API. +export const start = _z => { + // publicFacet provides public API methods + // Mark it Far() to allow callers from outside the contract + // and give it a suggestive interface name for debugging. + const publicFacet = Far('Hello', { + greet: who => { + return `Hello, ${who}!`; + }, + }); + + return { publicFacet }; +}; diff --git a/snippets/zoe/src/02-state.js b/snippets/zoe/src/02-state.js new file mode 100644 index 000000000..d0eeb4395 --- /dev/null +++ b/snippets/zoe/src/02-state.js @@ -0,0 +1,16 @@ +import { Far } from '@endo/far'; + +export const start = _z => { + // Contracts can use ordinary variables for state + // that lasts between transactions. + let value = 'Hello, World!'; + const publicFacet = Far('ValueCell', { + get: () => value, + set: v => (value = v), + }); + + return { publicFacet }; +}; + +// p.s. Fits in a tweet! +// https://twitter.com/DeanTribble/status/1433268983977820161 diff --git a/snippets/zoe/src/03-access.js b/snippets/zoe/src/03-access.js new file mode 100644 index 000000000..4bda18ed2 --- /dev/null +++ b/snippets/zoe/src/03-access.js @@ -0,0 +1,20 @@ +import { Far } from '@endo/far'; + +export const start = _z => { + let value = 'Hello, World!'; + + // We can limit the public API to read-only + // by omitting the set() method + const publicFacet = Far('ValueView', { + get: () => value, + }); + + // The creatorFacet is provided only to the + // caller of E(zoe).startInstance() + const creatorFacet = Far('ValueCell', { + get: () => value, + set: v => (value = v), + }); + + return { publicFacet, creatorFacet }; +}; From eb25aac95eda4169beb561d1bff1514579b100d6 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 29 Nov 2023 01:31:45 -0600 Subject: [PATCH 02/31] docs: contract basics: hello, testing, state, access control - don't show region comments - reference to Zoe is just a distraction - index start later - fix test name - don't mention snippets - connect to surrounding sections - link fixes --- main/.vuepress/config.js | 1 + main/guides/zoe/contract-basics.md | 84 ++++++++++++++++++++++++ snippets/zoe/contracts/test-zoe-hello.js | 22 ++++--- snippets/zoe/src/01-hello.js | 25 +++---- snippets/zoe/src/02-state.js | 19 +++--- snippets/zoe/src/03-access.js | 22 ++----- 6 files changed, 127 insertions(+), 46 deletions(-) create mode 100644 main/guides/zoe/contract-basics.md diff --git a/main/.vuepress/config.js b/main/.vuepress/config.js index 4ad254db7..11774016a 100644 --- a/main/.vuepress/config.js +++ b/main/.vuepress/config.js @@ -170,6 +170,7 @@ module.exports = { collapsible: false, children: [ '/guides/getting-started/', + '/guides/getting-started/contract-basics.html', '/guides/getting-started/contract-rpc.html', '/guides/getting-started/deploying.html', '/guides/getting-started/syncing-up.html', diff --git a/main/guides/zoe/contract-basics.md b/main/guides/zoe/contract-basics.md new file mode 100644 index 000000000..e770fd75d --- /dev/null +++ b/main/guides/zoe/contract-basics.md @@ -0,0 +1,84 @@ +# Smart Contract Basics + +Before we look at how to make a contract such as the one in [the +basic dapp](./) in the previous section, let's cover some basics. + +A contract is a JavaScript module that exports a `start` function +that defines the contract's API. + +<<< @/snippets/zoe/src/01-hello.js#start + +Let's start with a contract with a simple `greet` function: + +<<< @/snippets/zoe/src/01-hello.js#greet + +The `start` function can expose the `greet` function +as part of the contract API by making it +a method of the contract's `publicFacet`: + +<<< @/snippets/zoe/src/01-hello.js#publicFacet + +We mark it `Far(...)` to allow callers from outside the contract +and give it a suggestive interface name for debugging. +_We'll discuss [Far in more detail](../js-programming/far.md) later._ + +Putting it all together: + +<<< @/snippets/zoe/src/01-hello.js#contract + +## Using, testing a contract + +Let's use some tests to explore how a contract is used. + +Agoric contracts are typically tested +using the [ava](https://github.com/avajs/ava) framework. +They start with `@endo/init` to establish a [Hardened JavaScript](../js-programming/hardened-js.md) environment: + +<<< @/snippets/zoe/contracts/test-zoe-hello.js#test-imports + +_We'll talk more about [using `E()` for async method calls](../js-programming/eventual-send.md) later._ + +A test that the `greet` method works as expected looks like: + +<<< @/snippets/zoe/contracts/test-zoe-hello.js#test1 + +## State + +Contracts can use ordinary variables for state. + +<<< @/snippets/zoe/src/02-state.js#startfn + +Using `set` changes the results of the following call to `get`: + +<<< @/snippets/zoe/contracts/test-zoe-hello.js#test-state + +::: tip Heap state is persistent + +Ordinary heap state persists between contract invocations. + +We'll discuss more explicit state management for +large numbers of objects (_virtual objects_) and +objects that last across upgrades (_durable objects_) later. + +::: + +## Access Control with Objects + +We can limit the `publicFacet` API to read-only by omitting the `set()` method. + +The `creatorFacet` is provided only to the caller who creates the contract instance. + +<<< @/snippets/zoe/src/03-access.js + +Trying to `set` using the `publicFacet` throws, but +using the `creatorFacet` works: + +<<< @/snippets/zoe/contracts/test-zoe-hello.js#test-access + +Note that the `set()` method has no access check inside it. +Access control is based on separation of powers between +the `publicFacet`, which is expected to be shared widely, +and the `creatorFacet`, which is closely held. +_We'll discuss this [object capabilities](../js-programming/hardened-js.md#object-capabilities-ocaps) approach more later._ + +Next, let's look at minting and trading assets with [Zoe](../zoe/). diff --git a/snippets/zoe/contracts/test-zoe-hello.js b/snippets/zoe/contracts/test-zoe-hello.js index 8fc76b137..950c6413f 100644 --- a/snippets/zoe/contracts/test-zoe-hello.js +++ b/snippets/zoe/contracts/test-zoe-hello.js @@ -1,32 +1,38 @@ // TODO: convince prettier that harden() is a global /* global harden */ +// #region test-imports import '@endo/init'; import { E } from '@endo/far'; import test from 'ava'; -import { start as startHello } from '../src/01-hello.js'; +// #endregion test-imports import { start as startState } from '../src/02-state.js'; import { start as startAccess } from '../src/03-access.js'; +// #region test1 +import { start } from '../src/01-hello.js'; -const mockZcf = harden({}); - -test('contract greet greets by name', async t => { - const { publicFacet } = startHello(mockZcf); +test('contract greets by name', async t => { + const { publicFacet } = start(); const actual = await E(publicFacet).greet('Bob'); t.is(actual, 'Hello, Bob!'); }); +// #endregion test1 +// #region test-state test('state', async t => { - const { publicFacet } = startState(mockZcf); + const { publicFacet } = startState(); t.is(await E(publicFacet).get(), 'Hello, World!'); await E(publicFacet).set(2); t.is(await E(publicFacet).get(), 2); }); +// #endregion test-state -test('access', async t => { - const { publicFacet, creatorFacet } = startAccess(mockZcf); +// #region test-access +test('access control', async t => { + const { publicFacet, creatorFacet } = startAccess(); t.is(await E(publicFacet).get(), 'Hello, World!'); await t.throwsAsync(E(publicFacet).set(2), { message: /no method/ }); await E(creatorFacet).set(2); t.is(await E(publicFacet).get(), 2); }); +// #endregion test-access diff --git a/snippets/zoe/src/01-hello.js b/snippets/zoe/src/01-hello.js index d8ea3ffb5..8353e87cd 100644 --- a/snippets/zoe/src/01-hello.js +++ b/snippets/zoe/src/01-hello.js @@ -1,16 +1,17 @@ +// #region contract import { Far } from '@endo/far'; -// A Zoe contract is a module that exports a start function -// that defines the contract's API. -export const start = _z => { - // publicFacet provides public API methods - // Mark it Far() to allow callers from outside the contract - // and give it a suggestive interface name for debugging. - const publicFacet = Far('Hello', { - greet: who => { - return `Hello, ${who}!`; - }, - }); +// #region greet +const greet = who => `Hello, ${who}!`; +// #endregion greet - return { publicFacet }; +// #region start +export const start = () => { + // #endregion start + // #region publicFacet + return { + publicFacet: Far('Hello', { greet }), + }; + // #endregion publicFacet }; +// #endregion contract diff --git a/snippets/zoe/src/02-state.js b/snippets/zoe/src/02-state.js index d0eeb4395..487d1643d 100644 --- a/snippets/zoe/src/02-state.js +++ b/snippets/zoe/src/02-state.js @@ -1,16 +1,13 @@ import { Far } from '@endo/far'; -export const start = _z => { - // Contracts can use ordinary variables for state - // that lasts between transactions. +// #region startfn +export const start = () => { let value = 'Hello, World!'; - const publicFacet = Far('ValueCell', { - get: () => value, - set: v => (value = v), - }); + const get = () => value; + const set = v => (value = v); - return { publicFacet }; + return { + publicFacet: Far('ValueCell', { get, set }), + }; }; - -// p.s. Fits in a tweet! -// https://twitter.com/DeanTribble/status/1433268983977820161 +// #endregion startfn diff --git a/snippets/zoe/src/03-access.js b/snippets/zoe/src/03-access.js index 4bda18ed2..6a019c648 100644 --- a/snippets/zoe/src/03-access.js +++ b/snippets/zoe/src/03-access.js @@ -1,20 +1,12 @@ import { Far } from '@endo/far'; -export const start = _z => { +export const start = () => { let value = 'Hello, World!'; + const get = () => value; + const set = v => (value = v); - // We can limit the public API to read-only - // by omitting the set() method - const publicFacet = Far('ValueView', { - get: () => value, - }); - - // The creatorFacet is provided only to the - // caller of E(zoe).startInstance() - const creatorFacet = Far('ValueCell', { - get: () => value, - set: v => (value = v), - }); - - return { publicFacet, creatorFacet }; + return { + publicFacet: Far('ValueView', { get }), + creatorFacet: Far('ValueCell', { get, set }), + }; }; From 4ca123ee77eed132437532d7632283c7ae9d095d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 6 Dec 2023 05:08:39 -0600 Subject: [PATCH 03/31] docs(zoe): markdown lint --- main/guides/zoe/README.md | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 9d63c9cea..fb6e124e5 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -4,22 +4,22 @@ Zoe is Agoric's smart contract framework. Use Zoe to: -* **Run your code on-chain** -* **Mint new digital assets** -* **Credibly trade assets** +- **Run your code on-chain** +- **Mint new digital assets** +- **Credibly trade assets** ## Why Use Zoe? -### For Users ### +### For Users **Zoe is safer.** Traditionally, putting digital assets in a smart contract has carried the risk of losing them. But Zoe guarantees you get either what you wanted or a full refund of the assets you put in. You will never leave a smart contract empty-handed, even if it is buggy or malicious. -### For Developers ### +### For Developers -**Zoe is easier.** Traditionally, writing a smart contract meant +**Zoe is easier.** Traditionally, writing a smart contract meant learning a new, untried language. And don't make any mistakes - if you do, your users might lose millions. @@ -32,11 +32,12 @@ users to lose their assets.** Agoric has written [a number of example contracts that you can use](/zoe/guide/contracts/), including: -* an [Automated Market Maker (AMM) + +- an [Automated Market Maker (AMM) implementation](/zoe/guide/contracts/constantProductAMM.md) -* a [covered call option contract](./contracts/covered-call.md) -* an [OTC Desk market maker contract](./contracts/otc-desk.md) -* contracts for [minting fungible](./contracts/mint-payments.md) and +- a [covered call option contract](./contracts/covered-call.md) +- an [OTC Desk market maker contract](./contracts/otc-desk.md) +- contracts for [minting fungible](./contracts/mint-payments.md) and [non-fungible tokens](./contracts/mint-and-sell-nfts.md) ## Using an Example Zoe Smart Contract @@ -100,6 +101,7 @@ code directly by calling: In most cases, the bundle contains a base64-encoded zip file that you can extract for review: + ```sh echo "$endoZipBase64" | base64 -d > bundle.zip unzip bundle.zip @@ -117,9 +119,10 @@ You've successfully checked out the invitation, so now you can make an offer. An offer has three required parts: -* a Zoe invitation -* a proposal -* a [payment](/guides/ertp/purses-and-payments.md#payments) containing + +- a Zoe invitation +- a proposal +- a [payment](/guides/ertp/purses-and-payments.md#payments) containing the digital assets you're offering to swap The `proposal` states what you want from the offer, and what you will @@ -273,7 +276,7 @@ parameter. This is the Zoe Contract Facet. Zoe has two sides: the Zoe Service, which you've seen users interact with, and the Zoe Contract Facet (ZCF), which is accessible to the contract code. Note that users have access to the Zoe Service, but do not have access to ZCF. -Contract code has access to ZCF *and* can get access to the Zoe +Contract code has access to ZCF _and_ can get access to the Zoe Service. To learn more about the Zoe Service, Zoe Contract Facet, and Zoe From 68f631a2d58430d55a10c301db473b708d6592b4 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 6 Dec 2023 05:10:43 -0600 Subject: [PATCH 04/31] docs(sidebar): begin reorg: Zoe comes after Getting Started, etc. --- main/.vuepress/config.js | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/main/.vuepress/config.js b/main/.vuepress/config.js index 11774016a..2b8b91736 100644 --- a/main/.vuepress/config.js +++ b/main/.vuepress/config.js @@ -169,13 +169,26 @@ module.exports = { path: '/guides/getting-started/', collapsible: false, children: [ - '/guides/getting-started/', - '/guides/getting-started/contract-basics.html', + '/guides/getting-started/' '/guides/getting-started/contract-rpc.html', '/guides/getting-started/deploying.html', '/guides/getting-started/syncing-up.html', ], }, + { + title: 'Zoe', + path: '/guides/zoe/', + collapsible: false, + children: [ + '/guides/getting-started/contract-basics.html', + '/guides/zoe/', + '/guides/zoe/offer-enforcement.html', + '/guides/zoe/offer-safety.html', + '/guides/zoe/proposal.html', + '/guides/zoe/contract-requirements.html', + '/guides/zoe/price-authority.html', + ], + }, { title: 'Agoric CLI', path: '/guides/agoric-cli/', @@ -215,19 +228,6 @@ module.exports = { '/guides/ertp/purses-and-payments.html', ], }, - { - title: 'Zoe', - path: '/guides/zoe/', - collapsible: false, - children: [ - '/guides/zoe/', - '/guides/zoe/offer-enforcement.html', - '/guides/zoe/offer-safety.html', - '/guides/zoe/proposal.html', - '/guides/zoe/contract-requirements.html', - '/guides/zoe/price-authority.html', - ], - }, { title: 'Permissioned Contract Deployment', path: '/guides/coreeval/', @@ -282,6 +282,8 @@ module.exports = { '/guides/dapps/', '/guides/dapps/dapp-templates.html', '/guides/dapps/starting-multiuser-dapps.html', + '/guides/getting-started/deploying.html', + '/guides/getting-started/contract-rpc.html', ], }, { From ddcf19c926dab7562fc84c2f0cbb0f91f2270284 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 8 Dec 2023 11:36:39 -0600 Subject: [PATCH 05/31] chore: update sidebar for contract-basics --- main/.vuepress/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/.vuepress/config.js b/main/.vuepress/config.js index 2b8b91736..1277d604e 100644 --- a/main/.vuepress/config.js +++ b/main/.vuepress/config.js @@ -180,7 +180,7 @@ module.exports = { path: '/guides/zoe/', collapsible: false, children: [ - '/guides/getting-started/contract-basics.html', + '/guides/zoe/contract-basics.html', '/guides/zoe/', '/guides/zoe/offer-enforcement.html', '/guides/zoe/offer-safety.html', From 12915988d37791852cae50fec0e1f826b369922c Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 6 Dec 2023 05:09:01 -0600 Subject: [PATCH 06/31] docs: distinguish Zoe from the framework --- main/guides/zoe/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index fb6e124e5..b06cd92d3 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -2,7 +2,7 @@ ## What is Zoe? -Zoe is Agoric's smart contract framework. Use Zoe to: +Zoe is the heart of Agoric's smart contract framework. Use Zoe to: - **Run your code on-chain** - **Mint new digital assets** From 36db0b5bfe41d85512fa6fa8e58910e662b75b1d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 02:53:21 -0600 Subject: [PATCH 07/31] chore: copy gameAssetContract, test-alice from dapp-game-places - debug.js too - refactor(test-alice-trade): rename pmt to Place to match keyword, resulting in short property form that's handy in a diagram - refactor: postpone discussion of harden returning a fresh { ... } record does not put a function at risk. - hide region markers in full listing add whole-file region to suppress others --- snippets/zoe/contracts/test-alice-trade.js | 50 ++++++++++++++ snippets/zoe/src/debug.js | 41 +++++++++++ snippets/zoe/src/gameAssetContract.js | 80 ++++++++++++++++++++++ 3 files changed, 171 insertions(+) create mode 100644 snippets/zoe/contracts/test-alice-trade.js create mode 100644 snippets/zoe/src/debug.js create mode 100644 snippets/zoe/src/gameAssetContract.js diff --git a/snippets/zoe/contracts/test-alice-trade.js b/snippets/zoe/contracts/test-alice-trade.js new file mode 100644 index 000000000..f09c4a95c --- /dev/null +++ b/snippets/zoe/contracts/test-alice-trade.js @@ -0,0 +1,50 @@ +/** + * @file Test using bundleSource() on the contract. + * + * TODO: how to sync with agoric-labs/dapp-game-places? + */ +// @ts-check +import { E } from '@endo/far'; +import { makeCopyBag } from '@endo/patterns'; +import { AmountMath } from '@agoric/ertp'; + +/** + * Alice joins the game by paying the price from the contract's terms. + * + * @param {import('ava').ExecutionContext} t + * @param {ZoeService} zoe + * @param {ERef>} instance + * @param {Purse} purse + */ +const alice = async (t, zoe, instance, purse) => { + // #region queryInstance + const publicFacet = E(zoe).getPublicFacet(instance); + const terms = await E(zoe).getTerms(instance); + const { issuers, brands, joinPrice } = terms; + // #endregion queryInstance + + // #region makeProposal + const choices = ['Park Place', 'Boardwalk']; + const choiceBag = makeCopyBag(choices.map(name => [name, 1n])); + const proposal = { + give: { Price: joinPrice }, + want: { Places: AmountMath.make(brands.Place, choiceBag) }, + }; + const Price = await E(purse).withdraw(joinPrice); + t.log('Alice gives', proposal.give); + // #endregion makeProposal + + // #region trade + const toJoin = E(publicFacet).makeJoinInvitation(); + + const seat = E(zoe).offer(toJoin, proposal, { Price }); + const places = await E(seat).getPayout('Places'); + // #endregion trade + + // #region payouts + const actual = await E(issuers.Place).getAmountOf(places); + t.log('Alice payout brand', actual.brand); + t.log('Alice payout value', actual.value); + t.deepEqual(actual, proposal.want.Places); + // #endregion payouts +}; diff --git a/snippets/zoe/src/debug.js b/snippets/zoe/src/debug.js new file mode 100644 index 000000000..fb502a564 --- /dev/null +++ b/snippets/zoe/src/debug.js @@ -0,0 +1,41 @@ +// @jessie-check + +let debugInstance = 1; + +/** + * @param {string} name + * @param {boolean | 'verbose'} enable + */ +export const makeTracer = (name, enable = true) => { + debugInstance += 1; + let debugCount = 1; + const key = `----- ${name}.${debugInstance} `; + // the cases below define a named variable to provide better debug info + switch (enable) { + case false: { + const logDisabled = (..._args) => {}; + return logDisabled; + } + case 'verbose': { + const infoTick = (optLog, ...args) => { + if (optLog.log) { + console.info(key, (debugCount += 1), ...args); + } else { + console.info(key, (debugCount += 1), optLog, ...args); + } + }; + return infoTick; + } + default: { + const debugTick = (optLog, ...args) => { + if (optLog.log) { + optLog.log(key, (debugCount += 1), ...args); + } else { + console.info(key, (debugCount += 1), optLog, ...args); + } + }; + return debugTick; + } + } +}; +harden(makeTracer); diff --git a/snippets/zoe/src/gameAssetContract.js b/snippets/zoe/src/gameAssetContract.js new file mode 100644 index 000000000..80ad5b174 --- /dev/null +++ b/snippets/zoe/src/gameAssetContract.js @@ -0,0 +1,80 @@ +// #region file +/** @file Contract to mint and sell Place NFTs for a hypothetical game. */ +// @ts-check + +import { Far } from '@endo/far'; +import { M, getCopyBagEntries } 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'; + +import { makeTracer } from './debug.js'; + +const { Fail, quote: q } = assert; + +const trace = makeTracer('Game', true); + +/** @param {Amount<'copyBag'>} amt */ +const bagValueSize = amt => { + /** @type {[unknown, bigint][]} */ + const entries = getCopyBagEntries(amt.value); // XXX getCopyBagEntries returns any??? + const total = entries.reduce((acc, [_place, qty]) => acc + qty, 0n); + return total; +}; + +/** + * @param {ZCF<{joinPrice: Amount}>} zcf + */ +// #region start +export const start = async zcf => { + const { joinPrice } = zcf.getTerms(); + + const { zcfSeat: gameSeat } = zcf.makeEmptySeatKit(); + const mint = await zcf.makeZCFMint('Place', AssetKind.COPY_BAG); + // #endregion start + + // #region handler + /** @param {ZCFSeat} playerSeat */ + const joinHandler = playerSeat => { + const { give, want } = playerSeat.getProposal(); + trace('join', 'give', give, 'want', want.Places.value); + + AmountMath.isGTE(give.Price, joinPrice) || + Fail`${q(give.Price)} below joinPrice of ${q(joinPrice)}}`; + + bagValueSize(want.Places) <= 3n || Fail`only 3 places allowed when joining`; + + const tmp = mint.mintGains(want); + atomicRearrange( + zcf, + harden([ + [playerSeat, gameSeat, give], + [tmp, playerSeat, want], + ]), + ); + + playerSeat.exit(true); + return 'welcome to the game'; + }; + // #endregion handler + + // #region makeInvitation + const joinShape = harden({ + give: { Price: AmountShape }, + want: { Places: AmountShape }, + exit: M.any(), + }); + + const publicFacet = Far('API', { + makeJoinInvitation: () => + zcf.makeInvitation(joinHandler, 'join', undefined, joinShape), + }); + // #endregion makeInvitation + + // #region started + return { publicFacet }; + // #endregion started +}; +harden(start); +// #endregion file From d58b5ce538bf73adade657d85e122ce2c9274f70 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 8 Dec 2023 17:15:36 -0600 Subject: [PATCH 08/31] build: ERTP --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 311a1738f..dd72b045b 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "homepage": "https://github.com/Agoric/documentation#readme", "dependencies": { "@agoric/zoe": "beta", + "@agoric/ertp": "beta", "@endo/far": "^0.2.19", "@endo/marshal": "^0.8.6", "@endo/patterns": "^0.2.3", From 4aa44ec67b928c7e8fbb77ca02510c68dae3e066 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 6 Dec 2023 05:06:29 -0600 Subject: [PATCH 09/31] test: test-bundle-source.js with regions --- snippets/zoe/contracts/test-bundle-source.js | 37 ++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 snippets/zoe/contracts/test-bundle-source.js diff --git a/snippets/zoe/contracts/test-bundle-source.js b/snippets/zoe/contracts/test-bundle-source.js new file mode 100644 index 000000000..ae7258ee9 --- /dev/null +++ b/snippets/zoe/contracts/test-bundle-source.js @@ -0,0 +1,37 @@ +/** + * @file Test using bundleSource() on the contract. + */ +// @ts-check + +/* eslint-disable import/order -- https://github.com/endojs/endo/issues/1235 */ +import { test } from '../../prepare-test-env-ava.js'; + +// #region bundleSourceImports +import bundleSource from '@endo/bundle-source'; +import { createRequire } from 'module'; +// #endregion bundleSourceImports +import { E } from '@endo/far'; +// #region importZoeForTest +import { makeZoeKitForTest } from '@agoric/zoe/tools/setup-zoe.js'; +// #endregion importZoeForTest + +// #region contractPath +const myRequire = createRequire(import.meta.url); +const contractPath = myRequire.resolve(`../src/gameAssetContract.js`); +// #endregion contractPath + +test('bundleSource() bundles the contract for use with zoe', async t => { + // #region testBundleSource + const bundle = await bundleSource(contractPath); + t.is(bundle.moduleFormat, 'endoZipBase64'); + t.log(bundle.endoZipBase64Sha512); + t.true(bundle.endoZipBase64.length > 10_000); + // #endregion testBundleSource + + // #region testInstall + const { zoeService: zoe } = await makeZoeKitForTest(); + const installation = await E(zoe).install(bundle); + t.log(installation); + t.is(typeof installation, 'object'); + // #endregion testInstall +}); From 7901a07cc02b2647f625b0329680f6a14dce4bd4 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 6 Dec 2023 05:09:28 -0600 Subject: [PATCH 10/31] docs(zoe): Bundling, Installation, Starting w/basic dapp sections that are starting to take shape: - Bundling a Contract Get this housekeeping aspect out of the way so that further tests can ignore it. - Contract Installation - Starting a contract --- main/guides/zoe/README.md | 121 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index b06cd92d3..55e46a763 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -28,6 +28,124 @@ Moreover, Zoe automatically escrows all user digital assets and handles their subsequent payout. **Even a buggy contract can't cause users to lose their assets.** +::: warning TODO tone down the claims above + +::: + +## Bundling a Contract + +Recall from [deploying the basic dapp contract](../getting-started/#deploy-the-contract) +that the first step was to _bundle_ all if its modules into a single artifact. +We used the [agoric run](../agoric-cli/#agoric-run) command in that case. +The core mechanism used in `agoric run` is a call to `bundleSource()`. + +In the `contract` directory of the +[basic dapp project structure](../getting-started/#project-structure), +run `test-bundle-source.js` following `ava` conventions: + +```sh +cd contract +yarn ava test/test-bundle-source.js +``` + +The results look something like... + +```console + ✔ bundleSource() bundles the contract for use with zoe (2.7s) + ℹ 1e1aeca9d3ebc0bd39130fe5ef6fbb077177753563db522d6623886da9b43515816df825f7ebcb009cbe86dcaf70f93b9b8595d1a87c2ab9951ee7a32ad8e572 + ℹ Object @Alleged: BundleInstallation {} + ─ + + 1 test passed +``` + +::: details Test Setup + +The test uses `createRequire` from the node `module` API to resolve the main module specifier: + +<<< @/snippets/zoe/contracts/test-bundle-source.js#bundleSourceImports + +<<< @/snippets/zoe/contracts/test-bundle-source.js#contractPath +::: + +`bundleSource()` returns a bundle object with `moduleFormat`, a hash, and the contents: + +<<< @/snippets/zoe/contracts/test-bundle-source.js#testBundleSource{1} + +## Contract Installation + +To identify the code of contracts that parties consent to participate in, Zoe +uses _Installation_ objects. Let's try it: + +```sh +yarn ava test/test-contract.js -m 'Install the contract' +``` + +``` + ✔ Install the contract + ℹ Object @Alleged: BundleInstallation {} +``` + +::: details Test Setup + +The test starts by using `makeZoeKitForTest` to set up zoe for testing: + +<<< @/snippets/zoe/contracts/test-bundle-source.js#importZoeForTest + +```js +const { zoeService: zoe } = makeZoeKitForTest(); +``` + +::: + +Using a bundle as in the previous section: + +```js{1} +const installation = await E(zoe).install(bundle); +t.log(installation); +t.is(typeof installation, 'object'); +``` + +## Starting a contract + +Now we're ready for the to start an instance of the contract: + +```sh +yarn ava test/test-contract.js -m 'Start the contract' +``` + +``` + ✔ Start the contract (652ms) + ℹ terms: { + joinPrice: { + brand: Object @Alleged: PlayMoney brand {}, + value: 5n, + }, + } + ℹ Object @Alleged: InstanceHandle {} +``` + +Contracts can be parameterized by _terms_. +The price of joining the game is not fixed in the source code of this contract, +but rather chosen when starting an _instance_ of the contract. +Likewise, when starting an instance, we can choose which asset _issuers_ +the contract should use for its business: + +```js +const money = makeIssuerKit('PlayMoney'); +const issuers = { Price: money.issuer }; +const terms = { joinPrice: AmountMath.make(money.brand, 5n) }; +t.log('terms:', terms); + +/** @type {ERef>} */ +const installation = E(zoe).install(bundle); +const { instance } = await E(zoe).startInstance(installation, issuers, terms); +t.log(instance); +t.is(typeof instance, 'object'); +``` + +_`makeIssuerKit` and `AmountMath.make` are covered in the [ERTP](../ertp/) section._ + ### Contracts on Zoe Agoric has written [a number of example contracts that you can @@ -103,6 +221,9 @@ In most cases, the bundle contains a base64-encoded zip file that you can extract for review: ```sh +@@@@@@ +jq -r .endoZipBase64 bundle-game1aginst.json | base64 -d >game1aginst.zip + echo "$endoZipBase64" | base64 -d > bundle.zip unzip bundle.zip ``` From 70b3fc25618cfb0546ce4b824cb59e954eca4f62 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 02:54:57 -0600 Subject: [PATCH 11/31] docs: Trading with Offer Safety --- main/guides/zoe/README.md | 98 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 55e46a763..4a64d76a7 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -144,7 +144,103 @@ t.log(instance); t.is(typeof instance, 'object'); ``` -_`makeIssuerKit` and `AmountMath.make` are covered in the [ERTP](../ertp/) section._ +_`makeIssuerKit` and `AmountMath.make` are covered in the [ERTP](../ertp/) section, along with `makeEmptyPurse`, `mintPayment`, and `getAmountOf` below._ + +Let's take a look at what happens in the contract when it starts. A _facet_ of Zoe, the _Zoe Contract Facet_, is passed to the contract `start` function. +The contract uses this `zcf` to get its terms. Likewise it uses `zcf` to +make a `gameSeat` where it can store assets that it receives in trade +as well as a `mint` for making assets consisting of collections (bags) of Places: + +<<< @/snippets/zoe/contracts/gameAssetContract.js#start + +It defines a `joinShape`, `joinHandler` but doesn't do anything with them yet. They will come into play later. It defines and returns its `publicFacet` and stands by. + +<<< @/snippets/zoe/contracts/gameAssetContract.js#started + +## Trading with Offer Safety + +Now let's try trading: + +```sh +yarn ava test/test-contract.js -m 'Alice trades*' +``` + +``` + ✔ Alice trades: give some play money, want some game places (674ms) + ℹ Object @Alleged: InstanceHandle {} + ℹ Alice gives { + Price: { + brand: Object @Alleged: PlayMoney brand {}, + value: 5n, + }, + } + ℹ Alice payout brand Object @Alleged: Place brand {} + ℹ Alice payout value Object @copyBag { + payload: [ + [ + 'Park Place', + 1n, + ], + [ + 'Boardwalk', + 1n, + ], + ], + } +``` + +We start by putting some money in a purse for Alice: + +```js +const alicePurse = money.issuer.makeEmptyPurse(); +const amountOfMoney = AmountMath.make(money.brand, 10n); +const moneyPayment = money.mint.mintPayment(amountOfMoney); +alicePurse.deposit(moneyPayment); +``` + +Then we pass the contract instance and the purse to our code for `alice`: + +```js +await alice(t, zoe, instance, alicePurse); +``` + +Alice starts by using the `instance` to get the contract's `publicFacet` and `terms` from Zoe: + +<<< @/snippets/zoe/contracts/test-alice-trade.js#queryInstance + +Then she constructs a _proposal_ to give the `joinPrice` in exchange +for 1 Park Place and 1 Boardwalk, denominated in the game's `Place` brand; and she withdraws a payment from her purse: + +<<< @/snippets/zoe/contracts/test-alice-trade.js#makeProposal + +She then requests an _invitation_ to join the game; makes an _offer_ with this invitation, her proposal, and her payment; and awaits her Place payout: + +<<< @/snippets/zoe/contracts/test-alice-trade.js#trade + +The contract gets Alice's `E(publicFacet).makeJoinInvitation()` call and uses `zcf` to make an invitation with an associated handler, description, and proposal shape. Zoe gets Alice's `E(zoe).offer(...)` call, checks the proposal against the proposal shape, escrows the payment, and invokes the handler. + +<<< @/snippets/zoe/contracts/gameAssetContract.js#makeInvitation + +The handler is invoked with a _seat_ representing the party making the offer. +It extracts the `give` and `want` from the party's offer and checks that +they are giving at least the `joinPrice` and not asking for too many +places in return. + +With all these prerequisites met, it instructs `zcf` to mint the requested +place assets, allocate what the player is giving into its own seat, +and allocate the minted places to the player. Finally, it concludes its business with the player. + +<<< @/snippets/zoe/contracts/gameAssetContract.js#handler + +Zoe checks that the contract's instructions are consistent with +the offer and with conservation of assets. Then it allocates +the escrowed payment to the contract's gameSeat and pays out +the place NFTs to Alice. + +Alice asks the `Place` issuer what her payout is worth +and tests that it's what she wanted. + +<<< @/snippets/zoe/contracts/test-alice-trade.js#payouts ### Contracts on Zoe From a2b406334673e118f59dc7d553f0427b8b3ede3b Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 03:13:50 -0600 Subject: [PATCH 12/31] docs(zoe): prune material covered by current approach - rescue a few bits --- main/guides/zoe/README.md | 280 ++++++-------------------------------- 1 file changed, 38 insertions(+), 242 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 4a64d76a7..bf38c8f9f 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -72,6 +72,22 @@ The test uses `createRequire` from the node `module` API to resolve the main mod <<< @/snippets/zoe/contracts/test-bundle-source.js#testBundleSource{1} +::: details Getting the zip file from inside a bundle + +An endo bundle is a zip file inside JSON. To get it back out: + +```sh +jq -r .endoZipBase64 bundle-xyz.json | base64 -d >xyz.zip +``` + +You can then, for example, look at its contents: + +```sh +unzip -l xyz.zip +``` + +::: + ## Contract Installation To identify the code of contracts that parties consent to participate in, Zoe @@ -217,6 +233,24 @@ She then requests an _invitation_ to join the game; makes an _offer_ with this i <<< @/snippets/zoe/contracts/test-alice-trade.js#trade +::: details Troubleshooting missing brands in offers + +If you see... + +``` +Error#1: key Object [Alleged: IST brand] {} not found in collection brandToIssuerRecord +``` + +then it may be that your offer uses brands that are not known to the contract. +Use [E(zoe).getTerms()](/reference/zoe-api/zoe.md#e-zoe-getterms-instance) to find out what issuers +are known to the contract. + +If you're writing or instantiating the contract, you can tell the contract about issuers +when you are [creating an instance](#creating-an-instance) or by using +[zcf.saveIssuer()](/reference/zoe-api/zoe-contract-facet.md#zcf-saveissuer-issuer-keyword). + +::: + The contract gets Alice's `E(publicFacet).makeJoinInvitation()` call and uses `zcf` to make an invitation with an associated handler, description, and proposal shape. Zoe gets Alice's `E(zoe).offer(...)` call, checks the proposal against the proposal shape, escrows the payment, and invokes the handler. <<< @/snippets/zoe/contracts/gameAssetContract.js#makeInvitation @@ -242,7 +276,7 @@ and tests that it's what she wanted. <<< @/snippets/zoe/contracts/test-alice-trade.js#payouts -### Contracts on Zoe +## Example Contracts Agoric has written [a number of example contracts that you can use](/zoe/guide/contracts/), including: @@ -254,247 +288,9 @@ use](/zoe/guide/contracts/), including: - contracts for [minting fungible](./contracts/mint-payments.md) and [non-fungible tokens](./contracts/mint-and-sell-nfts.md) -## Using an Example Zoe Smart Contract +::: warning Beta Features -You must have a Zoe invitation to a specific contract instance to join -and participate in it. Let's imagine your friend Alice has sent an -invitation for a contract instance to your [wallet](/guides/wallet/). +These contracts may depend on features from our Beta release +that are not available in mainnet. -Compare this to a smart contract on Ethereum. On Ethereum, the smart -contract developer must guard against malicious calls and store an -internal access control list to check whether the message sender is -allowed to send such a message. Zoe, built on Agoric's [object -capability](/glossary/#object-capabilities) security model, is just -easier. - -This particular invitation is for an [Atomic Swap -contract](/zoe/guide/contracts/atomic-swap.md). -In an Atomic Swap, one party puts up digital assets they want to -exchange and sends an invitation to a second party for them to -possibly complete the exchange. In this example, Alice has already -escrowed the assets she wants to swap and is asking you to pay a -specified price to receive her digital assets. - -### Inspecting an Invitation - -So you have an invitation, but how do you use it? First, you use Zoe -to inspect and validate the invitation. - -<<< @/snippets/test-intro-zoe.js#details - -::: warning Note - -E() is part of the Agoric platform and is used to [call methods on -remote objects and receive a promise for the -result](/guides/js-programming/eventual-send.md). -Code on the Agoric platform is put in separate environments, called -[vats](/glossary/#vat), for security. Zoe is a remote object in its own vat, -so we must use E(). ::: - -Invitations include information about their contract's installation. -Essentially, this is the contract's source code as installed on Zoe. -From this overall contract installation, people use Zoe to create and -run specific instances of the contract. For example, if a real estate -company has a contract for selling a house, they would create an -instance of the contract for each individual house they have up for -sale. - -You use object identity comparison to quickly check that you recognize -this contract installation, without having to compare source code -line-by-line. If the installation matches, you're -sure the invitation is for participating in an instance of the -expected contract rather than an unknown and possibly malicious one. - -<<< @/snippets/test-intro-zoe.js#isCorrectCode - -However, if you don't recognize the installation, you can inspect its -code directly by calling: - -<<< @/snippets/test-intro-zoe.js#inspectCode - -In most cases, the bundle contains a base64-encoded zip file that you can -extract for review: - -```sh -@@@@@@ -jq -r .endoZipBase64 bundle-game1aginst.json | base64 -d >game1aginst.zip - -echo "$endoZipBase64" | base64 -d > bundle.zip -unzip bundle.zip -``` - -Contracts can add their own specific information to invitations. In -this case, the Atomic Swap contract adds information about what is -being traded: the `asset` [amount](/guides/ertp/amounts.md#amounts) -Alice has escrowed, and the `price` amount that you must pay to get it. -Note that both are _descriptions_ of digital assets with no intrinsic value of their own. - -### Making an Offer - -You've successfully checked out the invitation, so now you can make an -offer. - -An offer has three required parts: - -- a Zoe invitation -- a proposal -- a [payment](/guides/ertp/purses-and-payments.md#payments) containing - the digital assets you're offering to swap - -The `proposal` states what you want from the offer, and what you will -give in return. Zoe uses the proposal as an invariant to ensure you -don't lose your assets in the trade. This invariant is known as **offer -safety**. - -You use the invitation's `asset` and `price` amounts to make your -proposal. Let's say `asset` is an amount of 3 Moola, and `price` is an amount -of 7 Simoleans (Moola and Simoleans are made-up currencies for this example). - -<<< @/snippets/test-intro-zoe.js#ourProposal - -Proposals must use Keywords, which are -[identifier](https://developer.mozilla.org/en-US/docs/Glossary/Identifier) -properties that start with an upper case letter and contain no non-ASCII characters. -Here, the specific keywords, `Asset` and `Price`, are [determined by the -contract code](/zoe/guide/contracts/atomic-swap.md). - -You said you would give 7 Simoleans, so you must send 7 Simoleans as a payment. -You happen to have some Simoleans lying around in a Simolean -[purse](/guides/ertp/purses-and-payments.md) (used to hold digital -assets of a specific type). You withdraw a payment of 7 Simoleans from -the purse for your offer, and construct an object using the same -Keyword as your `proposal.give`: - -<<< @/snippets/test-intro-zoe.js#getPayments - -Now you need to [harden](https://github.com/endojs/endo/blob/HEAD/packages/ses/docs/guide.md) your -just created `proposal` and `payments` objects. Hardening is -transitively freezing an object. For security reasons, we must harden -any objects that will be passed to a remote object like Zoe. - -<<< @/snippets/test-intro-zoe.js#harden - -You've put the required pieces together, so now you can make an offer: - -<<< @/snippets/test-intro-zoe.js#offer - -At this point, Zoe confirms your invitation's validity and [burns](/glossary/#burn) it. -Zoe also escrows your payments, representing their value as -amounts in your **[Allocation](/reference/zoe-api/zoe-data-types.md#allocation)** -in the contract. - -::: tip Troubleshooting missing brands in offers - -If you see... - -``` -Error#1: key Object [Alleged: IST brand] {} not found in collection brandToIssuerRecord -``` - -then it may be that your offer uses brands that are not known to the contract. -Use [E(zoe).getTerms()](/reference/zoe-api/zoe.md#e-zoe-getterms-instance) to find out what issuers -are known to the contract. - -If you're writing or instantiating the contract, you can tell the contract about issuers -when you are [creating an instance](#creating-an-instance) or by using -[zcf.saveIssuer()](/reference/zoe-api/zoe-contract-facet.md#zcf-saveissuer-issuer-keyword). - -::: - -### Using Your UserSeat - -Making an offer as a user returns a [UserSeat](/reference/zoe-api/user-seat.md) -representing your position in the ongoing contract instance (your -"seat at the table"). You can use this seat to: - -1. Exit the contract. -2. Get information about your position such as your current allocation. -3. Get your payouts from Zoe. - -Check that your offer was successful: - -<<< @/snippets/test-intro-zoe.js#offerResult - -In response to your offer, the `atomicSwap` contract returns the -message: "The offer has been accepted. Once the contract has been -completed, please check your payout." Other contracts and offers may -return something different. The offer's result is entirely up to the -contract. - -### Getting Payouts - -The `atomicSwap` contract of this example is over once the second -party escrows the correct assets. You can get your payout of Moola -with the Keyword you used ('Asset'): - -<<< @/snippets/test-intro-zoe.js#getPayout - -Alice also receives her payouts: - -
- -<<< @/snippets/test-intro-zoe.js#alicePayout - -
- -## Writing and Installing a Contract - -Now that you've seen how to participate in a contract instance, let's -look at how you'd create a contract and its instances. - -Let's pretend Alice wrote that contract from scratch, even though -`atomicSwap` is one of Agoric's example contracts (see [Atomic Swap](./contracts/atomic-swap.md)). -Note: All Zoe contracts must have this format: - -::: details Show contract format -<<< @/snippets/contract-format.js#contractFormat -::: - -Alice fills in this code template with `atomicSwap`'s particulars. -To install this particular code, Alice first must bundle it off-chain, -meaning the code and its imports are flattened together: - -<<< @/snippets/test-intro-zoe.js#importBundleSource - -<<< @/snippets/test-intro-zoe.js#bundle - -Then Alice must install it on Zoe: - -<<< @/snippets/test-intro-zoe.js#install - -The return value is an `installation`, which we saw -[earlier](#inspecting-an-invitation). It is an -object identifying a particular piece of code installed on Zoe. It can -be compared to other installations, and you can call -`E(atomicSwapInstallation).getBundle()` to see the code itself. - -### Creating an Instance - -Now Alice uses the installation to create a new instance. She must -also tell Zoe about the ERTP issuers she wants to use, by specifying -their role with Keywords. Alice was escrowing Moola, so she uses the -keyword `Asset` to label the `moolaIssuer`. She wanted Simoleans, so -she uses the keyword `Price` to label the `simoleanIssuer`. - -<<< @/snippets/test-intro-zoe.js#startInstance - -Even the creator of a contract instance needs an invitation to -participate in it. Alice uses the returned `creatorInvitation` to -make an offer, from which she gets an invitation that can be sent to -the counter-party. - -<<< @/snippets/test-intro-zoe.js#aliceOffer - -## Zoe's Two Sides: Zoe Service and Zoe Contract Facet (ZCF) - -You may have noticed the contract code's `start` method has a `zcf` -parameter. This is the Zoe Contract Facet. Zoe has two sides: the Zoe -Service, which you've seen users interact with, and the Zoe Contract -Facet (ZCF), which is accessible to the contract code. Note that users -have access to the Zoe Service, but do not have access to ZCF. -Contract code has access to ZCF _and_ can get access to the Zoe -Service. - -To learn more about the Zoe Service, Zoe Contract Facet, and Zoe -Helper APIs, [see our Zoe API documentation](/reference/zoe-api/). From bdcb38d42a2582b6f30856b5387827652e8be109 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 16:18:26 -0600 Subject: [PATCH 13/31] docs(zoe): sequence diagrams for Trading with Offer Safety section --- .../zoe/assets/trade-offer-safety-1.mmd | 18 ++++++++++++ .../zoe/assets/trade-offer-safety-2.mmd | 18 ++++++++++++ .../zoe/assets/trade-offer-safety-3.mmd | 29 +++++++++++++++++++ .../zoe/assets/trade-offer-safety-4.mmd | 28 ++++++++++++++++++ .../zoe/assets/trade-offer-safety-5.mmd | 20 +++++++++++++ 5 files changed, 113 insertions(+) create mode 100644 main/guides/zoe/assets/trade-offer-safety-1.mmd create mode 100644 main/guides/zoe/assets/trade-offer-safety-2.mmd create mode 100644 main/guides/zoe/assets/trade-offer-safety-3.mmd create mode 100644 main/guides/zoe/assets/trade-offer-safety-4.mmd create mode 100644 main/guides/zoe/assets/trade-offer-safety-5.mmd diff --git a/main/guides/zoe/assets/trade-offer-safety-1.mmd b/main/guides/zoe/assets/trade-offer-safety-1.mmd new file mode 100644 index 000000000..f0eb07c8b --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-1.mmd @@ -0,0 +1,18 @@ +sequenceDiagram + autonumber + + box aqua + actor A as Alice + end + + box darksalmon + participant Zoe + end + + box skyblue Contract Instance + participant C as gameAssetContract + end + + A-)Zoe: getPublicFacet(instance) + A-)+Zoe: getTerms(instance) + Zoe--)-A: { issuers, brands, joinPrice } diff --git a/main/guides/zoe/assets/trade-offer-safety-2.mmd b/main/guides/zoe/assets/trade-offer-safety-2.mmd new file mode 100644 index 000000000..51afaeff5 --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-2.mmd @@ -0,0 +1,18 @@ +sequenceDiagram + autonumber + + box aqua + actor A as Alice + end + + box darksalmon + participant Zoe + end + + box skyblue Contract + participant C as gameAssetContract + end + + A-)C: makeJoinInvitation() + A-)Zoe: offer(toJoin, proposal, { Price }) + A-)Zoe: E(seat).getPayout('Places') diff --git a/main/guides/zoe/assets/trade-offer-safety-3.mmd b/main/guides/zoe/assets/trade-offer-safety-3.mmd new file mode 100644 index 000000000..25eb5b26a --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-3.mmd @@ -0,0 +1,29 @@ +sequenceDiagram + autonumber + + box aqua + actor A as Alice + end + + box darksalmon + participant Zoe + end + + box skyblue Contract + participant C as gameAssetContract + end + + A-)C: makeJoinInvitation() + activate C + C--)Zoe: makeInvitation(joinHandler, ...) + deactivate C + activate Zoe + Zoe--)-C: invitation + activate C + C--)A: Invitation toJoin + deactivate C + A-)+Zoe: offer(toJoin, proposal, { Price }) + + Zoe--)Zoe: escrow Price pmt + + Zoe--)-C: joinHandler(gameSeat) diff --git a/main/guides/zoe/assets/trade-offer-safety-4.mmd b/main/guides/zoe/assets/trade-offer-safety-4.mmd new file mode 100644 index 000000000..e3a07e24e --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-4.mmd @@ -0,0 +1,28 @@ +sequenceDiagram + autonumber + + box aqua + actor A as Alice + end + + box darksalmon + participant Zoe + end + + box skyblue Contract + participant C as gameAssetContract + end + + Zoe--)C: joinHandler(gameSeat) + activate C + C--)C: check proposal + C--)Zoe: mintGains(want) + deactivate C + activate Zoe + Zoe--)-C: tmp seat + + C--)Zoe: atomicRearrange(...) + + Zoe--)Zoe: check offer safety + + C--)Zoe: playerSeat.exit(true) \ No newline at end of file diff --git a/main/guides/zoe/assets/trade-offer-safety-5.mmd b/main/guides/zoe/assets/trade-offer-safety-5.mmd new file mode 100644 index 000000000..f8fc74ca8 --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-5.mmd @@ -0,0 +1,20 @@ +sequenceDiagram + autonumber + + box aqua + actor A as Alice + end + + box darksalmon + participant Zoe + end + + box skyblue Contract + participant C as gameAssetContract + end + + A-)+Zoe: E(seat).getPayout('Places') + Note over Zoe: ... many steps above ... + Zoe--)-A: placesPayment + A-)+Zoe: E(issuers.Place).getAmountOf(placesPayment) + Zoe--)-A: { brand: Place brand,
value: [['Park Place, 1n], ['Boardwalk', 1n]] From 558d49e5d18e1df71cbdb5d388d82d630690ba63 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 16:29:27 -0600 Subject: [PATCH 14/31] chore: generated SVG sequence diagrams for zoe section --- main/guides/zoe/assets/trade-offer-safety-1.svg | 1 + main/guides/zoe/assets/trade-offer-safety-2.svg | 1 + main/guides/zoe/assets/trade-offer-safety-3.svg | 1 + main/guides/zoe/assets/trade-offer-safety-4.svg | 1 + main/guides/zoe/assets/trade-offer-safety-5.svg | 1 + 5 files changed, 5 insertions(+) create mode 100644 main/guides/zoe/assets/trade-offer-safety-1.svg create mode 100644 main/guides/zoe/assets/trade-offer-safety-2.svg create mode 100644 main/guides/zoe/assets/trade-offer-safety-3.svg create mode 100644 main/guides/zoe/assets/trade-offer-safety-4.svg create mode 100644 main/guides/zoe/assets/trade-offer-safety-5.svg diff --git a/main/guides/zoe/assets/trade-offer-safety-1.svg b/main/guides/zoe/assets/trade-offer-safety-1.svg new file mode 100644 index 000000000..7b70d2a53 --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-1.svg @@ -0,0 +1 @@ +Contract InstancegameAssetContractZoegameAssetContractZoeAlicegetPublicFacet(instance)1getTerms(instance)2{ issuers, brands, joinPrice }3Alice \ No newline at end of file diff --git a/main/guides/zoe/assets/trade-offer-safety-2.svg b/main/guides/zoe/assets/trade-offer-safety-2.svg new file mode 100644 index 000000000..4ab44c82f --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-2.svg @@ -0,0 +1 @@ +ContractgameAssetContractZoegameAssetContractZoeAlicemakeJoinInvitation()1offer(toJoin, proposal, { Price })2E(seat).getPayout('Places')3Alice \ No newline at end of file diff --git a/main/guides/zoe/assets/trade-offer-safety-3.svg b/main/guides/zoe/assets/trade-offer-safety-3.svg new file mode 100644 index 000000000..8323004f0 --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-3.svg @@ -0,0 +1 @@ +ContractgameAssetContractZoegameAssetContractZoeAlicemakeJoinInvitation()1makeInvitation(joinHandler, ...)2invitation3Invitation toJoin4offer(toJoin, proposal, { Price })5escrow Price pmt6joinHandler(gameSeat)7Alice \ No newline at end of file diff --git a/main/guides/zoe/assets/trade-offer-safety-4.svg b/main/guides/zoe/assets/trade-offer-safety-4.svg new file mode 100644 index 000000000..eb0b82e51 --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-4.svg @@ -0,0 +1 @@ +ContractgameAssetContractZoegameAssetContractZoeAlicejoinHandler(gameSeat)1check proposal2mintGains(want)3tmp seat4atomicRearrange(...)5check offer safety6playerSeat.exit(true)7Alice \ No newline at end of file diff --git a/main/guides/zoe/assets/trade-offer-safety-5.svg b/main/guides/zoe/assets/trade-offer-safety-5.svg new file mode 100644 index 000000000..70b42dfe6 --- /dev/null +++ b/main/guides/zoe/assets/trade-offer-safety-5.svg @@ -0,0 +1 @@ +ContractgameAssetContractZoegameAssetContractZoe... many steps above ...AliceE(seat).getPayout('Places')1placesPayment2E(issuers.Place).getAmountOf(placesPayment)3{ brand: Place brand, value: [['Park Place, 1n], ['Boardwalk', 1n]]4Alice \ No newline at end of file From 5f93deb10c3a162d7a9bb6a03ab7de311069a447 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 16:20:17 -0600 Subject: [PATCH 15/31] docs(zoe): full contract listing; misc. clarification - make each section more context-free by harking back explicitly to the basic dapp - focus on lines by number - misc. editorial --- main/guides/zoe/README.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index bf38c8f9f..fefb7ac01 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -91,7 +91,9 @@ unzip -l xyz.zip ## Contract Installation To identify the code of contracts that parties consent to participate in, Zoe -uses _Installation_ objects. Let's try it: +uses _Installation_ objects. + +Let's try it with the contract from our [basic dapp](../getting-started/): ```sh yarn ava test/test-contract.js -m 'Install the contract' @@ -114,7 +116,7 @@ const { zoeService: zoe } = makeZoeKitForTest(); ::: -Using a bundle as in the previous section: +It gets an installation using a bundle as in the previous section: ```js{1} const installation = await E(zoe).install(bundle); @@ -122,9 +124,18 @@ t.log(installation); t.is(typeof installation, 'object'); ``` +The `installation` identifies the basic contract that we'll +go over in detail in the sections below. + +::: details gameAssetContract.js listing + +<<< @/snippets/zoe/contracts/gameAssetContract.js#file + +::: + ## Starting a contract -Now we're ready for the to start an instance of the contract: +Now we're ready for the to start an instance of the [basic dapp](../getting-started/) contract: ```sh yarn ava test/test-contract.js -m 'Start the contract' @@ -147,7 +158,7 @@ but rather chosen when starting an _instance_ of the contract. Likewise, when starting an instance, we can choose which asset _issuers_ the contract should use for its business: -```js +```js{8} const money = makeIssuerKit('PlayMoney'); const issuers = { Price: money.issuer }; const terms = { joinPrice: AmountMath.make(money.brand, 5n) }; @@ -169,13 +180,13 @@ as well as a `mint` for making assets consisting of collections (bags) of Places <<< @/snippets/zoe/contracts/gameAssetContract.js#start -It defines a `joinShape`, `joinHandler` but doesn't do anything with them yet. They will come into play later. It defines and returns its `publicFacet` and stands by. +It defines a `joinShape` and `joinHandler` but doesn't do anything with them yet. They will come into play later. It defines and returns its `publicFacet` and stands by. <<< @/snippets/zoe/contracts/gameAssetContract.js#started ## Trading with Offer Safety -Now let's try trading: +Our [basic dapp](../getting-started/) includes a test of trading: ```sh yarn ava test/test-contract.js -m 'Alice trades*' @@ -207,7 +218,7 @@ yarn ava test/test-contract.js -m 'Alice trades*' We start by putting some money in a purse for Alice: -```js +```js{4} const alicePurse = money.issuer.makeEmptyPurse(); const amountOfMoney = AmountMath.make(money.brand, 10n); const moneyPayment = money.mint.mintPayment(amountOfMoney); @@ -229,7 +240,10 @@ for 1 Park Place and 1 Boardwalk, denominated in the game's `Place` brand; and s <<< @/snippets/zoe/contracts/test-alice-trade.js#makeProposal -She then requests an _invitation_ to join the game; makes an _offer_ with this invitation, her proposal, and her payment; and awaits her Place payout: +She then requests an _invitation_ to join the game; makes an _offer_ with +(a promise for) this invitation, her proposal, and her payment; +and awaits her **Places** payout: + <<< @/snippets/zoe/contracts/test-alice-trade.js#trade @@ -255,13 +269,13 @@ The contract gets Alice's `E(publicFacet).makeJoinInvitation()` call and uses `z <<< @/snippets/zoe/contracts/gameAssetContract.js#makeInvitation -The handler is invoked with a _seat_ representing the party making the offer. +The offer handler is invoked with a _seat_ representing the party making the offer. It extracts the `give` and `want` from the party's offer and checks that they are giving at least the `joinPrice` and not asking for too many places in return. -With all these prerequisites met, it instructs `zcf` to mint the requested -place assets, allocate what the player is giving into its own seat, +With all these prerequisites met, the handler instructs `zcf` to mint the requested +**Place** assets, allocate what the player is giving into its own `gameSeat`, and allocate the minted places to the player. Finally, it concludes its business with the player. <<< @/snippets/zoe/contracts/gameAssetContract.js#handler @@ -269,7 +283,7 @@ and allocate the minted places to the player. Finally, it concludes its business Zoe checks that the contract's instructions are consistent with the offer and with conservation of assets. Then it allocates the escrowed payment to the contract's gameSeat and pays out -the place NFTs to Alice. +the place NFTs to Alice in response to the earlier `getPayout(...)` call. Alice asks the `Place` issuer what her payout is worth and tests that it's what she wanted. From e1c29489e81f6ec61ac7e842a62c527bf5203482 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 16:23:25 -0600 Subject: [PATCH 16/31] docs(zoe): add diagrams to Trading with Offer Safety section --- main/guides/zoe/README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index fefb7ac01..6fc38b840 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -233,6 +233,9 @@ await alice(t, zoe, instance, alicePurse); Alice starts by using the `instance` to get the contract's `publicFacet` and `terms` from Zoe: + + <<< @/snippets/zoe/contracts/test-alice-trade.js#queryInstance Then she constructs a _proposal_ to give the `joinPrice` in exchange @@ -244,6 +247,8 @@ She then requests an _invitation_ to join the game; makes an _offer_ with (a promise for) this invitation, her proposal, and her payment; and awaits her **Places** payout: + <<< @/snippets/zoe/contracts/test-alice-trade.js#trade @@ -267,6 +272,9 @@ when you are [creating an instance](#creating-an-instance) or by using The contract gets Alice's `E(publicFacet).makeJoinInvitation()` call and uses `zcf` to make an invitation with an associated handler, description, and proposal shape. Zoe gets Alice's `E(zoe).offer(...)` call, checks the proposal against the proposal shape, escrows the payment, and invokes the handler. + + <<< @/snippets/zoe/contracts/gameAssetContract.js#makeInvitation The offer handler is invoked with a _seat_ representing the party making the offer. @@ -278,6 +286,9 @@ With all these prerequisites met, the handler instructs `zcf` to mint the reques **Place** assets, allocate what the player is giving into its own `gameSeat`, and allocate the minted places to the player. Finally, it concludes its business with the player. + + <<< @/snippets/zoe/contracts/gameAssetContract.js#handler Zoe checks that the contract's instructions are consistent with @@ -288,6 +299,9 @@ the place NFTs to Alice in response to the earlier `getPayout(...)` call. Alice asks the `Place` issuer what her payout is worth and tests that it's what she wanted. + + <<< @/snippets/zoe/contracts/test-alice-trade.js#payouts ## Example Contracts From d825818aecb1294c4b19837da40c22c4cf797ba4 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 16:35:40 -0600 Subject: [PATCH 17/31] refactor(coreeval): Contract Installation section was renamed --- main/guides/coreeval/README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/main/guides/coreeval/README.md b/main/guides/coreeval/README.md index 8b47c3855..eaaf13a05 100644 --- a/main/guides/coreeval/README.md +++ b/main/guides/coreeval/README.md @@ -1,7 +1,7 @@ # Permissioned Contract Deployment Until mainnet enters the Mainnet-3 phase of the [multi-phase mainnet rollout](https://agoric.com/blog/announcements/mainnet-phase-0-launch), -permissionless [contract installation with Zoe](/guides/zoe/#writing-and-installing-a-contract) +permissionless [contract installation with Zoe](/guides/zoe/#contract-installation) is limited to development environments. Until then, permission to deploy contracts can be granted using an Agoric extension to [Cosmos SDK Governance](https://hub.cosmos.network/main/delegators/delegator-guide-cli.html#participating-in-governance) called `swingset.CoreEval`. As discussed in [governance using Hardened JavaScript: swingset\.CoreEval](https://community.agoric.com/t/bld-staker-governance-using-hardened-javascript-swingset-coreeval/99), @@ -12,6 +12,3 @@ To do try it out in a local testnet chain: 1. [Declare the capabilities that the proposal will require](./permissions.md). 2. [Code the proposal itself](./proposal.md). 3. [Deploy the proposal to a local testnet](./local-testnet.md). - - - From 290f31297c44ee4773f32fcea257d1dce523e77d Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 7 Dec 2023 16:37:08 -0600 Subject: [PATCH 18/31] docs(zoe): include Instance in starting heading --- main/guides/zoe/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 6fc38b840..dc8cc87cf 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -133,9 +133,9 @@ go over in detail in the sections below. ::: -## Starting a contract +## Starting a Contract Instance -Now we're ready for the to start an instance of the [basic dapp](../getting-started/) contract: +Now we're ready for the to start an _instance_ of the [basic dapp](../getting-started/) contract: ```sh yarn ava test/test-contract.js -m 'Start the contract' @@ -154,7 +154,7 @@ yarn ava test/test-contract.js -m 'Start the contract' Contracts can be parameterized by _terms_. The price of joining the game is not fixed in the source code of this contract, -but rather chosen when starting an _instance_ of the contract. +but rather chosen when starting an instance of the contract. Likewise, when starting an instance, we can choose which asset _issuers_ the contract should use for its business: @@ -265,7 +265,7 @@ Use [E(zoe).getTerms()](/reference/zoe-api/zoe.md#e-zoe-getterms-instance) to fi are known to the contract. If you're writing or instantiating the contract, you can tell the contract about issuers -when you are [creating an instance](#creating-an-instance) or by using +when you are [creating an instance](#starting-a-contract-instance) or by using [zcf.saveIssuer()](/reference/zoe-api/zoe-contract-facet.md#zcf-saveissuer-issuer-keyword). ::: From 1eb3eef2b1d906c8320f1c66486c8b2cc136e848 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 8 Dec 2023 17:23:11 -0600 Subject: [PATCH 19/31] fixup! docs(zoe): Bundling, Installation, Starting w/basic dapp --- main/guides/zoe/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index dc8cc87cf..1064554fc 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -129,7 +129,7 @@ go over in detail in the sections below. ::: details gameAssetContract.js listing -<<< @/snippets/zoe/contracts/gameAssetContract.js#file +<<< @/snippets/zoe/src/gameAssetContract.js#file ::: @@ -178,11 +178,11 @@ The contract uses this `zcf` to get its terms. Likewise it uses `zcf` to make a `gameSeat` where it can store assets that it receives in trade as well as a `mint` for making assets consisting of collections (bags) of Places: -<<< @/snippets/zoe/contracts/gameAssetContract.js#start +<<< @/snippets/zoe/src/gameAssetContract.js#start It defines a `joinShape` and `joinHandler` but doesn't do anything with them yet. They will come into play later. It defines and returns its `publicFacet` and stands by. -<<< @/snippets/zoe/contracts/gameAssetContract.js#started +<<< @/snippets/zoe/src/gameAssetContract.js#started ## Trading with Offer Safety From 9848d03dcb78aa8ccff7dc70b1e641fe25002ce5 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Fri, 8 Dec 2023 17:23:29 -0600 Subject: [PATCH 20/31] fixup! docs: Trading with Offer Safety --- main/guides/zoe/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 1064554fc..ab7e94d49 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -275,7 +275,7 @@ The contract gets Alice's `E(publicFacet).makeJoinInvitation()` call and uses `z -<<< @/snippets/zoe/contracts/gameAssetContract.js#makeInvitation +<<< @/snippets/zoe/src/gameAssetContract.js#makeInvitation The offer handler is invoked with a _seat_ representing the party making the offer. It extracts the `give` and `want` from the party's offer and checks that @@ -289,7 +289,7 @@ and allocate the minted places to the player. Finally, it concludes its business -<<< @/snippets/zoe/contracts/gameAssetContract.js#handler +<<< @/snippets/zoe/src/gameAssetContract.js#handler Zoe checks that the contract's instructions are consistent with the offer and with conservation of assets. Then it allocates From 6d6bb40f9a5b4731e13f3780c3ec70ce70f3b5f1 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 12 Dec 2023 15:33:44 -0600 Subject: [PATCH 21/31] docs: editorial: avoid "a contract _is_ a module..." --- main/guides/zoe/contract-basics.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main/guides/zoe/contract-basics.md b/main/guides/zoe/contract-basics.md index e770fd75d..ac6e69882 100644 --- a/main/guides/zoe/contract-basics.md +++ b/main/guides/zoe/contract-basics.md @@ -3,8 +3,8 @@ Before we look at how to make a contract such as the one in [the basic dapp](./) in the previous section, let's cover some basics. -A contract is a JavaScript module that exports a `start` function -that defines the contract's API. +A contract is defined by a JavaScript module that exports a `start` function +that implements the contract's API. <<< @/snippets/zoe/src/01-hello.js#start From 3f05ce6c872a9b441aff7f4bd490a153b3b29cba Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Wed, 3 Jan 2024 17:38:44 -0600 Subject: [PATCH 22/31] fixup! chore: update sidebar for contract-basics --- main/.vuepress/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/.vuepress/config.js b/main/.vuepress/config.js index 1277d604e..5313ad6ec 100644 --- a/main/.vuepress/config.js +++ b/main/.vuepress/config.js @@ -169,7 +169,7 @@ module.exports = { path: '/guides/getting-started/', collapsible: false, children: [ - '/guides/getting-started/' + '/guides/getting-started/', '/guides/getting-started/contract-rpc.html', '/guides/getting-started/deploying.html', '/guides/getting-started/syncing-up.html', From 1febd690167b044d9161438858ff9199283eae99 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 4 Jan 2024 16:33:49 -0600 Subject: [PATCH 23/31] docs(zoe): editorial Co-authored-by: Chris Hibbert --- main/guides/zoe/contract-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/guides/zoe/contract-basics.md b/main/guides/zoe/contract-basics.md index ac6e69882..374303115 100644 --- a/main/guides/zoe/contract-basics.md +++ b/main/guides/zoe/contract-basics.md @@ -18,7 +18,7 @@ a method of the contract's `publicFacet`: <<< @/snippets/zoe/src/01-hello.js#publicFacet -We mark it `Far(...)` to allow callers from outside the contract +We mark it `Far(...)` to allow callers to use it from outside the contract and give it a suggestive interface name for debugging. _We'll discuss [Far in more detail](../js-programming/far.md) later._ From 58e02a5d6dd12684f80550e6e637d64954b5bda6 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Thu, 4 Jan 2024 16:34:57 -0600 Subject: [PATCH 24/31] fixup! docs(zoe): Bundling, Installation, Starting w/basic dapp --- main/guides/zoe/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index ab7e94d49..64b47588c 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -35,7 +35,7 @@ users to lose their assets.** ## Bundling a Contract Recall from [deploying the basic dapp contract](../getting-started/#deploy-the-contract) -that the first step was to _bundle_ all if its modules into a single artifact. +that the first step was to _bundle_ all of its modules into a single artifact. We used the [agoric run](../agoric-cli/#agoric-run) command in that case. The core mechanism used in `agoric run` is a call to `bundleSource()`. From a39affa8aa16bb9187c7ad17fcc5c97869b36cd4 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 8 Jan 2024 18:05:22 -0600 Subject: [PATCH 25/31] fixup! docs(zoe): Bundling, Installation, Starting w/basic dapp --- main/guides/zoe/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 64b47588c..33215b840 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -34,13 +34,12 @@ users to lose their assets.** ## Bundling a Contract -Recall from [deploying the basic dapp contract](../getting-started/#deploy-the-contract) -that the first step was to _bundle_ all of its modules into a single artifact. +In [deploying the basic dapp contract](../getting-started/#starting-the-dapp-smart-contract), +the first step was to _bundle_ all of its modules into a single artifact. We used the [agoric run](../agoric-cli/#agoric-run) command in that case. The core mechanism used in `agoric run` is a call to `bundleSource()`. -In the `contract` directory of the -[basic dapp project structure](../getting-started/#project-structure), +In the `contract` directory of the dapp, run `test-bundle-source.js` following `ava` conventions: ```sh From 7e8e315da23bf9e687425df1a01b59205c240daf Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 8 Jan 2024 18:09:34 -0600 Subject: [PATCH 26/31] fixup! test(zoe): snippets for learning zoe lessons 1, 2, 3 --- snippets/zoe/contracts/test-zoe-hello.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/snippets/zoe/contracts/test-zoe-hello.js b/snippets/zoe/contracts/test-zoe-hello.js index 950c6413f..03659ebf7 100644 --- a/snippets/zoe/contracts/test-zoe-hello.js +++ b/snippets/zoe/contracts/test-zoe-hello.js @@ -1,6 +1,3 @@ -// TODO: convince prettier that harden() is a global -/* global harden */ - // #region test-imports import '@endo/init'; import { E } from '@endo/far'; From 955327d021d397126f4a843b4efbe660523e5c3f Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 8 Jan 2024 18:10:14 -0600 Subject: [PATCH 27/31] fixup! chore: copy gameAssetContract, test-alice from dapp-game-places --- snippets/zoe/contracts/test-alice-trade.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snippets/zoe/contracts/test-alice-trade.js b/snippets/zoe/contracts/test-alice-trade.js index f09c4a95c..94010f1a8 100644 --- a/snippets/zoe/contracts/test-alice-trade.js +++ b/snippets/zoe/contracts/test-alice-trade.js @@ -16,7 +16,7 @@ import { AmountMath } from '@agoric/ertp'; * @param {ERef>} instance * @param {Purse} purse */ -const alice = async (t, zoe, instance, purse) => { +export const alice = async (t, zoe, instance, purse) => { // #region queryInstance const publicFacet = E(zoe).getPublicFacet(instance); const terms = await E(zoe).getTerms(instance); From 96cecd8476ce864225fe17b6b77bcf0c5b08e0cb Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Mon, 8 Jan 2024 18:12:19 -0600 Subject: [PATCH 28/31] WIP: ava unresolved work-around --- snippets/zoe/contracts/test-zoe-hello.js | 1 + 1 file changed, 1 insertion(+) diff --git a/snippets/zoe/contracts/test-zoe-hello.js b/snippets/zoe/contracts/test-zoe-hello.js index 03659ebf7..a59f4d628 100644 --- a/snippets/zoe/contracts/test-zoe-hello.js +++ b/snippets/zoe/contracts/test-zoe-hello.js @@ -1,6 +1,7 @@ // #region test-imports import '@endo/init'; import { E } from '@endo/far'; +// eslint-disable-next-line import/no-unresolved -- https://github.com/avajs/ava/issues/2951 import test from 'ava'; // #endregion test-imports import { start as startState } from '../src/02-state.js'; From 32880238b571b30923feb377772baa5dd5f986d8 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 9 Jan 2024 11:31:44 -0600 Subject: [PATCH 29/31] fixup: alice-trade is not a test file --- main/guides/zoe/README.md | 8 ++++---- .../zoe/contracts/{test-alice-trade.js => alice-trade.js} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename snippets/zoe/contracts/{test-alice-trade.js => alice-trade.js} (100%) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 33215b840..26af19b10 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -235,12 +235,12 @@ Alice starts by using the `instance` to get the contract's `publicFacet` and `te -<<< @/snippets/zoe/contracts/test-alice-trade.js#queryInstance +<<< @/snippets/zoe/contracts/alice-trade.js#queryInstance Then she constructs a _proposal_ to give the `joinPrice` in exchange for 1 Park Place and 1 Boardwalk, denominated in the game's `Place` brand; and she withdraws a payment from her purse: -<<< @/snippets/zoe/contracts/test-alice-trade.js#makeProposal +<<< @/snippets/zoe/contracts/alice-trade.js#makeProposal She then requests an _invitation_ to join the game; makes an _offer_ with (a promise for) this invitation, her proposal, and her payment; @@ -249,7 +249,7 @@ and awaits her **Places** payout: -<<< @/snippets/zoe/contracts/test-alice-trade.js#trade +<<< @/snippets/zoe/contracts/alice-trade.js#trade ::: details Troubleshooting missing brands in offers @@ -301,7 +301,7 @@ and tests that it's what she wanted. -<<< @/snippets/zoe/contracts/test-alice-trade.js#payouts +<<< @/snippets/zoe/contracts/alice-trade.js#payouts ## Example Contracts diff --git a/snippets/zoe/contracts/test-alice-trade.js b/snippets/zoe/contracts/alice-trade.js similarity index 100% rename from snippets/zoe/contracts/test-alice-trade.js rename to snippets/zoe/contracts/alice-trade.js From 5576ce1b2b3ec4e296df6f769d113368b56bdb84 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 9 Jan 2024 13:08:13 -0600 Subject: [PATCH 30/31] Update main/guides/zoe/README.md Co-authored-by: Chris Hibbert --- main/guides/zoe/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main/guides/zoe/README.md b/main/guides/zoe/README.md index 26af19b10..d955344f6 100644 --- a/main/guides/zoe/README.md +++ b/main/guides/zoe/README.md @@ -134,7 +134,7 @@ go over in detail in the sections below. ## Starting a Contract Instance -Now we're ready for the to start an _instance_ of the [basic dapp](../getting-started/) contract: +Now we're ready to start an _instance_ of the [basic dapp](../getting-started/) contract: ```sh yarn ava test/test-contract.js -m 'Start the contract' From d48ede53b39f046753a968cadc3ffc47e1aae965 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 9 Jan 2024 13:12:05 -0600 Subject: [PATCH 31/31] refactor: clarify test imports --- snippets/zoe/contracts/test-zoe-hello.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/snippets/zoe/contracts/test-zoe-hello.js b/snippets/zoe/contracts/test-zoe-hello.js index a59f4d628..7a62d6050 100644 --- a/snippets/zoe/contracts/test-zoe-hello.js +++ b/snippets/zoe/contracts/test-zoe-hello.js @@ -4,8 +4,8 @@ import { E } from '@endo/far'; // eslint-disable-next-line import/no-unresolved -- https://github.com/avajs/ava/issues/2951 import test from 'ava'; // #endregion test-imports -import { start as startState } from '../src/02-state.js'; -import { start as startAccess } from '../src/03-access.js'; +import * as state from '../src/02-state.js'; +import * as access from '../src/03-access.js'; // #region test1 import { start } from '../src/01-hello.js'; @@ -18,7 +18,7 @@ test('contract greets by name', async t => { // #region test-state test('state', async t => { - const { publicFacet } = startState(); + const { publicFacet } = state.start(); t.is(await E(publicFacet).get(), 'Hello, World!'); await E(publicFacet).set(2); t.is(await E(publicFacet).get(), 2); @@ -27,7 +27,7 @@ test('state', async t => { // #region test-access test('access control', async t => { - const { publicFacet, creatorFacet } = startAccess(); + const { publicFacet, creatorFacet } = access.start(); t.is(await E(publicFacet).get(), 'Hello, World!'); await t.throwsAsync(E(publicFacet).set(2), { message: /no method/ }); await E(creatorFacet).set(2);