Skip to content

Commit 83cc2e6

Browse files
committed
refactor: move contract walk-through out of zoe overview
... to make room for material on proposal shapes and to fix the goofy order of contract-basics
1 parent c2926ae commit 83cc2e6

File tree

4 files changed

+284
-273
lines changed

4 files changed

+284
-273
lines changed

main/.vitepress/config.mjs

+4-1
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,10 @@ export default defineConfig({
128128
text: 'Smart Contract Basics',
129129
link: '/guides/zoe/contract-basics',
130130
},
131-
{ text: 'Zoe Overview', link: '/guides/zoe/' },
131+
{
132+
text: 'Complete Contract Walk-Through',
133+
link: '/guides/zoe/contract-walkthru',
134+
},
132135
{ text: 'Contract Upgrade', link: '/guides/zoe/contract-upgrade' },
133136
{ text: 'Contract Governance', link: '/guides/governance/' },
134137
],

main/guides/zoe/contract-upgrade.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Governance of the right to upgrade is a complex topic that we cover only briefly
2020

2121
## Upgrading a Contract
2222

23-
Upgrading a contract instance means re-starting the contract using a different [code bundle](./#bundling-a-contract). Suppose we start a contract as usual, using
23+
Upgrading a contract instance means re-starting the contract using a different [code bundle](./contract-walkthru#bundling-a-contract). Suppose we start a contract as usual, using
2424
the bundle ID of a bundle we already sent to the chain:
2525

2626
```js

main/guides/zoe/contract-walkthru.md

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
# Complete Contract Walk-Through
2+
3+
Let's look at the contract from [the basic dapp](../getting-started/) in some detail.
4+
5+
## Bundling a Contract
6+
7+
In [deploying the basic dapp contract](../getting-started/#starting-the-dapp-smart-contract),
8+
the first step was to _bundle_ all of its modules into a single artifact.
9+
We used the [agoric run](../agoric-cli/#agoric-run) command in that case.
10+
The core mechanism used in `agoric run` is a call to `bundleSource()`.
11+
12+
In the `contract` directory of the dapp,
13+
run `test-bundle-source.js` following `ava` conventions:
14+
15+
```sh
16+
cd contract
17+
yarn ava test/test-bundle-source.js
18+
```
19+
20+
The results look something like...
21+
22+
```console
23+
✔ bundleSource() bundles the contract for use with zoe (2.7s)
24+
ℹ 1e1aeca9d3ebc0bd39130fe5ef6fbb077177753563db522d6623886da9b43515816df825f7ebcb009cbe86dcaf70f93b9b8595d1a87c2ab9951ee7a32ad8e572
25+
ℹ Object @Alleged: BundleInstallation {}
26+
27+
28+
1 test passed
29+
```
30+
31+
::: details Test Setup
32+
33+
The test uses `createRequire` from the node `module` API to resolve the main module specifier:
34+
35+
<<< @/../snippets/zoe/contracts/test-bundle-source.js#bundleSourceImports
36+
37+
<<< @/../snippets/zoe/contracts/test-bundle-source.js#contractPath
38+
:::
39+
40+
`bundleSource()` returns a bundle object with `moduleFormat`, a hash, and the contents:
41+
42+
<<< @/../snippets/zoe/contracts/test-bundle-source.js#testBundleSource{1}
43+
44+
::: details Getting the zip file from inside a bundle
45+
46+
An endo bundle is a zip file inside JSON. To get it back out:
47+
48+
```sh
49+
jq -r .endoZipBase64 bundle-xyz.json | base64 -d >xyz.zip
50+
```
51+
52+
You can then, for example, look at its contents:
53+
54+
```sh
55+
unzip -l xyz.zip
56+
```
57+
58+
:::
59+
60+
## Contract Installation
61+
62+
To identify the code of contracts that parties consent to participate in, Zoe
63+
uses _Installation_ objects.
64+
65+
Let's try it with the contract from our [basic dapp](../getting-started/):
66+
67+
```sh
68+
yarn ava test/test-contract.js -m 'Install the contract'
69+
```
70+
71+
```
72+
✔ Install the contract
73+
ℹ Object @Alleged: BundleInstallation {}
74+
```
75+
76+
::: details Test Setup
77+
78+
The test starts by using `makeZoeKitForTest` to set up zoe for testing:
79+
80+
<<< @/../snippets/zoe/contracts/test-bundle-source.js#importZoeForTest
81+
82+
```js
83+
const { zoeService: zoe } = makeZoeKitForTest();
84+
```
85+
86+
:::
87+
88+
It gets an installation using a bundle as in the previous section:
89+
90+
```js{1}
91+
const installation = await E(zoe).install(bundle);
92+
t.log(installation);
93+
t.is(typeof installation, 'object');
94+
```
95+
96+
The `installation` identifies the basic contract that we'll
97+
go over in detail in the sections below.
98+
99+
::: details gameAssetContract.js listing
100+
101+
<<< @/../snippets/zoe/src/gameAssetContract.js#file
102+
103+
:::
104+
105+
## Starting a Contract Instance
106+
107+
Now we're ready to start an _instance_ of the [basic dapp](../getting-started/) contract:
108+
109+
```sh
110+
yarn ava test/test-contract.js -m 'Start the contract'
111+
```
112+
113+
```
114+
✔ Start the contract (652ms)
115+
ℹ terms: {
116+
joinPrice: {
117+
brand: Object @Alleged: PlayMoney brand {},
118+
value: 5n,
119+
},
120+
}
121+
ℹ Object @Alleged: InstanceHandle {}
122+
```
123+
124+
Contracts can be parameterized by _terms_.
125+
The price of joining the game is not fixed in the source code of this contract,
126+
but rather chosen when starting an instance of the contract.
127+
Likewise, when starting an instance, we can choose which asset _issuers_
128+
the contract should use for its business:
129+
130+
```js{8}
131+
const money = makeIssuerKit('PlayMoney');
132+
const issuers = { Price: money.issuer };
133+
const terms = { joinPrice: AmountMath.make(money.brand, 5n) };
134+
t.log('terms:', terms);
135+
136+
/** @type {ERef<Installation<GameContractFn>>} */
137+
const installation = E(zoe).install(bundle);
138+
const { instance } = await E(zoe).startInstance(installation, issuers, terms);
139+
t.log(instance);
140+
t.is(typeof instance, 'object');
141+
```
142+
143+
_`makeIssuerKit` and `AmountMath.make` are covered in the [ERTP](../ertp/) section, along with `makeEmptyPurse`, `mintPayment`, and `getAmountOf` below._
144+
145+
_See also [E(zoe).startInstance(...)](/reference/zoe-api/zoe.md#e-zoe-startinstance-installation-issuerkeywordrecord-terms-privateargs)._
146+
147+
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.
148+
The contract uses this `zcf` to get its terms. Likewise it uses `zcf` to
149+
make a `gameSeat` where it can store assets that it receives in trade
150+
as well as a `mint` for making assets consisting of collections (bags) of Places:
151+
152+
<<< @/../snippets/zoe/src/gameAssetContract.js#start
153+
154+
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.
155+
156+
<<< @/../snippets/zoe/src/gameAssetContract.js#started
157+
158+
## Trading with Offer Safety
159+
160+
Our [basic dapp](../getting-started/) includes a test of trading:
161+
162+
```sh
163+
yarn ava test/test-contract.js -m 'Alice trades*'
164+
```
165+
166+
```
167+
✔ Alice trades: give some play money, want some game places (674ms)
168+
ℹ Object @Alleged: InstanceHandle {}
169+
ℹ Alice gives {
170+
Price: {
171+
brand: Object @Alleged: PlayMoney brand {},
172+
value: 5n,
173+
},
174+
}
175+
ℹ Alice payout brand Object @Alleged: Place brand {}
176+
ℹ Alice payout value Object @copyBag {
177+
payload: [
178+
[
179+
'Park Place',
180+
1n,
181+
],
182+
[
183+
'Boardwalk',
184+
1n,
185+
],
186+
],
187+
}
188+
```
189+
190+
We start by putting some money in a purse for Alice:
191+
192+
```js{4}
193+
const alicePurse = money.issuer.makeEmptyPurse();
194+
const amountOfMoney = AmountMath.make(money.brand, 10n);
195+
const moneyPayment = money.mint.mintPayment(amountOfMoney);
196+
alicePurse.deposit(moneyPayment);
197+
```
198+
199+
Then we pass the contract instance and the purse to our code for `alice`:
200+
201+
```js
202+
await alice(t, zoe, instance, alicePurse);
203+
```
204+
205+
Alice starts by using the `instance` to get the contract's `publicFacet` and `terms` from Zoe:
206+
207+
<img src="./assets/trade-offer-safety-1.svg"
208+
style="border: 2px solid" width="600" />
209+
210+
<<< @/../snippets/zoe/contracts/alice-trade.js#queryInstance
211+
212+
Then she constructs a _proposal_ to give the `joinPrice` in exchange
213+
for 1 Park Place and 1 Boardwalk, denominated in the game's `Place` brand; and she withdraws a payment from her purse:
214+
215+
<<< @/../snippets/zoe/contracts/alice-trade.js#makeProposal
216+
217+
She then requests an _invitation_ to join the game; makes an _offer_ with
218+
(a promise for) this invitation, her proposal, and her payment;
219+
and awaits her **Places** payout:
220+
221+
<img src="./assets/trade-offer-safety-2.svg"
222+
style="border: 2px solid" width="600" />
223+
224+
<<< @/../snippets/zoe/contracts/alice-trade.js#trade
225+
226+
::: details Troubleshooting missing brands in offers
227+
228+
If you see...
229+
230+
```
231+
Error#1: key Object [Alleged: IST brand] {} not found in collection brandToIssuerRecord
232+
```
233+
234+
then it may be that your offer uses brands that are not known to the contract.
235+
Use [E(zoe).getTerms()](/reference/zoe-api/zoe#e-zoe-getterms-instance) to find out what issuers
236+
are known to the contract.
237+
238+
If you're writing or instantiating the contract, you can tell the contract about issuers
239+
when you are [creating an instance](#starting-a-contract-instance) or by using
240+
[zcf.saveIssuer()](/reference/zoe-api/zoe-contract-facet#zcf-saveissuer-issuer-keyword).
241+
242+
:::
243+
244+
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.
245+
246+
<img src="./assets/trade-offer-safety-3.svg"
247+
style="border: 2px solid" width="600" />
248+
249+
<<< @/../snippets/zoe/src/gameAssetContract.js#makeInvitation
250+
251+
The offer handler is invoked with a _seat_ representing the party making the offer.
252+
It extracts the `give` and `want` from the party's offer and checks that
253+
they are giving at least the `joinPrice` and not asking for too many
254+
places in return.
255+
256+
With all these prerequisites met, the handler instructs `zcf` to mint the requested
257+
**Place** assets, allocate what the player is giving into its own `gameSeat`,
258+
and allocate the minted places to the player. Finally, it concludes its business with the player.
259+
260+
<img src="./assets/trade-offer-safety-4.svg"
261+
style="border: 2px solid" width="600" />
262+
263+
<<< @/../snippets/zoe/src/gameAssetContract.js#handler
264+
265+
Zoe checks that the contract's instructions are consistent with
266+
the offer and with conservation of assets. Then it allocates
267+
the escrowed payment to the contract's gameSeat and pays out
268+
the place NFTs to Alice in response to the earlier `getPayout(...)` call.
269+
270+
Alice asks the `Place` issuer what her payout is worth
271+
and tests that it's what she wanted.
272+
273+
<img src="./assets/trade-offer-safety-5.svg"
274+
style="border: 2px solid" width="600" />
275+
276+
<<< @/../snippets/zoe/contracts/alice-trade.js#payouts

0 commit comments

Comments
 (0)