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

6771 pricefeed errors #6776

Merged
merged 6 commits into from
Jan 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions packages/inter-protocol/src/price/priceAggregatorChainlink.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,10 +219,14 @@ export const start = async (zcf, privateArgs, baggage) => {
const invitationMakers = Far('invitation makers', {
/** @param {import('./roundsManager.js').PriceRound} result */
PushPrice(result) {
return zcf.makeInvitation(cSeat => {
cSeat.exit();
admin.pushPrice(result);
}, 'PushPrice');
return zcf.makeInvitation(
/** @param {ZCFSeat} cSeat */
async cSeat => {
cSeat.exit();
await admin.pushPrice(result);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when this throws, getOfferResult() rejects

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that seems right. (Sorry if I misled you previously.)

getOfferResult() is supposed to give you the return value of the offerHandler, so if it throws, getOfferResult() has nothing else to return. The exitSubscriber should still report that the seat has exited, and numWantsSatisfied should be 1.

},
'PushPrice',
);
},
});
seat.exit();
Expand Down
181 changes: 148 additions & 33 deletions packages/inter-protocol/test/smartWallet/test-oracle-integration.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { INVITATION_MAKERS_DESC } from '../../src/price/priceAggregatorChainlink
import { ensureOracleBrands } from '../../src/proposals/price-feed-proposal.js';
import { headValue } from '../supports.js';
import { makeDefaultTestContext } from './contexts.js';
import { zip } from '../../src/collect.js';

/**
* @type {import('ava').TestFn<Awaited<ReturnType<makeDefaultTestContext>>
Expand All @@ -24,8 +25,6 @@ import { makeDefaultTestContext } from './contexts.js';
*/
const test = anyTest;

const operatorAddress = 'oracleTestAddress';

const makeTestSpace = async log => {
const psmParams = {
anchorAssets: [{ denom: 'ibc/usdc1234', keyword: 'AUSD' }],
Expand Down Expand Up @@ -78,23 +77,83 @@ test.before(async t => {
t.context = await makeDefaultTestContext(t, makeTestSpace);
});

test('admin price', async t => {
const { agoricNames, zoe } = t.context.consume;
const setupFeedWithWallets = async (t, oracleAddresses) => {
const { agoricNames } = t.context.consume;

const wallet = await t.context.simpleProvideWallet(operatorAddress);
const computedState = coalesceUpdates(E(wallet).getUpdatesSubscriber());
const currentSub = E(wallet).getCurrentSubscriber();
const wallets = await Promise.all(
oracleAddresses.map(addr => t.context.simpleProvideWallet(addr)),
);

await t.context.simpleCreatePriceFeed([operatorAddress], 'ATOM', 'USD');
const oracleWallets = Object.fromEntries(zip(oracleAddresses, wallets));

const offersFacet = wallet.getOffersFacet();
await t.context.simpleCreatePriceFeed(oracleAddresses, 'ATOM', 'USD');

/** @type {import('@agoric/zoe/src/zoeService/utils.js').Instance<import('@agoric/inter-protocol/src/price/priceAggregatorChainlink.js').start>} */
const priceAggregator = await E(agoricNames).lookup(
'instance',
'ATOM-USD price feed',
);
const paPublicFacet = await E(zoe).getPublicFacet(priceAggregator);

return { oracleWallets, priceAggregator };
};

let acceptInvitationCounter = 0;
const acceptInvitation = async (wallet, priceAggregator) => {
acceptInvitationCounter += 1;
const id = `acceptInvitation${acceptInvitationCounter}`;
/** @type {import('@agoric/smart-wallet/src/invitations.js').PurseInvitationSpec} */
const getInvMakersSpec = {
source: 'purse',
instance: priceAggregator,
description: INVITATION_MAKERS_DESC,
};

/** @type {import('@agoric/smart-wallet/src/offers').OfferSpec} */
const invMakersOffer = {
id,
invitationSpec: getInvMakersSpec,
proposal: {},
};
await wallet.getOffersFacet().executeOffer(invMakersOffer);
// wait for it to settle
await eventLoopIteration();
return id;
};

let pushPriceCounter = 0;
const pushPrice = async (wallet, adminOfferId, priceRound) => {
/** @type {import('@agoric/smart-wallet/src/invitations.js').ContinuingInvitationSpec} */
const proposeInvitationSpec = {
source: 'continuing',
previousOffer: adminOfferId,
invitationMakerName: 'PushPrice',
invitationArgs: harden([priceRound]),
};

pushPriceCounter += 1;
const id = `pushPrice${pushPriceCounter}`;
/** @type {import('@agoric/smart-wallet/src/offers').OfferSpec} */
const proposalOfferSpec = {
id,
invitationSpec: proposeInvitationSpec,
proposal: {},
};

await wallet.getOffersFacet().executeOffer(proposalOfferSpec);
await eventLoopIteration();
return id;
};

// The tests are serial because they mutate shared state

test.serial('invitations', async t => {
const operatorAddress = 'invitation test';
const wallet = await t.context.simpleProvideWallet(operatorAddress);
const computedState = coalesceUpdates(E(wallet).getUpdatesSubscriber());

// this returns wallets, but we need the updates subscriber to start before the price feed starts
// so we provision the wallet earlier above
const { priceAggregator } = await setupFeedWithWallets(t, [operatorAddress]);

/**
* get invitation details the way a user would
Expand Down Expand Up @@ -127,7 +186,7 @@ test('admin price', async t => {
'priceAggregator',
);

// The purse has the invitation to get the makers ///////////
// The purse has the invitation to get the makers
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the reason you removed that apply to line 227 as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just cleanliness. Not worth another push.


/** @type {import('@agoric/smart-wallet/src/invitations.js').PurseInvitationSpec} */
const getInvMakersSpec = {
Expand All @@ -136,45 +195,41 @@ test('admin price', async t => {
description: INVITATION_MAKERS_DESC,
};

const id = '33';
/** @type {import('@agoric/smart-wallet/src/offers').OfferSpec} */
const invMakersOffer = {
id: 44,
id,
invitationSpec: getInvMakersSpec,
proposal: {},
};
await wallet.getOffersFacet().executeOffer(invMakersOffer);

await offersFacet.executeOffer(invMakersOffer);

const currentSub = E(wallet).getCurrentSubscriber();
/** @type {import('@agoric/smart-wallet/src/smartWallet.js').CurrentWalletRecord} */
const currentState = await headValue(currentSub);
t.deepEqual(Object.keys(currentState.offerToUsedInvitation), ['44']);
t.deepEqual(Object.keys(currentState.offerToUsedInvitation), [id]);
t.is(
currentState.offerToUsedInvitation[44].value[0].description,
currentState.offerToUsedInvitation[id].value[0].description,
INVITATION_MAKERS_DESC,
);
});

test.serial('admin price', async t => {
const operatorAddress = 'adminPriceAddress';
const { zoe } = t.context.consume;

const { oracleWallets, priceAggregator } = await setupFeedWithWallets(t, [
operatorAddress,
]);
const wallet = oracleWallets[operatorAddress];
const adminOfferId = await acceptInvitation(wallet, priceAggregator);

// Push a new price result /////////////////////////

/** @type {import('@agoric/inter-protocol/src/price/roundsManager.js').PriceRound} */
const result = { roundId: 1, unitPrice: 123n };

/** @type {import('@agoric/smart-wallet/src/invitations.js').ContinuingInvitationSpec} */
const proposeInvitationSpec = {
source: 'continuing',
previousOffer: 44,
invitationMakerName: 'PushPrice',
invitationArgs: harden([result]),
};

/** @type {import('@agoric/smart-wallet/src/offers').OfferSpec} */
const proposalOfferSpec = {
id: 45,
invitationSpec: proposeInvitationSpec,
proposal: {},
};

await offersFacet.executeOffer(proposalOfferSpec);
await eventLoopIteration();
await pushPrice(wallet, adminOfferId, result);

// Verify price result

Expand All @@ -184,10 +239,70 @@ test('admin price', async t => {
// trigger an aggregation (POLL_INTERVAL=1n in context)
E(manualTimer).tickN(1);

const paPublicFacet = await E(zoe).getPublicFacet(priceAggregator);

const latestRoundSubscriber = await E(paPublicFacet).getRoundStartNotifier();

t.deepEqual((await latestRoundSubscriber.subscribeAfter()).head.value, {
roundId: 1n,
startedAt: 0n,
});
});

test.serial('errors', async t => {
const operatorAddress = 'badInputsAddress';

const { oracleWallets, priceAggregator } = await setupFeedWithWallets(t, [
operatorAddress,
]);
const wallet = oracleWallets[operatorAddress];
const adminOfferId = await acceptInvitation(wallet, priceAggregator);

const computedState = coalesceUpdates(E(wallet).getUpdatesSubscriber());

const walletPushPrice = async priceRound => {
const offerId = await pushPrice(wallet, adminOfferId, priceRound);
return computedState.offerStatuses.get(offerId);
};
await eventLoopIteration();

// Invalid priceRound argument
t.like(
await walletPushPrice({
roundId: 1,
unitPrice: 1,
}),
{
error:
'Error: In "pushPrice" method of (OracleAdmin): arg 0: unitPrice: number 1 - Must be a bigint',
// trivially satisfied because the Want is empty
numWantsSatisfied: 1,
},
);
await eventLoopIteration();

// Success, round starts
t.like(
await walletPushPrice({
roundId: 1,
unitPrice: 1n,
}),
{
error: undefined,
numWantsSatisfied: 1,
},
);
await eventLoopIteration();

// Invalid attempt to push again to the same round
t.like(
await walletPushPrice({
roundId: 1,
unitPrice: 1n,
}),
{
error: 'Error: cannot report on previous rounds',
numWantsSatisfied: 1,
},
);
});