Skip to content

Commit

Permalink
Merge branch 'master' into update-rank-sort-order
Browse files Browse the repository at this point in the history
  • Loading branch information
FUDCo authored Jan 8, 2022
2 parents baf99cd + 118694b commit f7dd333
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 94 deletions.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
124 changes: 81 additions & 43 deletions packages/run-protocol/src/vpool-xyk-amm/constantProduct/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,25 +11,26 @@ actions of arbitrageurs. At any time a trader can trade with the pool by
offering to deposit one of the two assets. They will receive an amount
of the complementary asset that will maintain the invariant that the product of
the balances doesn't decrease. (Rounding is done in favor of the
pool.) A fee is charged on the swap to reward the liquidity providers.
pool.)

The user can specify a maximum amount they want to pay or a minimum amount they
want to receive. Unlike Uniswap, this approach will charge less than the user
offered or pay more than they asked for when appropriate. By analogy, if a user
is willing to pay up to $20 when the price of soda is $3 per bottle, it would
give 6 bottles and only charge $18. Uniswap doesn't adjust the provided price,
so it charges $20. This matters whenever the values of the smallest unit of the
currencies are significantly different, which is common in DeFi. (We refer to
these as "improved" prices.)
give 6 bottles and only charge $18. (We refer to these as "improved" prices.)
Uniswap doesn't adjust the provided price, so it would charge $20 in that trade.
This matters whenever the values of the smallest unit of the currencies are
significantly different, which is common in DeFi.

The rules that drive the design include

* When the user names an input (or output) price, they shouldn't pay more
* When the user specifies an input (or output) price, they shouldn't pay more
(or receive less) than they said.
* The pool fee is charged against the side not specified by the user (the
"computed side").
"computed side"). This increases the pool balance without impacting the amount
that the user specified.
* The protocol fee is always charged in RUN.
* The fees should be calculated based on the pool balances before a transaction.
* The fees are calculated based on the pool balances before the transaction.
* Computations are rounded in favor of the pool.

