Skip to content

Commit

Permalink
chore: update internal Zoe documentation (#4081)
Browse files Browse the repository at this point in the history
* chore: update internal Zoe and ERTP documentation

* docs(zoe): misc editorial

Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com>

* docs(zoe): prune obsolete reference to fees

Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com>

Co-authored-by: Dan Connolly <connolly@agoric.com>
Co-authored-by: Chris Hibbert <Chris-Hibbert@users.noreply.github.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Jan 8, 2022
1 parent 118694b commit 9e437d5
Show file tree
Hide file tree
Showing 14 changed files with 790 additions and 0 deletions.
67 changes: 67 additions & 0 deletions packages/ERTP/docs/user-safety.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# ERTP User Safety

## Both a protocol and an implementation

ERTP can be thought of as a protocol and an implementation. There
could be multiple different implementations of the protocol, but for
now, we just have one (@agoric/ertp) whose types specify the protocol.
The distinction between protocol and implementation becomes important
because when Zoe escrows assets for use in contracts, it does not
verify that the assets were made using the latest @agoric/ertp
implementation. This means that the issuers and brands of assets that
users escrow may misbehave.

Zoe specifically uses the @agoric/ertp implementation for contract invitations.

For every new brand of token, there is:
* A mint - this is the only object with the ability to mint tokens. To mint, call `mint.mintPayment`.
- A good example of an object capability - anyone who has access to the `mint` object can mint, and *only those* who have access to the `mint` object can mint.
* A brand - this is the object that is the embodiment of the brand of token. When making offers, including the brand allows you to specify exactly what tokens you are giving and will accept.
* An issuer - The trusted authority of which objects hold which tokens.

The only objects that can “hold” assets:
* Purses: An object that a user holds for the long-term, made directly using the issuer for the token
* Payments: An short-lived object that is usually created by withdrawing from a purse (Payments can also be created by minting.) Payments are what are sent to other people and sent as an offer to Zoe

More generally in ERTP:

* In ERTP, users rely on issuers, and must be very careful which issuers they choose to rely on.
* Users do not trust payments from other users. Users must use a trusted issuer to claim the payment or a purse (made from a trusted issuer) to deposit the payment.
* Users do not accept purses from other users.
* Users send amounts (amount = brand + value) to other users to reference the intended kind of an asset.


| If someone sends you x, you should: | |
|-------------------------------------|----|
|An issuer | Ask yourself: Given what I know about the sender and what the sender says the issuer is an authority on, am I willing to rely on this issuer as the authority for this particular brand of token? (See below for more information on when to accept issuers.) |
|A payment | Call `E(payment).getAllegedBrand()` to get the purported brand of the payment. Then, using the brand, look up the corresponding issuer or purse. Then, do either: `const newPaymentJustForMe = await E(issuer).claim(payment);` (This produces a new payment that only the user has access to. The old payment (which the sender might have access to) is used up and can be discarded.) Or: `await E(purse).deposit(payment);` This will also use up the old payment; it can now be discarded. The amount within the payment is transferred to the purse. |
| A purse | DO NOT ACCEPT. Just drop it. |
| A mint | Ask: why would someone do this? Probably, do not accept it. Mints are meant to stay in a contract and not be exposed, so someone sending a mint is highly unusual. |
| An amount | This is a very normal part of negotiation about a potential trade or deal. If you want to check whether the amount is well-formed for a particular brand that you already know, you can create a safe copy with: `const myAmount = AmountMath.coerce(particularBrand, amount);`
| A brand | This is a normal part of negotiation, if the value part of the amount isn’t yet known. This is how someone would tell you the kind of token that is wanted or given. |
| A value | Normally this would be part of an amount. Just make sure you know which brand or potential brands you are talking about so there’s no confusion. |


## Accepting Issuers

The dangers of accepting an issuer:
* The issuer is actually a different one than the one that you think
it is.
* The issuer wasn’t made using the ERTP implementation, and its actual
code does things like revoke assets from purses and payments
unexpectedly.

Examples of receiving purported “issuers” that should not be trusted:
* A stranger tells you about a new currency with issuer x.
* A stranger sends you issuer x and says it’s the issuer for something valuable, like the Agoric staking token.

Examples of receiving purported issuers that could be trusted:
* Your best friend sends you the issuer for a new currency they're
excited about
* A currency’s public website sends your wallet the issuer for its own currency
E.g. The well known AMM website tells your wallet that its liquidity
token issuer for a certain pool is x

If the issuer was made in a Zoe contract (currently, the only way for users to create issuers on-chain), then asserting that the issuer was produced in a contract by inspecting the contract code itself and the mechanisms by which the issuer is exposed, is the best way to be assured that an issuer is well-behaving.

Note that Zoe assumes that issuers may misbehave. Users of Zoe are required to rely on issuers at their own risk. A misbehaving issuer will not hurt Zoe or other users, but it will potentially hurt a user who makes an offer that `wants` or `gives` assets of the same `brand` as the issuer.
100 changes: 100 additions & 0 deletions packages/zoe/docs/handling-authority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Handling of Authority in ERTP and Zoe

Note: These are internal documentation notes. For how to use Zoe and
how to develop smart contracts, please see
https://agoric.com/documentation/

This document gives a quick overview of how Agoric handles authority
in ERTP and Zoe. Two case studies will be used: first, a study of how
ERTP purses interact with the ERTP paymentLedger, and second, a study
of the escrow code in Zoe.

## Use of Closures
Agoric has deliberately chosen to use closures to create objects and
store state. Agoric does not use classes, and many functions in Agoric
code are not purely functional. Rather, the functions and objects are
the means of conveying authority.

## Division of Files
Files in ERTP and Zoe have been structured to isolate and attenuate
authority so that POLA (Principle of Least Authority) is followed. For
example, in ERTP, the core code that controls the movement of assets
is isolated to paymentLedger.js. Because of this organization choice,
we can confine the `paymentLedger` WeakStore to this file and only
export attenuated access, in the form of an `issuer` and `mint`
object.

## Why not imports? Why pass functions and objects around?

In the object capabilities paradigm, access control is achieved by
selectively passing objects (or functions). This is a fundamental
aspect of object capabilities. These objects have methods which allow
the holder to take certain actions. Importantly, only the holder can
take these actions.

We use this pattern both in our external APIs and also within services
and packages. We do this in order to achieve defense-in-depth and
least authority. For example, we want the bare minimum of code to be
able to withdraw funds from Zoe’s escrow purses, and we want the code
to be easy to audit.

Imports do not serve the same purposes. If powerful capabilities can
be imported, then we have to restrict which files can import what,
thus relying on a lower level to give us guarantees. By passing
powerful capabilities instead, we can merely inspect the code and how
it is being used to ensure that the powerful capability does not
wrongfully escape.

## Case study: ERTP purses and the ERTP paymentLedger

First, some quick context on the file structure in ERTP. There are two
main entrypoints: amountMath.js and issuerKit.js. It is common for
users of ERTP to import `AmountMath` as a stateless library and use it
with already existing ERTP assets. issuerKit.js is how users create
new assets.

There is specific malicious behavior that ERTP must not allow:
* Users stealing funds from other users
* Users minting who do not have access to the mint
* Holders of a mint revoking assets.

In ERTP, the most powerful object is `paymentLedger`, which is a
mapping from payment objects to the amounts which they are said to
hold. Any movement of assets (payment to payment, or purse to payment,
or payment to purse) makes a change to `paymentLedger`. The malicious
behavior listed above would have to engage with the `paymentLedger` in
some regard to be a successful attack. Thus, we want to try to isolate
this powerful authority as much as possible, and be very clear about
when it can be accessed. To achieve this, the `paymentLedger` has its
own file, and never escapes the file.

Other parts of ERTP are also split into their own files. For example,
payments themselves have no internal state, so the code for making a
payment can be in an external file. The same with brands. Purses are
slightly different. We’ve chosen to divide up the code for making a
purse. Rather than add all of the purse-making code to
`paymentLedger`, we chose to only add the `deposit` and `withdraw`
logic. This allowed us to group together the code that accesses the
`paymentLedger`. The alternatives, such as passing the paymentLedger
itself, or moving all of the purse code into the paymentLedger file
would not be as easy to audit.

## Case study: EscrowStorage in Zoe
As one of its features, Zoe provides escrow services for users. This
means that within Zoe, there are ERTP purses containing all of the
funds for all of the users of contracts. This is obviously an area of
high authority that needs as much protection as possible.

To achieve this goal, the code for escrowing is in a separate file:
https://github.com/Agoric/agoric-sdk/blob/198e5c37b4ce3738ed5776c36c949847a226265c/packages/zoe/src/zoeService/escrowStorage.js

Isolating the purses in this way means that there is no way to
directly access the purse objects, outside the file. And more, the
only way that assets can be withdrawn from the purses is through the
function `withdrawPayments`.

Thus, once the file itself has been audited to ensure these
assumptions are correct, then the reviewer only has to search for uses
of `withdrawPayments`. There is only one usage, which is to make a
payout:
https://github.com/Agoric/agoric-sdk/blob/198e5c37b4ce3738ed5776c36c949847a226265c/packages/zoe/src/zoeService/zoeSeat.js#L49
18 changes: 18 additions & 0 deletions packages/zoe/docs/platform-specification.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Platform Specification

Note: These are internal documentation notes. For how to use Zoe and
how to develop smart contracts, please see
https://agoric.com/documentation/

Zoe and ERTP depend on layers of Agoric-specific infrastructure.

Dean Tribble (@dtribble) gave a presentation that specifies and
explains the infrastructure that Zoe and ERTP depend on.

[Link to
Recording](https://drive.google.com/file/d/1dhPD2PWxIpiepvIx3_bek0tDBTI_DQB6/view)
(ask for permission to view)

[Link to
Slides](https://docs.google.com/presentation/d/1Ejrs4PH3ZfQ9uKFO47wQvOYCHSMbcJxgjCyfO-Rg3VM/edit?usp=sharing)
(should be accessible to all)
98 changes: 98 additions & 0 deletions packages/zoe/docs/seats.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# Seats in the Zoe Service and Zoe Contract Facet

Note: These are internal documentation notes. For how to use Zoe and
how to develop smart contracts, please see
https://agoric.com/documentation/


__UserSeat.tryExit() Flow:__

![UserSeat Exit Flow](./user-seat-exit-flow.png)

__ZCFSeat.exit() Flow:__

![ZCFSeat Exit Flow](./zcf-seat-exit-flow.png)

__ZCF.reallocate() Flow:__

![ZCF Reallocate Flow](./zcf-reallocate-flow.png)


## UserSeat

The `UserSeat` is what is returned when a user calls
`E(zoe).offer(invitation, proposal, payments, offerArgs, feePurse)`. It has the following
type:

```js
/**
* @typedef {Object} UserSeat
* @property {() => Promise<Allocation>} getCurrentAllocation
* @property {() => Promise<ProposalRecord>} getProposal
* @property {() => Promise<PaymentPKeywordRecord>} getPayouts
* @property {(keyword: Keyword) => Promise<Payment>} getPayout
* @property {() => Promise<OfferResult>} getOfferResult
* @property {() => void=} tryExit
* @property {() => Promise<boolean>} hasExited
* @property {() => Promise<Notifier<Allocation>>} getNotifier
*/
```

Note that `tryExit` is only present if the exit rule is `onDemand`. The
user can use the seat to get their payout, get the result of their
offer (whatever the contract chooses to return. This varies, but
examples are a string and an invitation for another user.)

## ZCFSeat

The `ZCFSeat` is a facet of the same seat, specifically for the
contract to manipulate. It is the `ZCFSeat` that is passed as the first
parameter to `offerHandlers`:

```js
const buyItems = buyerSeat => {
const proposal = buyerSeat.getProposal();
const moneyGiven = buyerSeat.getAmountAllocated('Money', moneyBrand);
...
```
The type of the ZCFSeat is:
```js
/**
* @typedef {Object} ZCFSeat
* @property {() => void} exit
* @property {ZCFSeatFail} fail
* @property {() => Notifier<Allocation>} getNotifier
* @property {() => boolean} hasExited
* @property {() => ProposalRecord} getProposal
* @property {ZCFGetAmountAllocated} getAmountAllocated
* @property {() => Allocation} getCurrentAllocation
* @property {() => Allocation} getStagedAllocation
* @property {() => boolean} hasStagedAllocation
* @property {(newAllocation: Allocation) => boolean} isOfferSafe
* @property {(amountKeywordRecord: AmountKeywordRecord) => AmountKeywordRecord} incrementBy
* @property {(amountKeywordRecord: AmountKeywordRecord) => AmountKeywordRecord} decrementBy
* @property {() => void} clear
*/
```
## ZoeSeatAdmin
Internal to Zoe Service code and passed to ZCF. Never external.
The `ZoeSeatAdmin` is the administrative facet of a seat within Zoe.
When `exit()` is called on this object, the payouts accessible through
the `UserSeat` are resolved. `replaceAllocation` changes the Zoe
allocation to the `replacementAllocation`.
The type of the `ZoeSeatAdmin` is:
```js
/**
* @typedef {Object} ZoeSeatAdmin
* @property {(allocation: Allocation) => void} replaceAllocation
* @property {ZoeSeatAdminExit} exit
* @property {ShutdownWithFailure} fail called with the reason
* for calling fail on this seat, where reason is normally an instanceof Error.
*/
```
Binary file added packages/zoe/docs/user-seat-exit-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions packages/zoe/docs/user-seat-exit-flow.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
@startuml UserSeat.tryExit() flow

package ZoeService <<Rectangle>> {
object UserSeat
UserSeat : tryExit()
UserSeat : ...

object ZoeSeatAdmin
ZoeSeatAdmin : exit()
ZoeSeatAdmin : ...
}

package ZCF <<Rectangle>> {
object ZCFSeat
ZCFSeat : exit()
ZCFSeat : ...

object ExitObj
ExitObj : exit()
ExitObj : ...
}

ZoeService -[hidden]> ZCF
UserSeat -[hidden]> ZoeSeatAdmin
UserSeat --|> ExitObj : (1) exit
ExitObj --|> ZCFSeat: (2) exit
ZCFSeat --|> ZoeSeatAdmin: (3) exit
@enduml
Binary file added packages/zoe/docs/zcf-reallocate-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions packages/zoe/docs/zcf-reallocate-flow.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@startuml ZCF.reallocate() flow

package ZoeService <<Rectangle>> {
object UserSeat
UserSeat : tryExit()
UserSeat : ...

object ZoeSeatAdmin
ZoeSeatAdmin : exit()
ZoeSeatAdmin : replaceAllocation()
}

package ZCF <<Rectangle>> {
object ZCFSeat
ZCFSeat : exit()
ZCFSeat : ...

object ZCFSeatAdmin
ZCFSeatAdmin : commit()

}

ZCFSeat --|> ZCFSeatAdmin : 2) looked up in internal map
ZCFSeatAdmin --|> ZoeSeatAdmin : 3) commit()
ZoeSeatAdmin --|> ZoeSeatAdmin : 4) replaceAllocation()
@enduml
Binary file added packages/zoe/docs/zcf-seat-exit-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions packages/zoe/docs/zcf-seat-exit-flow.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
@startuml ZCFSeat.exit() flow

package ZoeService <<Rectangle>> {
object UserSeat
UserSeat : tryExit()
UserSeat : ...

object ZoeSeatAdmin
ZoeSeatAdmin : exit()
ZoeSeatAdmin : ...
}

package ZCF <<Rectangle>> {
object ZCFSeat
ZCFSeat : exit()
ZCFSeat : ...

object ExitObj
ExitObj : exit()
ExitObj : ...
}

ZCFSeat --|> ZoeSeatAdmin: (1) exit
@enduml
Loading

0 comments on commit 9e437d5

Please sign in to comment.