Skip to content

Commit 537427b

Browse files
Durable contracts (#1255)
* docs: durable contract details collected in one place * docs: more detail and more cross links * chore: prettier's suggestions * chore: clean up links and improve wording Thanks, Dan! * chore: cleanup with yarn format * chore: better format for glossary link
1 parent 8fe5188 commit 537427b

File tree

5 files changed

+193
-30
lines changed

5 files changed

+193
-30
lines changed

main/.vitepress/config.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,10 @@ export default defineConfig({
216216
text: 'Complete Contract Walk-Through',
217217
link: '/guides/zoe/contract-walkthru',
218218
},
219+
{
220+
text: 'Durable Contract Details',
221+
link: '/guides/zoe/contract-details',
222+
},
219223
{
220224
text: 'Contract Upgrade',
221225
link: '/guides/zoe/contract-upgrade',
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
# Durable Contract Details
2+
3+
Zoe allows you to write smart contracts that manage interactions between cooperative, but possibly untrusting,
4+
parties. Some contracts perform a single simple task like trading one good for another, and then are
5+
done. Others stay around and manage assets and interactions over time. The first kind don't need persistence
6+
or the ability to upgrade, while the second kind need to make use of more sophisticated functionality.
7+
8+
When a contract is intended to continue running and serving many customers over a long time, its objects and
9+
data need to be persistent, its owners or managers may need to be able to adjust parameters or perform other
10+
governance actions, and they may want it to be upgradeable so that the code can adapt over time.
11+
12+
## Durable Objects
13+
14+
The first step toward contracts that can be upgraded is storing all the data that a later incarnation will
15+
need. This means putting relevant state in [Baggage](/guides/zoe/contract-upgrade#baggage), and ensuring that
16+
reachable objects that will be accessible to clients have an identity that can be maintained as the behavior
17+
changes with contract upgrades.
18+
19+
We use zone.exo(), zone.exoClass(), and zone.exoClassKit() to define durable objects.
20+
21+
[Zone](/glossary/#zone) provides an interface for defining objects and classes that supports both ephemeral
22+
objects (allocated on the heap), and durable objects that can persist and that
23+
[SwingSet](/guides/platform/#swingset) will page in or out on demand.
24+
25+
Our persistent objects are designed to encapsulate their state, and can present different facets to different
26+
clients to distinguish different aspects of authority. `zone.exoClass()` defines a kind with a single facet,
27+
while `zone.exoClassKit()` defines a kind with multiple facets. (Here are some relevant [naming
28+
conventions](/guides/ertp/#method-naming-structure).)
29+
30+
```
31+
zone.exoClassKit(tag, guard, init, methodKit, options)
32+
zone.exoClass(tag, guard, init, methods, options)
33+
zone.exo(tag, guard, methods, options)
34+
```
35+
36+
The next several sub-sections explain how the parameters to those functions are used.
37+
38+
### Tag: naming kinds of objects
39+
40+
The `tag` provides the identity of the kind of object, which is associated with the defined behavior. When a
41+
contract (or other vat) is restarted or upgraded, SwingSet requires that all kinds that were previously
42+
defined in a vat be defined again. With an upgrade, the behavior can be changed. We use the term "null
43+
upgrade" to refer to upgrades that don't change the behavior.
44+
45+
### Init: specifying state
46+
47+
The `init` parameter defines a function that is used to define the state associated with each instance of an
48+
object. Notice that `exo()` doesn't take `init` as a parameter; this limits the defined (singleton) object to
49+
referencing values defined in the enclosing scope. Classes can also refer to variables in their defining
50+
scope, but any values inherited from the scope will be accessible to all instances of that type. The `init`
51+
function's parameters become the parameters of the maker function returned by `exoClass()` and
52+
`exoClassKit()`. `init`'s return value is the state that will be preserved by SwingSet for each
53+
object. `exo()` doesn't have an init parameter, so it creates and returns the singleton immediately rather
54+
than returning a maker.
55+
56+
The Exo objects (or just "Exos") defined by these functions are both persistent and virtualizable. SwingSet
57+
knows their complete state, so it can page them out when space is tight, and page them back in when they are
58+
referenced again.
59+
60+
Fields of the object returned by the `init` function become fields of the persistent state. (_These cannot
61+
currently be changed on upgrade, though we're considering relaxing that restriction._) Within methods they can
62+
each be accessed as fields of `this.state`. Our convention is to extract the fields that will be used in a
63+
method on its first line, like `const { a, b } = this.state;` Once that has been done, those variable can be
64+
read or written as normal javascript variables, and the values will persist. (For more details, see [the note
65+
here](/guides/zoe/contract-upgrade.html#kinds)).
66+
67+
### Methods: defining behavior
68+
69+
The methods argument of `exoClass()` is a record of methods, written in [concise method
70+
syntax](https://github.com/Agoric/agoric-sdk/wiki/Arrow-Function-Style#far-classes-do-not-use-arrow-function-style).
71+
`exoClass()` defines a single facet.
72+
73+
The methodKit parameter to `exoClassKit` is a record that can define multiple facets. The keys give the names
74+
of the facets, and each value defines the methods of the corresponding facet. All facets of each object share
75+
access to the entire state defined in `init`, but each facet is a separate capability. Within the methods,
76+
other facets can be reached by name within `this.facets`. The maker returned by `exoClassKit()` builds a new
77+
object each time it is called, and returns all the facets. The caller can decide which of the facets to hold
78+
or to pass to separate recipients.
79+
80+
### Guards: defensive methods
81+
82+
These objects and facets are designed to be durable and shareable across address space boundaries. Since code
83+
in different vats might not be mutually trusting, code needs to be defensive about parameters received from
84+
remote callers. Interface Guards let us express in code what parameters each method expects and can handle
85+
safely. If a caller provides a parameter that doesn't match the template specified in the guard, SwingSet
86+
returns an exception to the caller without notifying the receiver. If the return value doesn't match, the
87+
function returns an exception to the caller.
88+
89+
`exoClass()` takes a guard for a single interface, defined by
90+
91+
```
92+
M.interface('name', {
93+
methodA: M.call(paramsAShape).returns(resultAShape),
94+
methodB: M.callWhen(M.await(paramsBShape)).returns(resultBShape),
95+
}
96+
```
97+
98+
`M.call()` verifies that all parameters match the guard before passing them through to the
99+
method. `M.callWhen(M.await(paramsBGuard))` awaits the resolution of the parameter, and then verifies that the
100+
result matches before invoking the method. When a guard is written this latter way, the method doesn't have to
101+
be `async`. In both cases, the method doesn't have to check for compliance with the guard.
102+
103+
[Shapes can specify](https://endojs.github.io/endo/interfaces/_endo_patterns.PatternMatchers.html) simple
104+
types like `M.string()`, `M.remotable()`, and `M.number()`, as well as complex structures of records and
105+
arrays. The list of parameters can specify required and optional parameters, as well as allowing unspecified
106+
rest parameters.
107+
108+
If you want to make use of the power of this type-checking within methods, you can call `mustMatch(specimen,
109+
pattern)` or `matches(specimen, pattern)` directly. The former throws if the pattern doesn't match, while the
110+
latter returns a boolean.
111+
112+
### Options: finish and stateShape
113+
114+
All the type definers can also take an [options
115+
argument](https://endojs.github.io/endo/types/_endo_exo.FarClassOptions.html) at the end, which is commonly
116+
used for a `finish()` function to complete initialization, or a stateShape, which can enforce invariants on
117+
the state values.
118+
119+
`finish()`, if provided, is called after instances have been created but before they are returned to the
120+
caller. They can be used to send an initial state update, or to complete initialization which couldn't be done
121+
in `init`. `finish` has access to state and facets if needed.

main/guides/zoe/contract-upgrade.md

Lines changed: 28 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
# Contract Upgrade
22

3-
The result of starting a contract includes the right to upgrade the contract instance. A call to [E(zoe).startInstance(...)](/reference/zoe-api/zoe.md#e-zoe-startinstance-installation-issuerkeywordrecord-terms-privateargs) returns a record of several objects that represent different levels of access.
4-
The `publicFacet` and `creatorFacet` are defined by the contract.
5-
The `adminFacet` is defined by Zoe and includes methods to upgrade the contract.
3+
The return value when starting a contract includes a capability to upgrade the contract instance. A call to
4+
[E(zoe).startInstance(...)](/reference/zoe-api/zoe.md#e-zoe-startinstance-installation-issuerkeywordrecord-terms-privateargs)
5+
returns a [kit](/guides/ertp/#method-naming-structure) of [facets](/glossary/#facet); that is a
6+
record of several objects that represent different ways to access the contract instance. The
7+
`publicFacet` and `creatorFacet` are defined by the contract. The
8+
[`adminFacet`](/reference/zoe-api/zoe.html#adminFacet) is defined by Zoe and includes methods to
9+
upgrade the contract.
610

711
::: tip Upgrade Governance
812

@@ -60,16 +64,26 @@ There are a few requirements for the contract that differ from non-upgradable co
6064
6165
### Upgradable Declaration
6266
63-
The new code bundle declares that it supports upgrade by exporting a `prepare` function in place of `start`.
67+
The new code bundle declares that it supports upgrade by including a `meta` record in addition to
68+
`start`. (_We used to indicate upgradability by using `prepare` instead of `start`, but that
69+
approach is deprecated._)
6470
65-
<<< @/../snippets/zoe/src/02b-state-durable.js#export-prepare
71+
`meta` is a record with any or all of `upgradability`, `customTermsShape`, and `privateArgsShape`
72+
defined. The latter two are optional
73+
[Patterns](https://endojs.github.io/endo/modules/_endo_patterns.html) restricting respectively
74+
acceptable `terms`, and `privateArgs`. `upgradability` can be `none` (the contract is not
75+
upgradable), `canUpgrade` (this code can perform an upgrade), or `canBeUpgraded` (the contract
76+
stores kinds durably such that the next version can upgrade).
77+
78+
<<< @/../snippets/zoe/src/02b-state-durable.js#export-start
6679
6780
### Durability
6881
69-
The 3rd argument, `baggage`, of the `prepare` function is a `MapStore`
70-
that provides a way to preserve state and behavior of objects
71-
between incarnations in a way that preserves identity of objects
72-
as seen from other vats:
82+
<a id="baggage"></a>
83+
84+
The 3rd argument, `baggage`, of the `start` function is a `MapStore` that is saved by the kernel
85+
across restarts of the contract. It provides a way to preserve state and behavior of objects between
86+
incarnations in a way that also maintains the identity of objects as seen from other [vats](/glossary/#vat).
7387
7488
```js
7589
let rooms;
@@ -113,7 +127,8 @@ When the contract instance is restarted, its [vat](../js-programming/#vats-the-u
113127

114128
### Kinds
115129

116-
Use `zone.exoClass()` to define state and methods of kinds of durable objects such as `Room`:
130+
Use [`zone.exoClass()`](./contract-details.md#durable-objects) to define state and methods of kinds
131+
of durable objects such as `Room`:
117132

118133
<<< @/../snippets/zoe/src/02b-state-durable.js#exoclass
119134

@@ -141,8 +156,9 @@ const makeRoom = zone.exoClass('Room', RoomI, id => ({ id, value: 0 }), {
141156
});
142157
```
143158

144-
The interface guard also needs updating.
145-
<small>_See [@endo/patterns](https://endojs.github.io/endo/modules/_endo_patterns.html) for more on interface guards._</small>
159+
The interface guard also needs updating. <small>_[The Durable
160+
objects](./contract-details.md#guards-defensive-methods) section has more on interface
161+
guards._</small>
146162

147163
```js
148164
const RoomI = M.interface('Room', {
@@ -175,20 +191,6 @@ Define all exo classes/kits before any incoming method calls from other vats --
175191
176192
:::
177193
178-
### Baggage
179-
180-
baggage is a MapStore that provides a way to preserve the state and behavior of objects between [smart contract upgrades](/guides/zoe/contract-upgrade) in a way that preserves the identity of objects as seen from other [vats](#vat). In the provided contract, baggage is used to ensure that the state of various components is maintained even after the contract is upgraded.
181-
182-
```js
183-
export const start = async (zcf, privateArgs, baggage) => {
184-
// ...
185-
const { accountsStorageNode } = await provideAll(baggage, {
186-
accountsStorageNode: () => E(storageNode).makeChildNode('accounts')
187-
});
188-
// ...
189-
};
190-
```
191-
192194
### Exo
193195
194196
An Exo object is an exposed Remotable object with methods (aka a [`Far`](/guides/js-programming/far) object) which is

main/reference/zoe-api/zoe.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,24 +269,58 @@ It returns a **Promise** for a **StartInstanceResult** object. The object consis
269269
- **instance**: **Instance**
270270
- **creatorInvitation**: **Payment | undefined**
271271

272-
The **adminFacet** has one method:
272+
<a id="adminfacet"></a>
273+
274+
The **adminFacet** has four methods:
273275

274276
- **getVatShutdownPromise()**
277+
275278
- Returns a promise that resolves to reason (the value passed to **fail(reason)**) or
276279
completion (the value passed to **exit(completion)**) when this newly started instance terminates.
277280

281+
- **restartContract(newPrivateArgs?)**
282+
283+
- **newPrivateArgs**: **any** - Optional
284+
- returns VatUpgradeResults (a record with one field: incarnationNumber)
285+
286+
Restarts the contract without changing the contract bundle
287+
288+
- **upgradeContract(contractBundleId, newPrivateArgs)**
289+
290+
- **contractBundleId**: **string**
291+
- **newPrivateArgs**: **any** - Optional
292+
293+
- returns VatUpgradeResults (a record with one field: incarnationNumber)
294+
295+
Upgrades the contract to use source code from a new bundle.
296+
297+
See [Contract Upgrade](/guides/zoe/contract-upgrade) for a description the
298+
process of upgrading contracts.
299+
300+
- **terminateContract(reason)**
301+
302+
- **reason**: **Error**
303+
304+
terminates the contract. `reason` will be provided as the failure reason.
305+
306+
<a id="publicfacet"></a>
307+
278308
A **publicFacet** is an object available via Zoe to anyone knowing
279309
the instance they are associated with. The **publicFacet** is used for general queries
280310
and actions, such as getting a current price or creating public **[Invitations](./zoe-data-types#invitation)**. Since a
281311
facet is defined just as any other object,
282312
the contract developer can add methods to them just like they would any object.
283313

314+
<a id="creatorfacet"></a>
315+
284316
The **creatorFacet** is only available in this return value (i.e. only when starting
285317
a contract instance). The contract designer
286318
should use it to encapsulate things that the contract runner might not want to share,
287319
or might want to control the distribution of. The party who starts the contract
288320
should carefully consider the impact before sharing access to the **creatorFacet**.
289321

322+
<a id="creatorinvitation"></a>
323+
290324
**creatorInvitation** is an **Invitation** that the contract instance creator can use.
291325
It is usually used in contracts where the creator immediately sells
292326
something (auctions, swaps, etc.), so it's helpful for the creator to have

snippets/zoe/src/02b-state-durable.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ const RoomMakerI = M.interface('RoomMaker', {
1717
});
1818
// #endregion interface-guard
1919

20-
// #region export-prepare
21-
export const prepare = (_zcf, _privateArgs, baggage) => {
22-
// #endregion export-prepare
20+
// #region export-start
21+
export const meta = { upgradability: 'canUpgrade' };
22+
23+
export const start = (_zcf, _privateArgs, baggage) => {
24+
// #endregion export-start
2325
// #region zone1
2426
const zone = makeDurableZone(baggage);
2527
const rooms = zone.mapStore('rooms');

0 commit comments

Comments
 (0)