We start by estimating the exchange rate, and calculate fees based on that. Once
Expand All @@ -41,11 +42,23 @@ extracted from the pools to adhere to those rules.
In these tables BLD represents any collateral. The user can specify how much
they want or how much they're willing to pay. We'll call the value they
specified **sGive** or **sGet** and bold it. We'll always refer to the currency
being added as X (regardless of whether it's what they pay or what they receive)
and the currency the user gets as Y. This table shows which brands the
amounts each have, as well as what is computed vs. given. The PoolFee is
computed based on the calculated amount (BLD in rows 1 and 2; RUN in rows 3 and
4). The Protocol fee is always in RUN.
being added as X and the currency the user gets as Y. X and Y are the amounts
used to maintain the constant product invariant. On every transaction the pool
balance will also increase on one side or the other by the pool fee, and the
protocol fee will be extracted outside the pool. So the amount produced is less
(or the amount paid is more) because the invariant is maintained on the amounts
remaining after fees have been charged.

In the final table, we'll see what the user pays or gets (sGet, sGive), how much
the pool balances change (xIncr, yDecr) and the changes that impact the constant
product calculation(ΔX, ΔY). The amount paid and received by the
trader and changes to the pool are calculated relative to ΔX and ΔY
so that the pool grows by the poolFee and the protocolFee can be paid from the
proceeds.

This table shows which brands the amounts each have, as well as what is computed
vs. given. The PoolFee is computed based on the calculated amount (BLD in rows 1
and 2; RUN in rows 3 and 4). The Protocol fee is always in RUN.

| | In (X) | Out (Y) | PoolFee | Protocol Fee | Specified | Computed |
|---------|-----|-----|--------|-----|------|-----|
Expand All @@ -54,60 +67,85 @@ computed based on the calculated amount (BLD in rows 1 and 2; RUN in rows 3 and
| **BLD in** | BLD | RUN | RUN | RUN | **sGive** | sGet |
| **BLD out** | RUN | BLD | RUN | RUN | **sGet** | sGive |

We'll estimate how much the pool balances would change in the no-fee, improved
price case using the constant product formulas. We call these estimates
δX, and δY. The fees are based on δX, and δY. ρ is
the poolFee (e.g. .003).
We'll calculate how much the pool balances would change in the no-fee, improved
price case using the constant product formulas. We call these results δX,
and δY. This table uses lower case δ to distinguish estimates from
final values, which use capital Δ.

The pool fee will be ρ times whichever of δX and δY was
calculated. The protocol fee will be ρ * δX when RUN is paid in, and
ρ * δY when BLD is paid in.
The fees are based on δX, and δY. ρ is the poolFee (e.g., 30
basis points). φ is the protocol fee (e.g., 6 basis points). The pool fee
will be ρ times whichever of δX and δY was calculated. The
protocol fee will be φ * δX when RUN is paid in, and φ * δY
when BLD is paid in. (α and β in this table are the no-fee versions.)

| | δX | δY | PoolFee | Protocol Fee |
|---------|-----|-----|--------|-----|
| **RUN in** | **sGive** | calc | ρ × δY | ρ × **sGive** (= ρ × δX) |
| **RUN out** | calc | **sGet** | ρ × δY | ρ × **sGet** (= ρ × δY) |
| **BLD in** | **sGive** | calc | ρ × δX | ρ × δY |
| **BLD out** | calc | **sGet** | ρ × δX | ρ × δX |
| **RUN in** | **sGive** | y * α / (1 + α) | ρ × δY | φ × **sGive** (= φ × δX) |
| **RUN out** | x * β / (1 - β) | **sGet** | ρ × δX | φ × **sGet** (= φ × δY) |
| **BLD in** | **sGive** | y * α / (1 + α) | ρ × δY | φ × δY |
| **BLD out** | x * β / (1 - β) | **sGet** | ρ × δX | φ × δX |

In rows 1 and 3, **sGive** was specified and sGet will be calculated. In rows 2
and 4, **sGet** was specified and sGive will be calculated. Once we know the
fees, we can add or subtract the fees and calculate the pool changes.

Notice that the ProtocolFee always affects the inputs to the constant product
calculation (because it is collected outside the pool). The PoolFee is visible
in the formulas in this table when the input to the calculation is in RUN.
The next table shows how the amount added to and subtracted from the pool (xIncr
and yIncr) differs from what the trader gives or gets and the change in the pool
values. While X' and Y' are the vaules resulting from the constant product
formula, xHat and yHat are the new values in the pool, reflecting the
contribution of the pool fee. From the diagram, xIncr is the difference between
X and xHat, while yIncr is the difference between Y and yHat.

| | input estimate | output estimate |
|---------|-----|-----|
| **RUN in** | **sGive** - ProtocolFee | |
| **RUN out** | | **sGet** + ProtocolFee + PoolFee |
| **BLD in** | **sGive** - ProtocolFee - PoolFee | |
| **BLD out** | | **sGet** + ProtocolFee |
The protocol fee (φ) is always charged on the RUN side, and doesn't go in
the pool. The pool fee (ρ) is charged on the amount that is calculated
(ΔX or ΔY) and is included in yHat.

We use the estimate of the amount in or out to calculate improved values of
ΔX and ΔY. These values tell us how much the trader will pay, the
changes in pool balances, and what the trader will receive. As before, ΔX
reflects a balance that will be growing, and ΔY one that will be
shrinking. If **sGive** is known, we subtract fees to get ΔX and calculate
ΔY. If **sGet** is known, we add fees to get ΔY and calculate
ΔX. ΔY and ΔX are the values that maintain the constant
product invariant. The amount paid and received by the trader and changes to the
pool are calculated relative to ΔX and ΔY so that the pool grows by
the poolFee and the protocolFee can be paid from the proceeds.

| | xIncr | yDecr | pay In | pay Out |
shrinking.

![AMM Diagram](./AMM-trade.jpeg)

When RUN is specified (in or out), we adjust by the protocol Fee to get the
change to the RUN balance (which gives X' or Y'); use that to calculate the
BLD component of the constant product, and then adjust δ by the pool fee.
When BLD is specified, the entire amount will affect the pool balance (so the
value in the constant product formula is the final BLD balance.) Both the pool
and protocol fees are charged in RUN so they respectively reduce sGet or
increase sGive.

This table shows how the pool's growth and trader's values differ from the
amounts that match the constant product formula. X is in RUN on lines 1 and 4,
and BLD on lines 2 and 3.

| | xIncr | yDecr | pay In (sGive) | pay Out (sGet) |
|---------|-----|-----|-----|-----|
| **RUN in** | ΔX | ΔY - PoolFee | ΔX + protocolFee | ΔY - PoolFee |
| **RUN out** | ΔX | ΔY - PoolFee | ΔX + protocolFee | ΔY - PoolFee |
| **BLD in** | ΔX + PoolFee | ΔY | ΔX + PoolFee + ProtocolFee | ΔY |
| **RUN out** | ΔX + PoolFee | ΔY | ΔX + protocolFee + PoolFee | ΔY |
| **BLD in** | ΔX | ΔY - PoolFee | ΔX + ProtocolFee | ΔY - PoolFee |
| **BLD out** | ΔX + PoolFee | ΔY | ΔX + PoolFee + ProtocolFee | ΔY |

In the two right columns the protocolFee is either added to the amount the
trader pays, or subtracted from the proceeds. The poolFee does the same on the
left side, and it is either added to the amount deposited in the pool (xIncr)
or deducted from the amout removed from the pool (yDecr).


* line 1: the trader provides **sGive** RUN; the pool will gain that minus
the protocol fee in RUN. The value in the RUN pool after (X') will be used to
calculate Y'. The pool fee is calculated from δY and the trader gets
&deltaY; - poolFee
* line 2: the trader asked for **sGet** RUN; the pool will be reduced by that
amount plus the protocol fee. Y' will be used to calculate X'. The trader
must provide &deltaX; plus poolFee
* Line 3: the trader pays **sGive** BLD; the pool will increase by that minus
both fees
* line 4: the trader specified **sGet** BLD; the pool will be reduced by that
plus the protocol fee.


## Example

For example, let's say the pool has 40,000,000 RUN and 3,000,000 BLD. Alice
Expand Down
25 changes: 16 additions & 9 deletions packages/vats/src/nameHub.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,26 @@ export const makeNameHubKit = () => {
return E(firstValue).lookup(...remaining);
},
entries() {
return mapIterable(keyToRecord.entries(), ([key, record]) => [
key,
record.promise || record.value,
]);
return [
...mapIterable(
keyToRecord.entries(),
([key, record]) => /** @type {[string, ERef<unknown>]} */ ([
key,
record.promise || record.value,
]),
),
];
},
values() {
return mapIterable(
keyToRecord.values(),
record => record.promise || record.value,
);
return [
...mapIterable(
keyToRecord.values(),
record => record.promise || record.value,
),
];
},
keys() {
return keyToRecord.keys();
return [...keyToRecord.keys()];
},
});

Expand Down
11 changes: 8 additions & 3 deletions packages/vats/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@

/**
* @typedef {Object} NameHub
*
* NOTE: We need to return arrays, not iterables, because even if marshal could
* allow passing a remote iterable, there would be an inordinate number of round
* trips for the contents of even the simplest nameHub.
*
* @property {(...path: Array<string>) => Promise<any>} lookup Look up a
* path of keys starting from the current NameHub. Wait on any reserved
* promises.
* @property {() => Iterable<[string, unknown]>} entries get all the entries
* @property {() => [string, unknown][]} entries get all the entries
* available in the current NameHub
* @property {() => Iterable<string>} keys get all names available in the
* @property {() => string[]} keys get all names available in the
* current NameHub
* @property {() => Iterable<unknown>} values get all values available in the
* @property {() => unknown[]} values get all values available in the
* current NameHub
*/

Expand Down
72 changes: 36 additions & 36 deletions packages/vats/test/test-name-hub.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ test('makeNameHubKit - lookup paths', async t => {
const { nameAdmin: na2, nameHub: nh2 } = makeNameHubKit();
const { nameAdmin: na3, nameHub: nh3 } = makeNameHubKit();

t.deepEqual([...nh1.keys()], []);
t.deepEqual([...nh1.values()], []);
t.deepEqual([...nh1.entries()], []);
t.deepEqual([...nh2.keys()], []);
t.deepEqual([...nh2.values()], []);
t.deepEqual([...nh2.entries()], []);
t.deepEqual([...nh3.keys()], []);
t.deepEqual([...nh3.values()], []);
t.deepEqual([...nh3.entries()], []);
t.deepEqual(nh1.keys(), []);
t.deepEqual(nh1.values(), []);
t.deepEqual(nh1.entries(), []);
t.deepEqual(nh2.keys(), []);
t.deepEqual(nh2.values(), []);
t.deepEqual(nh2.entries(), []);
t.deepEqual(nh3.keys(), []);
t.deepEqual(nh3.values(), []);
t.deepEqual(nh3.entries(), []);

na1.update('path1', nh2);
t.is(await nh1.lookup('path1'), nh2);
Expand All @@ -24,15 +24,15 @@ test('makeNameHubKit - lookup paths', async t => {
na3.update('path3', 'finish');
t.is(await nh3.lookup('path3'), 'finish');

t.deepEqual([...nh1.keys()], ['path1']);
t.deepEqual([...nh1.values()], [nh2]);
t.deepEqual([...nh1.entries()], [['path1', nh2]]);
t.deepEqual([...nh2.keys()], ['path2']);
t.deepEqual([...nh2.values()], [nh3]);
t.deepEqual([...nh2.entries()], [['path2', nh3]]);
t.deepEqual([...nh3.keys()], ['path3']);
t.deepEqual([...nh3.values()], ['finish']);
t.deepEqual([...nh3.entries()], [['path3', 'finish']]);
t.deepEqual(nh1.keys(), ['path1']);
t.deepEqual(nh1.values(), [nh2]);
t.deepEqual(nh1.entries(), [['path1', nh2]]);
t.deepEqual(nh2.keys(), ['path2']);
t.deepEqual(nh2.values(), [nh3]);
t.deepEqual(nh2.entries(), [['path2', nh3]]);
t.deepEqual(nh3.keys(), ['path3']);
t.deepEqual(nh3.values(), ['finish']);
t.deepEqual(nh3.entries(), [['path3', 'finish']]);

t.is(await nh1.lookup(), nh1);
t.is(await nh1.lookup('path1'), nh2);
Expand All @@ -50,9 +50,9 @@ test('makeNameHubKit - reserve and update', async t => {
message: /"nameKey" not found: .*/,
});

t.deepEqual([...nameHub.keys()], []);
t.deepEqual([...nameHub.values()], []);
t.deepEqual([...nameHub.entries()], []);
t.deepEqual(nameHub.keys(), []);
t.deepEqual(nameHub.values(), []);
t.deepEqual(nameHub.entries(), []);

// Try reserving and looking up.
nameAdmin.reserve('hello');
Expand All @@ -61,16 +61,16 @@ test('makeNameHubKit - reserve and update', async t => {
const lookupHelloP = nameHub
.lookup('hello')
.finally(() => (lookedUpHello = true));
t.deepEqual([...nameHub.keys()], ['hello']);
const helloP = [...nameHub.values()][0];
t.deepEqual(nameHub.keys(), ['hello']);
const helloP = nameHub.values()[0];
t.assert(helloP instanceof Promise);
t.deepEqual([...nameHub.entries()], [['hello', helloP]]);
t.deepEqual(nameHub.entries(), [['hello', helloP]]);

t.falsy(lookedUpHello);
nameAdmin.update('hello', 'foo');
t.deepEqual([...nameHub.keys()], ['hello']);
t.deepEqual([...nameHub.values()], ['foo']);
t.deepEqual([...nameHub.entries()], [['hello', 'foo']]);
t.deepEqual(nameHub.keys(), ['hello']);
t.deepEqual(nameHub.values(), ['foo']);
t.deepEqual(nameHub.entries(), [['hello', 'foo']]);
t.is(await lookupHelloP, 'foo');
t.truthy(lookedUpHello);

Expand All @@ -85,9 +85,9 @@ test('makeNameHubKit - reserve and delete', async t => {
message: /"nameKey" not found: .*/,
});

t.deepEqual([...nameHub.keys()], []);
t.deepEqual([...nameHub.values()], []);
t.deepEqual([...nameHub.entries()], []);
t.deepEqual(nameHub.keys(), []);
t.deepEqual(nameHub.values(), []);
t.deepEqual(nameHub.entries(), []);

nameAdmin.reserve('goodbye');
let lookedUpGoodbye = false;
Expand All @@ -96,15 +96,15 @@ test('makeNameHubKit - reserve and delete', async t => {
.finally(() => (lookedUpGoodbye = true));

t.falsy(lookedUpGoodbye);
t.deepEqual([...nameHub.keys()], ['goodbye']);
const goodbyeP = [...nameHub.values()][0];
t.deepEqual(nameHub.keys(), ['goodbye']);
const goodbyeP = nameHub.values()[0];
t.assert(goodbyeP instanceof Promise);
t.deepEqual([...nameHub.entries()], [['goodbye', goodbyeP]]);
t.deepEqual(nameHub.entries(), [['goodbye', goodbyeP]]);

nameAdmin.delete('goodbye');
t.deepEqual([...nameHub.keys()], []);
t.deepEqual([...nameHub.values()], []);
t.deepEqual([...nameHub.entries()], []);
t.deepEqual(nameHub.keys(), []);
t.deepEqual(nameHub.values(), []);
t.deepEqual(nameHub.entries(), []);
await t.throwsAsync(lookupGoodbyeP, {
message: /"nameKey" not found: .*/,
});
Expand Down
12 changes: 11 additions & 1 deletion packages/zoe/src/contractFacet/zcfZygote.js
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,17 @@ export const makeZCFZygote = (

let contractCode;

/** @type {ZCFZygote} */
/**
* A zygote is a pre-image of a vat that can quickly be instantiated because
* the code has already been evaluated. SwingSet doesn't support zygotes yet.
* Once it does the code will be evaluated once when creating the zcfZygote,
* then the start() function will be called each time an instance is started.
*
* Currently, Zoe's buildRootObject calls makeZCFZygote, evaluateContract, and
* startContract every time a contract instance is created.
*
* @type {ZCFZygote}
* */
const zcfZygote = {
evaluateContract: bundle => {
contractCode = evalContractBundle(bundle);
Expand Down
7 changes: 5 additions & 2 deletions packages/zoe/src/zoeService/installationStorage.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@ export const makeInstallationStorage = () => {
const installations = new WeakSet();

/**
* Create an installation by permanently storing the bundle. It will be
* evaluated each time it is used to make a new instance of a contract.
* Create an installation by permanently storing the bundle. The code is
* currently evaluated each time it is used to make a new instance of a
* contract. When SwingSet supports zygotes, the code will be evaluated once
* when creating a zcfZygote, then the start() function will be called each
* time an instance is started.
*/
/** @type {Install} */
const install = async bundle => {
Expand Down

0 comments on commit f7dd333

Please sign in to comment.