Skip to content

Commit 28cb2fb

Browse files
committed
docs: durable contract details collected in one place
1 parent a03ff28 commit 28cb2fb

File tree

2 files changed

+126
-0
lines changed

2 files changed

+126
-0
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',

main/guides/zoe/contract-details.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
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 it needs 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
16+
[Baggage](http://localhost:5173/guides/zoe/contract-upgrade.html#baggage), and ensuring that reachable objects
17+
that will be accessible to users have an identity that can be maintained as the behavior changes with contract
18+
upgrades.
19+
20+
We use zone.exo(), zone.exoClass(), and zone.exoClassKit() to define durable objects.
21+
22+
[Zone](http://localhost:5173/glossary/#zone) provides an interface for defining objects and classes that
23+
supports both ephemeral objects (allocated on the heap), and durable objects that can persist and that
24+
SwingSet will page in or out on demand.
25+
26+
Our persistent objects are designed to encapsulate their state, and can present different facets to different
27+
clients to distinguish different aspects of authority. `zone.exoClass()` defines a kind with a single facet,
28+
while `zone.exoClassKit()` defines a kind with multiple facets. (Here are some relevant [naming
29+
conventions](https://docs.agoric.com/guides/ertp/#method-naming-structure).)
30+
31+
```
32+
zone.exoClassKit(tag, guard, init, methodKit, options)
33+
zone.exoClass(tag, guard, init, methods, options)
34+
zone.exo(tag, guard, methods, options)
35+
```
36+
37+
The next several sub-sections explain how the parameters to those functions are used.
38+
39+
### Tag: naming kinds of objects
40+
41+
The `tag` provides the identity of the kind of object, which is associated with the defined behavior. When a
42+
contract (or other vat) is restarted or upgraded, SwingSet requires that all kinds that were previously
43+
defined in a vat be defined again. With an upgrade, the behavior can be changed. We use the term "null
44+
upgrade" to refer to upgrades that don't change the behavior.
45+
46+
### Init: specifying state
47+
48+
The `init` parameter defines a function that is used to define the state associated with each instance of an
49+
object. Notice that `exo()` doesn't take `init` as a parameter; this limits the defined (singleton) object to
50+
referencing values defined in the enclosing scope. Classes can also refer to variables in their defining
51+
scope, but any values inherited from the scope will be accessible to all instances of that type. The `init`
52+
function's parameters become the parameters of the maker function returned by `exoClass()` and
53+
`exoClassKit()`. `init`'s return value is the state that will be preserved by SwingSet for each
54+
object. `exo()` doesn't have an init parameter, so it creates and returns the singleton immediately rather
55+
than returning a maker.
56+
57+
The Exo objects (or just "Exos") defined by these functions are both persistent and virtualizable. SwingSet
58+
knows their complete state, so it can page them out when space is tight, and page them back in when they are
59+
referenced again.
60+
61+
Fields of the object returned by the `init` function become fields of the persistent state. (These cannot
62+
currently be changed on upgrade, though we're considering relaxing that restriction.) Within methods they can
63+
each be accessed as fields of `this.state`. Our convention is to extract the fields that will be used in a
64+
method on its first line, like `const { a, b } = this.state;` Once that has been done, those variable can be
65+
read or written as normal javascript variables, and the values will persist. (For more details, see [the note
66+
here](https://docs.agoric.com/guides/zoe/contract-upgrade.html#kinds)).
67+
68+
### Methods: defining behavior
69+
70+
The methods argument of `exoClass()` is a bag of methods, written in [concise method
71+
syntax](https://github.com/Agoric/agoric-sdk/wiki/Arrow-Function-Style#far-classes-do-not-use-arrow-function-style).
72+
`exoClass()` defines a single facet.
73+
74+
The methodKit parameter to `exoClassKit` is a record of labelled bags of methods. Each label defines a facet
75+
of the object. All facets of each object share access to the same state, but each facet is a separate
76+
capability. Within the methods, other facets can be reached by name within `this.facets`. The maker you get
77+
from `exoClassKit()` builds a new object each time it is called, and return all the facets. The caller can
78+
decide which of the facets to pass to each recipient.
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.
122+

0 commit comments

Comments
 (0)