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

6707 pause feed #6982

Closed
wants to merge 1 commit into from
Closed
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
15 changes: 15 additions & 0 deletions packages/inter-protocol/src/price/fluxAggregator.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* Adaptation of Chainlink algorithm to the Agoric platform.
* Modeled on https://github.com/smartcontractkit/chainlink/blob/master/contracts/src/v0.6/FluxAggregator.sol (version?)
*/
import { Fail } from '@agoric/assert';
import { AmountMath } from '@agoric/ertp';
import { assertAllDefined, makeTracer } from '@agoric/internal';
import {
Expand Down Expand Up @@ -118,6 +119,8 @@ export const provideFluxAggregator = (
'Price Aggregator publish kit',
);

let pushIsPaused = false;

// For publishing priceAuthority values to off-chain storage
/** @type {PublishKit<PriceDescription>} */
const { publisher: pricePublisher, subscriber: quoteSubscriber } =
Expand Down Expand Up @@ -215,6 +218,9 @@ export const provideFluxAggregator = (
const invitationMakers = Far('invitation makers', {
/** @param {import('./roundsManager.js').PriceRound} result */
PushPrice(result) {
if (pushIsPaused) {
Fail`cannot PushPrice until unpaused`;
}
return zcf.makeInvitation(
/** @param {ZCFSeat} cSeat */
async cSeat => {
Expand Down Expand Up @@ -302,6 +308,15 @@ export const provideFluxAggregator = (
};
}
},

/**
* Pause (or unpause) whether PushPrice offers can proceed
*
* @param {boolean} paused
*/
setPaused(paused) {
pushIsPaused = paused;
},
});

const publicFacet = Far('publicFacet', {
Expand Down
7 changes: 7 additions & 0 deletions packages/inter-protocol/src/price/fluxAggregatorContract.js
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,13 @@ export const start = async (zcf, privateArgs, baggage) => {
removeOracles: oracleIds => {
return Promise.allSettled(oracleIds.map(removeOracle));
},

pause: () => {
return E(fa.creatorFacet).setPaused(true);
},
unpause: () => {
return E(fa.creatorFacet).setPaused(false);
},
};

const governorFacet = makeGovernorFacet(fa.creatorFacet, governedApis);
Expand Down
138 changes: 101 additions & 37 deletions packages/inter-protocol/test/smartWallet/test-oracle-integration.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-lone-blocks */
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';

import { NonNullish } from '@agoric/assert';
Expand Down Expand Up @@ -369,7 +370,7 @@ test.serial('govern oracles list', async t => {
'two invitations deposited',
);

// Accept the EC invitation makers ///////////
t.log('accept the EC invitation makers');
{
/** @type {import('@agoric/smart-wallet/src/invitations').PurseInvitationSpec} */
const getInvMakersSpec = {
Expand Down Expand Up @@ -430,7 +431,12 @@ test.serial('govern oracles list', async t => {
const feed = await E(agoricNames).lookup('instance', 'ATOM-USD price feed');
t.assert(feed);

// Call for a vote to addOracles ////////////////////////////////
const committeePublic = E(zoe).getPublicFacet(economicCommittee);
/** @type {ERef<ManualTimer>} */
// @ts-expect-error cast mock
const timer = t.context.consume.chainTimerService;

t.log('EC addOracles');
{
/** @type {import('@agoric/smart-wallet/src/invitations').ContinuingInvitationSpec} */
const proposeInvitationSpec = {
Expand All @@ -446,27 +452,19 @@ test.serial('govern oracles list', async t => {
proposal: {},
});
await eventLoopIteration();
await offersFacet.executeOffer({
id: '',
invitationSpec: await voteForOpenQuestion(
committeePublic,
'acceptVoterOID',
),
proposal: {},
});
// pass time to exceed the voting deadline
await E(timer).tickN(1);
}

const committeePublic = E(zoe).getPublicFacet(economicCommittee);
/** @type {ERef<ManualTimer>} */
// @ts-expect-error cast mock
const timer = t.context.consume.chainTimerService;

// vote to addOracles /////////////////////////
await offersFacet.executeOffer({
id: '',
invitationSpec: await voteForOpenQuestion(
committeePublic,
'acceptVoterOID',
),
proposal: {},
});
// pass time to exceed the voting deadline
await E(timer).tickN(10);

// accept deposit /////////////////////////

t.log('accept oracle invitation');
const oracleWallet = await t.context.simpleProvideWallet(newOracle);
const oracleWalletComputedState = coalesceUpdates(
E(oracleWallet).getUpdatesSubscriber(),
Expand All @@ -486,15 +484,82 @@ test.serial('govern oracles list', async t => {
const oracleOfferId = await acceptInvitation(oracleWallet, feed);
t.is(oracleOfferId, 'acceptInvitation3');

// Call for a vote to removeOracles ////////////////////////////////
t.log('EC pause');
{
await offersFacet.executeOffer({
id: 'proposePause',
invitationSpec: {
source: 'continuing',
previousOffer: 'acceptEcInvitationOID',
invitationMakerName: 'VoteOnApiCall',
invitationArgs: harden([feed, 'pause', [], 3n]),
},
proposal: {},
});
await eventLoopIteration();

await offersFacet.executeOffer({
id: 'pauseOID',
invitationSpec: await voteForOpenQuestion(
committeePublic,
'acceptVoterOID',
),
proposal: {},
});
// wait for vote to resolve
await E(timer).tickN(3);
await eventLoopIteration();
}

t.log('verify pause prevents PushPrice');
{
const pushPriceOfferId = await pushPrice(oracleWallet, oracleOfferId, {
roundId: 1,
unitPrice: 123n,
});

const offerStatus =
oracleWalletComputedState.offerStatuses.get(pushPriceOfferId);
t.like(offerStatus, {
id: pushPriceOfferId,
error: 'Error: cannot PushPrice until unpaused',
});
}

t.log('EC unpause');
{
await offersFacet.executeOffer({
id: 'proposePause',
invitationSpec: {
source: 'continuing',
previousOffer: 'acceptEcInvitationOID',
invitationMakerName: 'VoteOnApiCall',
invitationArgs: harden([feed, 'unpause', [], 6n]),
},
proposal: {},
});
await eventLoopIteration();

await offersFacet.executeOffer({
id: 'unpauseOID',
invitationSpec: await voteForOpenQuestion(
committeePublic,
'acceptVoterOID',
),
proposal: {},
});
// wait for vote to resolve
await E(timer).tickN(3);
await eventLoopIteration();
}
t.log('EC removeOracles');
{
/** @type {import('@agoric/smart-wallet/src/invitations').ContinuingInvitationSpec} */
const proposeInvitationSpec = {
source: 'continuing',
previousOffer: 'acceptEcInvitationOID',
invitationMakerName: 'VoteOnApiCall',
// XXX deadline 20n >> 2n before
invitationArgs: harden([feed, 'removeOracles', [[newOracle]], 20n]),
invitationArgs: harden([feed, 'removeOracles', [[newOracle]], 10n]),
};

await offersFacet.executeOffer({
Expand All @@ -503,21 +568,20 @@ test.serial('govern oracles list', async t => {
proposal: {},
});
await eventLoopIteration();
}

// vote to removeOracles /////////////////////////
await offersFacet.executeOffer({
id: 'removeOraclesOID',
invitationSpec: await voteForOpenQuestion(
committeePublic,
'acceptVoterOID',
),
proposal: {},
});
// wait for vote to resolve
await E(timer).tickN(20);
await offersFacet.executeOffer({
id: 'removeOraclesOID',
invitationSpec: await voteForOpenQuestion(
committeePublic,
'acceptVoterOID',
),
proposal: {},
});
// wait for vote to resolve
await E(timer).tickN(4);
}

// verify removed oracle can no longer PushPrice /////////////////////////
t.log('verify removed oracle can no longer PushPrice');
{
const pushPriceOfferId = await pushPrice(oracleWallet, oracleOfferId, {
roundId: 1,
Expand Down