Skip to content

Commit

Permalink
Merge branch 'master' into markm-durable-ertp
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Jul 8, 2022
2 parents 21d61bd + 4cca744 commit b4172e5
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 105 deletions.
98 changes: 40 additions & 58 deletions packages/run-protocol/src/vpool-xyk-amm/addPool.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import { E } from '@endo/eventual-send';
import { AmountMath, AssetKind } from '@agoric/ertp';
import { assertProposalShape } from '@agoric/zoe/src/contractSupport/index.js';
import { makePromiseKit } from '@endo/promise-kit';

import { definePoolKind } from './pool.js';

Expand All @@ -14,15 +13,13 @@ const DISPLAY_INFO = harden({ decimalPlaces: 6 });
/**
* @param {ZCF} zcf
* @param {(Brand) => boolean} isInSecondaries
* @param {WeakStore<Brand,ZCFMint>} brandToLiquidityMint
* @param {WeakStore<Brand,ERef<Issuer>>} brandToLiquidityIssuer
* @param {import('@agoric/store/src/stores/store-utils.js').AtomicProvider<Brand, ZCFMint>} brandToLiquidityMintProvider
* @param {() => (secondaryBrand: Brand) => Promise<void>} getAddIssuerToReserve
*/
export const makeAddIssuer = (
zcf,
isInSecondaries,
brandToLiquidityMint,
brandToLiquidityIssuer,
brandToLiquidityMintProvider,
getAddIssuerToReserve,
) => {
/**
Expand All @@ -44,66 +41,51 @@ export const makeAddIssuer = (
X`${keyword} asset not fungible (must use NAT math)`,
);

if (brandToLiquidityIssuer.has(secondaryBrand)) {
return brandToLiquidityIssuer.get(secondaryBrand);
}

assert(
!isInSecondaries(secondaryBrand),
X`issuer ${secondaryIssuer} already has a pool`,
);

if (brandToLiquidityMint.has(secondaryBrand)) {
const { issuer } = brandToLiquidityMint
.get(secondaryBrand)
.getIssuerRecord();
return issuer;
}
/** @type {(brand: Brand) => Promise<ZCFMint>} */
const makeLiquidityMint = brand => {
assert(
!isInSecondaries(brand),
X`issuer ${secondaryIssuer} already has a pool`,
);

/** @type {import('@endo/promise-kit').PromiseKit<Issuer<'nat'>>} */
const liquidityPromiseKit = makePromiseKit();
brandToLiquidityIssuer.init(secondaryBrand, liquidityPromiseKit.promise);

const liquidityKeyword = `${keyword}Liquidity`;
zcf.assertUniqueKeyword(liquidityKeyword);

return E.when(
Promise.all([
zcf.saveIssuer(secondaryIssuer, keyword),
zcf.makeZCFMint(liquidityKeyword, AssetKind.NAT, DISPLAY_INFO),
]),
)
.then(([issuer, mint]) => {
console.log(
X`Saved issuer ${secondaryIssuer} to keyword ${keyword} and got back ${issuer}`,
);
// this ensures that getSecondaryIssuer(thisIssuer) will return even
// before the pool is created
brandToLiquidityMint.init(secondaryBrand, mint);
const { issuer: liquidityIssuer } = mint.getIssuerRecord();
liquidityPromiseKit.resolve(liquidityIssuer);
// we only need entries in this table until brandToLiquidityIssuer knows
// the issuer and the promise is resolved.
brandToLiquidityIssuer.delete(secondaryBrand);

// defer lookup until necessary. more aligned with governed
// param we expect this to be eventually.
const addIssuerToReserve = getAddIssuerToReserve();

// tell the reserve about this brand, which it will validate by
// calling back to AMM for the issuer
return addIssuerToReserve(secondaryBrand).then(() => liquidityIssuer);
})
.catch(e => {
const liquidityKeyword = `${keyword}Liquidity`;
zcf.assertUniqueKeyword(liquidityKeyword);

return E.when(
Promise.all([
zcf.saveIssuer(secondaryIssuer, keyword),
zcf.makeZCFMint(liquidityKeyword, AssetKind.NAT, DISPLAY_INFO),
]),
([issuer, mint]) => {
console.log(
X`Saved issuer ${secondaryIssuer} to keyword ${keyword} and got back ${issuer}`,
);
return mint;
},
).catch(e => {
console.error(
X`Failure Saving issuer ${secondaryIssuer}. Not added to Reserve`,
);
liquidityPromiseKit.reject(e);
brandToLiquidityIssuer.delete(secondaryBrand);
brandToLiquidityMint.delete(secondaryBrand);
throw e;
});
};

/** @type {(brand: Brand) => Promise<void>} */
const finish = brand => {
// defer lookup until necessary. more aligned with governed
// param we expect this to be eventually.
const addIssuerToReserve = getAddIssuerToReserve();

// tell the reserve about this brand, which it will validate by
// calling back to AMM for the issuer
return addIssuerToReserve(brand);
};

return brandToLiquidityMintProvider
.provideAsync(secondaryBrand, makeLiquidityMint, finish)
.then(mint => mint.getIssuerRecord().issuer);
};

return addIssuer;
};

Expand Down
37 changes: 17 additions & 20 deletions packages/run-protocol/src/vpool-xyk-amm/multipoolMarketMaker.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
// @ts-check

import { makeStore, makeWeakStore } from '@agoric/store';
import { Far } from '@endo/marshal';
import '@agoric/zoe/exported.js';

import { AssetKind, makeIssuerKit } from '@agoric/ertp';
import { handleParamGovernance, ParamTypes } from '@agoric/governance';
import { makeStore, makeWeakStore } from '@agoric/store';
import { makeAtomicProvider } from '@agoric/store/src/stores/store-utils.js';
import {
assertIssuerKeywords,
offerTo,
} from '@agoric/zoe/src/contractSupport/index.js';
import { E } from '@endo/far';
import { makeAddIssuer, makeAddPoolInvitation } from './addPool.js';
import { publicPrices } from './pool.js';
import { Far } from '@endo/marshal';
import { makeMakeCollectFeesInvitation } from '../collectFees.js';
import { makeMetricsPublisherKit } from '../contractSupport.js';
import { makeTracer } from '../makeTracer.js';
import {
makeMakeAddLiquidityAtRateInvitation,
makeMakeAddLiquidityInvitation,
} from './addLiquidity.js';
import { makeMakeRemoveLiquidityInvitation } from './removeLiquidity.js';

import '@agoric/zoe/exported.js';
import { makeMakeCollectFeesInvitation } from '../collectFees.js';
import { makeMakeSwapInvitation } from './swap.js';
import { makeAddIssuer, makeAddPoolInvitation } from './addPool.js';
import { makeDoublePool } from './doublePool.js';
import {
MIN_INITIAL_POOL_LIQUIDITY_KEY,
POOL_FEE_KEY,
PROTOCOL_FEE_KEY,
MIN_INITIAL_POOL_LIQUIDITY_KEY,
} from './params.js';
import { makeTracer } from '../makeTracer.js';
import { makeMetricsPublisherKit } from '../contractSupport.js';
import { publicPrices } from './pool.js';
import { makeMakeRemoveLiquidityInvitation } from './removeLiquidity.js';
import { makeMakeSwapInvitation } from './swap.js';

const { quote: q, details: X } = assert;

Expand Down Expand Up @@ -166,15 +166,13 @@ const start = async (zcf, privateArgs) => {
const isSecondary = secondaryBrandToPool.has;

// The liquidityBrand has to exist to allow the addPool Offer to specify want
/** @type {WeakStore<Brand,ZCFMint<'nat'>>} */
/** @type {WeakMapStore<Brand,ZCFMint<'nat'>>} */
const secondaryBrandToLiquidityMint = makeWeakStore(
'secondaryBrandToLiquidityMint',
);
// To manage races in addIssuer, we keep a promise from the time of the
// first request until the Issuer is set up.
/** @type {WeakStore<Brand,ERef<Issuer<'nat'>>>} */
const secondaryBrandToLiquidityIssuerPromise =
makeWeakStore('secondaryBrand');
const secondaryBrandToLiquidityMintProvider = makeAtomicProvider(
secondaryBrandToLiquidityMint,
);

const quoteIssuerKit = makeIssuerKit('Quote', AssetKind.SET);

Expand Down Expand Up @@ -279,8 +277,7 @@ const start = async (zcf, privateArgs) => {
const addIssuer = makeAddIssuer(
zcf,
isSecondary,
secondaryBrandToLiquidityMint,
secondaryBrandToLiquidityIssuerPromise,
secondaryBrandToLiquidityMintProvider,
() => {
assert(reserveFacet, 'Must first resolveReserveFacet');
return E(reserveFacet).addIssuerFromAmm;
Expand Down
7 changes: 3 additions & 4 deletions packages/run-protocol/src/vpool-xyk-amm/pool.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
// @ts-check

import '@agoric/zoe/exported.js';

import { AmountMath, isNatValue } from '@agoric/ertp';
import { makeNotifierKit, makeStoredPublisherKit } from '@agoric/notifier';

import { defineKindMulti } from '@agoric/vat-data';
import {
calcLiqValueToMint,
calcSecondaryRequired,
calcValueToRemove,
} from '@agoric/zoe/src/contractSupport/index.js';

import '@agoric/zoe/exported.js';
import { defineKindMulti } from '@agoric/vat-data';
import { Far } from '@endo/marshal';
import { makePriceAuthority } from './priceAuthority.js';
import { singlePool } from './singlePool.js';
Expand Down
20 changes: 10 additions & 10 deletions packages/store/src/stores/store-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,15 +114,15 @@ harden(provide);
* the key.) This prevents that race condition by immediately storing a Promise
* for the maker in an ephemeral store.
*
* Upon termination, the ephemeral store of pending makes will be lost. It's
* possible for termination to happen after the make completes and before it
* reaches durable storage.
* When the `store` argument is durable storage, note that it's possible for
* termination to happen after the make completes and before it reaches durable
* storage.
*
* @template K
* @template V
* @param {MapStore<K, V>} durableStore
* @param {WeakMapStore<K, V>} store
*/
export const makeAtomicProvider = durableStore => {
export const makeAtomicProvider = store => {
/** @type {Map<K, Promise<V>>} */
const pending = new Map();

Expand All @@ -134,18 +134,18 @@ export const makeAtomicProvider = durableStore => {
* that key, and return it.
*
* @param {K} key
* @param {(key: K) => Promise<V>} makeValue
* @param {(key: K, value: V) => Promise<void>} [finishValue]
* @param {(key: K) => Promise<V>} makeValue make the value for the store if it hasn't been made yet or the last make failed
* @param {(key: K, value: V) => Promise<void>} [finishValue] runs exactly once after a new value is added to the store
* @returns {Promise<V>}
*/
const provideAsync = (key, makeValue, finishValue) => {
if (durableStore.has(key)) {
return Promise.resolve(durableStore.get(key));
if (store.has(key)) {
return Promise.resolve(store.get(key));
}
if (!pending.has(key)) {
const valP = makeValue(key)
.then(v => {
durableStore.init(key, v);
store.init(key, v);
return v;
})
.then(v => {
Expand Down
48 changes: 35 additions & 13 deletions packages/store/test/test-AtomicProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,41 +13,63 @@ import '../src/types.js';
test('race', async t => {
const store = makeScalarMapStore('foo');
const provider = makeAtomicProvider(store);

let i = 0;
const makeValue = k =>
const make = k =>
// in Node 15+ use timers/promise
new Promise(resolve => setTimeout(() => resolve(`${k} ${(i += 1)}`), 10));

t.is(await provider.provideAsync('a', makeValue), 'a 1');
t.is(await provider.provideAsync('a', makeValue), 'a 1');
let finishCalled = 0;
const finish = () => {
finishCalled += 1;
return Promise.resolve();
};

t.is(await provider.provideAsync('a', make, finish), 'a 1');
t.is(await provider.provideAsync('a', make, finish), 'a 1');
t.is(finishCalled, 1);

provider.provideAsync('b', makeValue);
provider.provideAsync('b', makeValue);
t.is(await provider.provideAsync('b', makeValue), 'b 2');
t.is(await provider.provideAsync('b', makeValue), 'b 2');
provider.provideAsync('b', make, finish);
provider.provideAsync('b', make, finish);
t.is(await provider.provideAsync('b', make, finish), 'b 2');
t.is(await provider.provideAsync('b', make, finish), 'b 2');
t.is(finishCalled, 2);
});

test('reject', async t => {
const store = makeScalarMapStore('foo');
const provider = makeAtomicProvider(store);

let i = 0;
const makeValue = k => Promise.reject(Error(`failure ${k} ${(i += 1)}`));
const makeFail = k => Promise.reject(Error(`failure ${k} ${(i += 1)}`));

let finishCalled = 0;
const finish = () => {
finishCalled += 1;
return Promise.resolve();
};

await t.throwsAsync(provider.provideAsync('a', makeValue), {
await t.throwsAsync(provider.provideAsync('a', makeFail, finish), {
message: 'failure a 1',
});
await t.throwsAsync(provider.provideAsync('a', makeValue), {
await t.throwsAsync(provider.provideAsync('a', makeFail, finish), {
// makeValue runs again (i += 1)
message: 'failure a 2',
});

await t.throwsAsync(provider.provideAsync('b', makeValue), {
await t.throwsAsync(provider.provideAsync('b', makeFail, finish), {
message: 'failure b 3',
});

await t.throwsAsync(provider.provideAsync('b', makeValue), {
await t.throwsAsync(provider.provideAsync('b', makeFail, finish), {
message: 'failure b 4',
});

t.is(finishCalled, 0);

// success after failure
const makeValue = () => Promise.resolve('success');
t.is(await provider.provideAsync('a', makeValue, finish), 'success');
t.is(finishCalled, 1);
});

test('far keys', async t => {
Expand Down

0 comments on commit b4172e5

Please sign in to comment.