Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reallocate api patch #519

Merged
merged 13 commits into from
Jun 8, 2021
4 changes: 0 additions & 4 deletions main/glossary/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,6 @@ The term comes from the expression "having a seat at the table" with regards to
For more details, see the [ZCFSeat documentation](/zoe/api/zoe-contract-facet.md#zcfseat-object) and
the [UserSeat documentation](/zoe/api/zoe.md#userseat-object).

## SeatStagings
`seatStagings` are associations of seats with new [allocations](#allocation). `seatStagings` are
passed to `zcf.reallocate()`.

## SES (Secure ECMAScript)

SES is a standards-track extension to the JavaScript standard. It
Expand Down
101 changes: 60 additions & 41 deletions main/zoe/api/zoe-contract-facet.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ to manipulate the offer. The queries and operations are as follows:
explicitly choosing to close out the `seat`. The guarantees also hold if the contract
encounters an error or misbehaves. There are several methods for finding out
what `amount` a current `allocation` is.


This is similar to the previous method, `getAmountAllocated()`. `getAmountAllocated()`
gets the `allocation` of one keyword at a time, while `getCurrentAllocation()` returns
Expand All @@ -273,7 +274,37 @@ to manipulate the offer. The queries and operations are as follows:
Asset: AmountMath.make(quatloosBrand, 5n),
Price: AmountMath.make(quatloosBrand, 9n)
}
```
```
### `ZCFSeat.incrementBy(amountKeywordRecord)`
- `amountKeywordRecord`: `{AmountKeywordRecord}`
- Returns: `{AmountKeyRecord}`
- Adds the `amountKeywordRecord` argument to the `ZCFseat`'s staged allocation and returns the
same `amountKeywordRecord` so it can be reused in another call. Note that this lets
`zcfSeat1.incrementBy(zcfSeat2.decrementBy(amountKeywordRecord))` work as a usage pattern.

### `ZCFSeat.decrementBy(amountKeywordRecord)`
- `amountKeywordRecord`: `{AmountKeywordRecord}`
- Returns: `{AmountKeywordRecord}`
- Subtracts the `amountKeywordRecord` argument from the `ZCFseat`'s staged allocation and returns the
same `amountKeywordRecord` so it can be used in another call. Note that this lets
`zcfSeat1.incrementBy(zcfSeat2.decrementBy(amountKeywordRecord))` work as a usage pattern.

The amounts to subtract cannot be
greater than the staged allocation (i.e. negative results are not allowed).

### `ZCFSeat.clear()`
- Returns: `{void}`
- Deletes the `ZCFSeat`'s current staged allocation, if any.

### `ZCFSeat.getStagedAllocation()`
- Returns: `{<Allocation>}`
- Gets and returns the `stagedAllocation`, which is the allocation committed if the seat is reallocated over, if offer safety holds and rights are conserved.

### `ZCFSeat.hasStagedAllocation()`
- Returns: `{boolean}`
- Returns `true` if there is a staged allocation, i.e. whether `incrementBy()` or `decrementBy()` has been called and `clear()`
and `reallocate()` have not. Otherwise returns `false`.

### `ZCFSeat.exit(completion)`
- `completion`: `{Object}`
- Returns: `void`
Expand All @@ -299,26 +330,7 @@ to manipulate the offer. The queries and operations are as follows:
```js
throw seat.fail(Error('you did it wrong'));
```
### `ZCFSeat.stage(newAllocation)`
- `newAllocation`: `{Allocation}`
- Returns: `{SeatStaging}`
- An `Allocation` is an `AmountKeywordRecord` of key-value pairs where
the key is a keyword such as `Asset` or `Price` applicable to the
contract. The value is an `amount` with its `value` and `brand`.

A `seatStaging` is an association of a `seat` with reallocations. `reallocate()` takes
at least two `seatStagings` as arguments and does its reallocation based on them.

You can create multiple independent `seatStagings` for a `seat`. None of them has
any effect until submitted to `reallocate()`. Each call to `stage()` starts from the
`seat`'s current `allocation` and uses the `newAllocation` as a replacement for the
current state. Any keywords not mentioned in `newAllocation` retain the
same `amounts`. All keywords mentioned in the `newAllocation` have their `amounts`
replaced with the corresponding `amount` from `newAllocation`.

Note that ZoeHelpers [`trade()`](./zoe-helpers.md#trade-zcf-left-right-lefthasexitedmsg-righthasexitedmsg)
and [`swap()`](./zoe-helpers.md#swap-zcf-leftseat-rightseat-lefthasexitedmsg-righthasexitedmsg) might
be easier to use for simple cases.

### `ZCFSeat.isOfferSafe(newAllocation)`
- `newAllocation`: `{Allocation}`
- Returns `{Boolean}`
Expand Down Expand Up @@ -421,39 +433,46 @@ a valid keyword, or is not unique.
```js
zcf.assertUniqueKeyword(keyword);
```
## `zcf.reallocate(seatStagings)`
- `seatStagings` `{SeatStaging[]}` (at least two)
## `zcf.reallocate(seats)`
- `seats` `{ZCFSeats[]}` (at least two)
- Returns: `{void}`

The contract reallocates over `seatStagings`, which are
associations of `seats` with new `allocations` to be used in reallocation.
There must be at least two `seatStagings` in the array argument.
`zcf.reallocate()` commits the staged allocations for each of its seat arguments,
making their staged allocations their current allocations. `zcf.reallocate()` then
transfers the assets escrowed in Zoe from one seat to another. Importantly, the assets
stay escrowed, with only the internal Zoe accounting of each seat's allocation changed.

There must be at least two `ZCFSeats` in the array argument. Every `ZCFSeat`
with a staged allocation must be included in the argument array or an error
is thrown. If any seat in the argument array does not have a staged allocation,
an error is thrown.

On commit, the staged allocations become the seats' current allocations and
the staged allocations are deleted.

Note: `reallocate()` is an *atomic operation*. To enforce offer safety,
it will never abort part way through. It will completely succeed or it will
fail before any seats have their current allocation changed.

The reallocation only succeeds if it:
1. Conserves rights (the specified `amounts` have the same total value as the
current total amount)
2. Is 'offer-safe' for all parties
involved. Offer safety is checked at the staging step.
2. Is 'offer-safe' for all parties involved.

The reallocation is partial, only applying to `seats` associated
tyg marked this conversation as resolved.
Show resolved Hide resolved
with the `seatStagings`. By induction, if rights conservation and
The reallocation is partial, only applying to the `seats` in the
argument array. By induction, if rights conservation and
offer safety hold before, they hold after a safe reallocation.

This is true even though we only re-validate for `seats` whose
`allocations` change. A reallocation can only effect offer safety for
allocations change. A reallocation can only effect offer safety for
those `seats`, and since rights are conserved for the change, overall
rights are unchanged.

`reallocate()` throws these errors:
`reallocate()` throws this error:
- `reallocating must be done over two or more seats`
- `The seatStaging was not recognized`
- `The seatStaging was not recognized`
- `keyword must be unique`
- If the total `amount` per `brand` is not equal to the total `amount` per `brand`
in the proposed reallocation. (no message)

```js
zcf.reallocate(
seat.stage(seatAAllocation),
seat.stage(seatBAllocation),
);
sellerSeat.incrementBy(buyerSeat.decrementBy({ Money: providedMoney }));
buyerSeat.incrementBy(sellerSeat.decrementBy({ Items: wantedItems }));
zcf.reallocate(buyerSeat, sellerSeat);
```
116 changes: 13 additions & 103 deletions main/zoe/api/zoe-helpers.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,104 +87,23 @@ if (satisfiedBy(offer, seat) && satisfiedBy(seat, offer)) {
swap(zcf, seat, offer);
```

## `trade(zcf, left, right, leftHasExitedMsg, rightHasExitedMsg)`
- `zcf` - `{ContractFacet}`
- `left` - `{SeatGainsLossesRecord}`
- `right` - `{SeatGainsLossesRecord}`
- `leftHasExitedMsg` - `{String}` - Optional
- `rightHasExitedMsg` - `{String}` - Optional
- Returns: void

::: details SeatGainsLossesRecord
- `seat` - `{ZCFSeat}`
- `gains` - `{AmountKeywordRecord}` - what the seat will
gain as a result of this trade
- `losses` - `{AmountKeywordRecord=}` - what the seat will give up
as a result of this trade. Losses is optional, but can only be
omitted if the keywords for both seats are the same. If losses is
not defined, the gains of the other seat is subtracted.
:::

**Note**: The `swap()` method is a specific use of `trade()`. In `swap()`,
for both `seats`, everything a `seat` wants is given to it, having been
taken from the other `seat`. `swap()` exits both `seats`, but `trade()` does not.
- Use `trade()` when any of these are true:
- The `seats` have different keywords.
- The `amounts` to be reallocated don't exactly match the wants of the `seats`.
- You want to continue to interact with the `seats` after the trade.
- Use `swap()` when all of these are true:
- Both `seats` use the same keywords.
- The `seats`' wants can be fulfilled from the other `seat`.
- No further `seat` interaction is desired.

This method always takes `zcf` as its first argument.

The `left` and `right` arguments are each `SeatGainsLossesRecords`
with `seat`, `gains`, and optional `losses` properties. `gains` and
`losses` are `amountKeywordRecords` describing declaratively what is
added or removed from that `seat`'s allocation.

`trade()` does a trade between the `seats` in `left` and `right`. If the two `seats` can trade,
it swaps their compatible assets.

Any surplus remains with its original `seat`. For example if `seat`
A gives 5 Quatloos and `seat` B only wants 3 Quatloos, `seat` A retains 2 Quatloos.

If either of the seats has exited, `trade` throws. `trade` itself does NOT
`fail` or `exit` either seat for any reason.

If the trade fails for reasons other than either seat exiting, it
throws the message `The trade between left and right failed. Please
check the log for more information`. It writes the specific error to
the console.

```js
import {
trade,
} from '@agoric/zoe/src/contractSupport';
trade(
zcf,
{
seat: poolSeat,
gains: { Central: amountIn },
losses: { Secondary: amountOut },
},
},
{
seat: swapSeat,
gains: { Out: amountOut },
losses: { In: amountIn },
},
);
```

## `swap(zcf, leftSeat, rightSeat, leftHasExitedMsg, rightHasExitedMsg)`
## `swap(zcf, leftSeat, rightSeat)`
- `zcf` `{ContractFacet}`
- `leftSeat` `{ZCFSeat}`
- `rightSeat` `{ZCFSeat}`
- `leftHasExitedMsg` - `{String}` - Optional
- `rightHasExitedMsg` - `{String}` - Optional
- Returns: `defaultAcceptanceMsg`

**Note**: The `swap()` method is a specific use of `trade()`. In `swap(),`
**Note**: In `swap(),`
for both `seats`, everything a `seat` wants is given to it, having been
taken from the other `seat`. `swap()` exits both `seats`, but `trade()` does not.
- Use `trade()` when any of these are true:
- The `seats` have different keywords.
- The `amounts` to be reallocated don't exactly match the wants of the `seats`.
- You want to continue interacting with the `seats` after the trade.
- Use `swap()` when all of these are true:
taken from the other `seat`. `swap()` exits both `seats`.
Use `swap()` when all of these are true:
- Both `seats` use the same keywords.
- The `seats`' wants can be fulfilled from the other `seat`.
- No further `seat` interaction is desired.

This method always takes `zcf` as its first argument.

`leftHasExitedMsg` and `rightHasExitedMsg` are optional and are passed
to `trade` within `swap` to add custom error messages in the case that
either seat has exited.

If the two `seats` can trade, then swap their compatible assets,
If the two `seats` can trade, they swap their compatible assets,
exiting both `seats`. It returns the message `The offer has been accepted.
Once the contract has been completed, please check your payout`.

Expand All @@ -202,40 +121,31 @@ import {
swap(zcf, firstSeat, secondSeat);
```

## `swapExact(zcf, leftSeat, rightSeat, leftHasExitedMsg, rightHasExitedMsg)`
## `swapExact(zcf, leftSeat, rightSeat)`
- `zcf` `{ContractFacet}`
- `leftSeat` `{ZCFSeat}`
- `rightSeat` `{ZCFSeat}`
- `leftHasExitedMsg` - `{String}` - Optional
- `rightHasExitedMsg` - `{String}` - Optional
- Returns: `defaultAcceptanceMsg`

**Note**: `swapExact()` and `swap()` are specific uses of `trade()`. In `swap()` and `swapExact()`,
In `swap()` and `swapExact()`,
for both seats, everything a seat wants is given to it, having been
taken from the other seat. `swap()` and `swapExact()` exit both seats, but `trade()` does not.
- Use `trade()` when any of these are true:
- The `seats` have different keywords.
- The `amounts` to be reallocated don't exactly match the wants of the `seats`.
- You want to continue interacting with the `seats` after the trade.
- Use `swap()` or `swapExact()` when all of these are true:
- Both `seats` use the same keywords.
taken from the other seat. `swap()` and `swapExact()` exit both seats.
Use `swap()` or `swapExact()` when both of these are true:
- The `seats`' wants can be fulfilled from the other `seat`.
- No further `seat` interaction is desired.

This method always takes `zcf` as its first argument.
For `swap()` only, both `seats` use the same keywords. This does **not**
hold for `swapExact()`

`leftHasExitedMsg` and `rightHasExitedMsg` are optional and are passed
to `trade()` within `swapExact()` to add custom error messages in the case that
either seat has exited. They default to `the left seat in swapExact() has exited`,
and `the right seat in swapExact() has exited` respectively.
This method always takes `zcf` as its first argument.

`exactSwap()` is a special case of `swap()` such that it is successful only
if both seats gain everything they want and lose everything they were willing to give.
It is only good for exact and entire swaps where each
tyg marked this conversation as resolved.
Show resolved Hide resolved
seat wants everything that the other seat has. The benefit of using
this method is that the keywords of each seat do not matter.

If the two `seats` can trade, then swap their compatible assets,
If the two `seats` can trade, they swap their compatible assets,
exiting both `seats`. It returns the message `The offer has been accepted.
Once the contract has been completed, please check your payout`.

Expand Down
2 changes: 1 addition & 1 deletion main/zoe/guide/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ The contract then uses `getProposal()` to extract the properties of the proposal
`makeMatchingInvitation()`, our first handler, then constructs a handler for the second offer,
with the first offer's `want` and `give` in scope. This second
handler, `matchingSeatOfferHandler()` does the final step.
It uses the [`swap` helper function](../api/zoe-helpers.md#swap-zcf-leftseat-rightseat-lefthasexitedmsg-righthasexitedmsg),
It uses the [`swap` helper function](../api/zoe-helpers.md#swap-zcf-leftseat-rightseat),
a powerful Zoe Helper that handles a lot of the logic of doing a basic swap of assets.

If the swap succeeds, it reallocates the assets between the parties, as described above. The handler then exits
Expand Down