Skip to content

Commit

Permalink
chore(acceptance-price-feeds): make sure old vats are quiescent and v…
Browse files Browse the repository at this point in the history
…aults receive quotes

Refs: #9928
Refs: Agoric/BytePitchPartnerEng#25

fix: lint fixes

Refs: Agoric/BytePitchPartnerEng#25
  • Loading branch information
anilhelvaci committed Oct 24, 2024
1 parent 9d1ad9e commit d3393aa
Show file tree
Hide file tree
Showing 5 changed files with 296 additions and 42 deletions.
3 changes: 2 additions & 1 deletion a3p-integration/proposals/z:acceptance/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"@endo/init": "^1.1.4",
"ava": "^6.1.2",
"execa": "^9.3.1",
"tsx": "^4.17.0"
"tsx": "^4.17.0",
"better-sqlite3": "11.5.0"
},
"ava": {
"concurrency": 1,
Expand Down
101 changes: 73 additions & 28 deletions a3p-integration/proposals/z:acceptance/priceFeed.test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import test from 'ava';
import '@endo/init';
import {
getTranscriptItemsForVat,
getVatsWithSameName,
swingStore,
} from './test-lib/vat-helpers.js';
import {
addPreexistingOracles,
generateOracleMap,
getPriceQuote,
GOV1ADDR,
Expand All @@ -15,8 +10,38 @@ import {
registerOraclesForBrand,
waitForBlock,
} from '@agoric/synthetic-chain';
import { bankSend, pollRoundIdAndPushPrice } from './test-lib/priceFeed-lib.js';
import { snapshotVat } from './test-lib/vat-helpers.js';
import {
bankSend,
ensureGCDeliveryOnly,
getQuoteFromVault,
pollRoundIdAndPushPrice,
scale6,
} from './test-lib/priceFeed-lib.js';

const config = {
vatNames: [
'-scaledPriceAuthority-stATOM',
'-scaledPriceAuthority-ATOM',
'-stATOM-USD_price_feed',
'-ATOM-USD_price_feed',
],
snapshots: { before: {}, after: {} }, // Will be filled in the runtime
priceFeeds: {
ATOM: {
price: 29,
managerIndex: 0,
name: 'ATOM',
},
stATOM: {
price: 25,
managerIndex: 1,
name: 'stATOM',
},
},
};

// Remove this one when #10296 goes in
const init = async oraclesByBrand => {
await registerOraclesForBrand('ATOM', oraclesByBrand);
await waitForBlock(3);
Expand All @@ -26,13 +51,13 @@ const init = async oraclesByBrand => {
await pushPrices(1, 'ATOM', oraclesByBrand, 1);
await waitForBlock(3);
await pushPrices(1, 'stATOM', oraclesByBrand, 1);
}
};

/**
* @typedef {Map<string, Array<{ address: string; offerId: string }>>} OraclesByBrand
*/

test.before.skip(async t => {
test.before(async t => {
// Fund each oracle members with 10IST incase we hit batch limit here https://github.com/Agoric/agoric-sdk/issues/6525
await bankSend(GOV2ADDR, '10000000uist', GOV1ADDR);
await bankSend(GOV3ADDR, '10000000uist', GOV1ADDR);
Expand All @@ -46,33 +71,53 @@ test.before.skip(async t => {
};
});

test.skip('push-price', async t => {
test.serial('snapshot state', t => {
config.vatNames.forEach(name => {
config.snapshots.before[name] = snapshotVat(name);
});
console.dir(config.snapshots, { depth: null });
t.pass();
});

test.serial('push-price', async t => {
// @ts-expect-error casting
const { oraclesByBrand } = t.context;
const {
priceFeeds: { ATOM, stATOM },
} = config;

await pollRoundIdAndPushPrice('ATOM', 25, oraclesByBrand);
await pollRoundIdAndPushPrice('stATOM', 21, oraclesByBrand);
await pollRoundIdAndPushPrice(ATOM.name, ATOM.price, oraclesByBrand);
await pollRoundIdAndPushPrice(stATOM.name, stATOM.price, oraclesByBrand);

const atomOut = await getPriceQuote('ATOM');
t.is(atomOut, '+25000000');
const stAtomOut = await getPriceQuote('stATOM');
t.is(stAtomOut, '+21000000');
const atomOut = await getPriceQuote(ATOM.name);
t.is(atomOut, `+${scale6(ATOM.price)}`);
const stAtomOut = await getPriceQuote(stATOM.name);
t.is(stAtomOut, `+${scale6(stATOM.price)}`);
t.pass();
});

test.serial('dum', async t => {
// const stATOM = await getVatsWithSameName('-stATOM-USD_price_feed');
// const scaledStATOM = await getVatsWithSameName(
// '-scaledPriceAuthority-stATOM',
// );
// t.log(scaledStATOM);
const { findVatsAll, lookupVat, findVatsExact } = swingStore;
test.serial('snapshot state after price pushed', t => {
config.vatNames.forEach(name => {
config.snapshots.after[name] = snapshotVat(name);
});
console.dir(config.snapshots, { depth: null });
t.pass();
});

// const items = findVatsAll('scaledPriceAuthority');
// items.forEach(id => console.log(lookupVat(id).options(), id));
test.serial('ensure only gc', t => {
ensureGCDeliveryOnly(config.snapshots);
t.pass();
});

const stATOMScaledPAs = findVatsExact('-scaledPriceAuthority-stATOM');
stATOMScaledPAs.forEach(id => console.log(getTranscriptItemsForVat(id, 1).map((element) =>( JSON.parse(element.item).d[0]))));
test.serial('make sure vaults got the prices', async t => {
const {
priceFeeds: { ATOM, stATOM },
} = config;
const [atomVaultQuote, stAtomVaultQuote] = await Promise.all([
getQuoteFromVault(ATOM.managerIndex),
getQuoteFromVault(stATOM.managerIndex),
]);

t.pass();
t.is(atomVaultQuote, scale6(ATOM.price).toString());
t.is(stAtomVaultQuote, scale6(stATOM.price).toString());
});
89 changes: 88 additions & 1 deletion a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,29 @@ import {
agoric as agoricAmbient,
pushPrices,
} from '@agoric/synthetic-chain';
import { Fail, q } from '@endo/errors';
import { getTranscriptItemsForVat } from './vat-helpers.js';

/**
* By the time we push prices to the new price feed vat, the old one might receive
* some deliveries related to GC events. These delivery types might be; 'dropExports',
* 'retireExports', 'retireImports', 'bringOutYourDead'.
*
* Even though we don't expect to receive all these types of deliveries at once;
* choosing MAX_DELIVERIES_ALLOWED = 5 seems reasonable.
*/
const MAX_DELIVERIES_ALLOWED = 5;

export const scale6 = x => BigInt(x * 1000000);

/**
* @typedef {Record<
* string,
* Record<string, number>
* >} SnapshotItem
*
* @typedef {Record<string, SnapshotItem>} Snapshots
*/

/**
* Import from synthetic-chain once it is updated
Expand Down Expand Up @@ -37,7 +60,7 @@ export const getRoundId = async (price, io = {}) => {
io;
const path = `:${prefix}priceFeed.${price}-USD_price_feed.latestRound`;
const round = await agoric.follow('-lF', path);
return parseInt(round.roundId);
return parseInt(round.roundId, 10);
};

/**
Expand All @@ -54,3 +77,67 @@ export const pollRoundIdAndPushPrice = async (
const roundId = await getRoundId(brandIn);
await pushPrices(price, brandIn, oraclesByBrand, roundId + 1);
};

/**
* @param {SnapshotItem} snapShotItem
*/
export const getQuiescentVats = snapShotItem => {
const quiescentVats = {};
[...Object.values(snapShotItem)].forEach(vats => {
const keyOne = Object.keys(vats)[0];
const keyTwo = Object.keys(vats)[1];

return parseInt(keyOne.substring(1), 10) > parseInt(keyTwo.substring(1), 10)
? (quiescentVats[keyTwo] = vats[keyTwo])
: (quiescentVats[keyOne] = vats[keyOne]);
});

return quiescentVats;
};

/**
*
* @param {Snapshots} snapshots
* @param {{ getTranscriptItems?: () => Array}} io
*/
export const ensureGCDeliveryOnly = (snapshots, io = {}) => {
const { getTranscriptItems = getTranscriptItemsForVat } = io;

const { after, before } = snapshots;
const quiescentVatsBefore = getQuiescentVats(before);
const quiescentVatsAfter = getQuiescentVats(after);

console.dir(quiescentVatsBefore, { depth: null });
console.dir(quiescentVatsAfter, { depth: null });

[...Object.entries(quiescentVatsBefore)].forEach(([vatId, position]) => {
const afterPosition = quiescentVatsAfter[vatId];
const messageDiff = afterPosition - position;
console.log(vatId, messageDiff);

if (messageDiff > MAX_DELIVERIES_ALLOWED)
Fail`${q(messageDiff)} deliveries is greater than maximum allowed: ${q(MAX_DELIVERIES_ALLOWED)}`;
else if (messageDiff === 0) return;

const transcripts = getTranscriptItems(vatId, messageDiff);
console.log('TRANSCRIPTS', transcripts);

transcripts.forEach(({ item }) => {
const deliveryType = JSON.parse(item).d[0];
console.log('DELIVERY TYPE', deliveryType);
if (deliveryType === 'notify' || deliveryType === 'message')
Fail`DeliveryType ${q(deliveryType)} is not GC delivery`;
});
});
};

/**
* @param {number} managerIndex
*/
export const getQuoteFromVault = async managerIndex => {
const res = await agoricAmbient.follow(
'-lF',
`:published.vaultFactory.managers.manager${managerIndex}.quotes`,
);
return res.quoteAmount.value[0].amountOut.value;
};
103 changes: 103 additions & 0 deletions a3p-integration/proposals/z:acceptance/test-lib/priceFeed-lib.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import test from 'ava';
import '@endo/init';
import { ensureGCDeliveryOnly } from './priceFeed-lib.js';

const testConfig = {
before: {
'-scaledPriceAuthority-stATOM': { v58: 13, v74: 119 },
'-scaledPriceAuthority-ATOM': { v46: 77, v73: 178 },
'-stATOM-USD_price_feed': { v57: 40, v72: 192 },
'-ATOM-USD_price_feed': { v29: 100, v70: 247 },
},
after: {
'-scaledPriceAuthority-stATOM': { v58: 15, v74: 119 },
'-scaledPriceAuthority-ATOM': { v46: 79, v73: 178 },
'-stATOM-USD_price_feed': { v57: 42, v72: 192 },
'-ATOM-USD_price_feed': { v29: 102, v70: 247 },
},
};

const makeFakeGetTranscriptItemsForVat = (
deliveryType,
maximumAllowedDeliveries,
) => {
const fakeGetTranscriptItemsForVat = (_, number) => {
const fakeTranscriptItems = [];
for (let i = 0; i < number; i += 1) {
const item = { d: [deliveryType] };
fakeTranscriptItems.push({ item: JSON.stringify(item) });
}
return fakeTranscriptItems;
};

const tooManyTranscriptItemsForVat = () => {
const fakeTranscriptItems = [];
for (let i = 0; i <= maximumAllowedDeliveries; i += 1) {
const item = { d: [deliveryType] };
fakeTranscriptItems.push({ item: JSON.stringify(item) });
}
return fakeTranscriptItems;
};

return { fakeGetTranscriptItemsForVat, tooManyTranscriptItemsForVat };
};

test('should not throw', t => {
const { fakeGetTranscriptItemsForVat } =
makeFakeGetTranscriptItemsForVat('dropExports');

t.notThrows(() =>
ensureGCDeliveryOnly(testConfig, {
getTranscriptItems: fakeGetTranscriptItemsForVat,
}),
);
});

test('should throw for "notify"', t => {
const { fakeGetTranscriptItemsForVat } =
makeFakeGetTranscriptItemsForVat('notify');

t.throws(
() =>
ensureGCDeliveryOnly(testConfig, {
getTranscriptItems: fakeGetTranscriptItemsForVat,
}),
{ message: 'DeliveryType "notify" is not GC delivery' },
);
});

test('should throw for "message"', t => {
const { fakeGetTranscriptItemsForVat } =
makeFakeGetTranscriptItemsForVat('message');

t.throws(
() =>
ensureGCDeliveryOnly(testConfig, {
getTranscriptItems: fakeGetTranscriptItemsForVat,
}),
{ message: 'DeliveryType "message" is not GC delivery' },
);
});

test('should throw too many deliveries', t => {
const { fakeGetTranscriptItemsForVat } = makeFakeGetTranscriptItemsForVat(
'dropExports',
5,
);

const config = {
...testConfig,
after: {
...testConfig.after,
'-scaledPriceAuthority-stATOM': { v58: 20, v74: 119 },
},
};

t.throws(
() =>
ensureGCDeliveryOnly(config, {
getTranscriptItems: fakeGetTranscriptItemsForVat,
}),
{ message: '7 deliveries is greater than maximum allowed: 5' },
);
});
Loading

0 comments on commit d3393aa

Please sign in to comment.