Skip to content

Commit

Permalink
feat(price): pause feed by governance
Browse files Browse the repository at this point in the history
  • Loading branch information
turadg committed Feb 13, 2023
1 parent 70e196e commit 32b0282
Show file tree
Hide file tree
Showing 3 changed files with 123 additions and 37 deletions.
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 @@ -377,7 +378,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 @@ -438,7 +439,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 @@ -454,27 +460,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 @@ -494,15 +492,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 @@ -511,21 +576,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

0 comments on commit 32b0282

Please sign in to comment.