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

make Chainlink the canonical priceAggregator #6629

Merged
merged 26 commits into from
Dec 21, 2022
Merged

Conversation

turadg
Copy link
Member

@turadg turadg commented Dec 5, 2022

closes: #6628
part of #6571

Description

Make priceAggregatorChainlink the price aggregator contract. Updates CLI command and smoketest script to use the new result struct.

Updates the contract itself for code quality and compatibility with the Smart Wallet (using continuing invitation pattern and keying oracles by wallet address).

Security Considerations

Increases security by implementing round constraints on oracle price pushes. No new privileges.

Documentation Considerations

This changes the priceAggregator to be Chainlink style, which is different than https://github.com/Agoric/dapp-oracle/ expects. OTOH it's already broken and serves mostly an illustration instead of a functioning dapp.

Testing Considerations

Tested manually with https://github.com/Agoric/agoric-sdk/blob/6ff21e34936bdaad17ecd3d0e40dc7493a75cac3/packages/agoric-cli/test/agops-oracle-smoketest.sh

@turadg turadg force-pushed the 6628-chainLink-working branch from 30f8d44 to c905450 Compare December 5, 2022 23:08
@turadg turadg force-pushed the 6628-chainLink-working branch from 4cfd391 to 6ff21e3 Compare December 20, 2022 00:07
@turadg turadg requested review from michaelfig and dckc December 20, 2022 00:14
@turadg turadg marked this pull request as ready for review December 20, 2022 00:14
@turadg turadg changed the title 6628 chain link working make Chainlink the canonical priceAggregator Dec 20, 2022
Copy link
Member

@dckc dckc left a comment

Choose a reason for hiding this comment

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

To what extent does closing #6628 mean claiming we're done with this contract?

Comparing the scope of this to the 5 tasks listed in #6571, it seems to claim to address:

  • make it support continuing invitation
  • FIXMEs (e.g. We throw away the updater but shouldn’t)
  • wire it into integration tests and fix whatever else comes up

but not these:

  • make durable
  • replace makeLegacyMap usage

I'm also curious whether...

... is in scope.

Is there more vstorage work to do after this? (#5854)

I suppose #5060 is likely to impact only the vaultFactory contract and not this one.

Comment on lines -40 to +52
bin/agops oracle pushPrice --price 1.01 --oracleAdminAcceptOfferId "$ORACLE_OFFER_ID" >|"$PROPOSAL_OFFER"
bin/agops oracle pushPriceRound --price 101 --roundId 1 --oracleAdminAcceptOfferId "$ORACLE_OFFER_ID" >|"$PROPOSAL_OFFER"
Copy link
Member

Choose a reason for hiding this comment

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

This makes me wonder whether the oracle operators aware of quoting prices in integers without decimals. I'm looking around.

Copy link
Member

Choose a reason for hiding this comment

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

The vstorage path segment is still ATOM-USD_price_feed. Is the number of decimals available at a nearby vstorage key?

Copy link
Member Author

Choose a reason for hiding this comment

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

Not presently. I expect the proposal config to suffice:

priceFeedOptions: {
IN_BRAND_NAME: inBrandName,
IN_BRAND_DECIMALS: '6',
OUT_BRAND_NAME: outBrandName,
OUT_BRAND_DECIMALS: '6',

packages/governance/src/types-ambient.js Outdated Show resolved Hide resolved
minSubmissionCount: 2,
minSubmissionValue: 1,
maxSubmissionCount: 5,
maxSubmissionValue: 99999,
Copy link
Member

Choose a reason for hiding this comment

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

What's this limitation of 100k - 1? I aim to look around a bit more...

Copy link
Member Author

Choose a reason for hiding this comment

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

Nothing purposeful. It's much bigger in

maxSubmissionValue: 2n ** 256n,

Any suggestions?

Copy link
Member

Choose a reason for hiding this comment

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

I don't understand the purpose of the limitation. What is it limiting? What bad thing might happen if there's no limit? Why is 100k a plausible number?

Copy link
Member Author

Choose a reason for hiding this comment

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

What is it limiting?

It's limiting the maximum value in a submission.

I don't understand the purpose of the limitation.

One is that it came in the port of https://github.com/smartcontractkit/chainlink/blob/b045416ebca769aa69bde2da23b5109abe07a8b5/contracts/src/v0.6/FluxAggregator.sol#L155-L156

What bad thing might happen if there's no limit?

If enough oracles were compromised they could push the median up to a ludicrous price. Or if there is a software bug affecting all of them it wouldn't even require malice. TMK it's a hedge against run-away values.

Why is 100k a plausible number?

I don't know that it is. This code isn't of the economics or risk, just the functionality. Isn't that sufficient?

Comment on lines -13 to +18
import { INVITATION_MAKERS_DESC } from '@agoric/zoe/src/contracts/priceAggregator.js';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { AmountMath } from '@agoric/ertp';
import { coalesceUpdates } from '@agoric/smart-wallet/src/utils.js';
import { INVITATION_MAKERS_DESC } from '@agoric/zoe/src/contracts/priceAggregatorChainlink.js';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { ensureOracleBrands } from '../../src/proposals/price-feed-proposal.js';
import { makeDefaultTestContext } from './contexts.js';
import { headValue } from '../supports.js';
import { makeDefaultTestContext } from './contexts.js';
Copy link
Member

Choose a reason for hiding this comment

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

Is there anything to this change besides line order? I can't tell. Is there some motivation for changing the order?

Copy link
Member Author

Choose a reason for hiding this comment

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

There was some import to remove and I used Organize imports in VS Code (from TypeScript LSP)

Copy link
Member

@dckc dckc left a comment

Choose a reason for hiding this comment

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

convention calls for capitalizing makePushPriceInvitation

and I don't believe oracleAddr is actually always a string. The tests seem to use an instance.

I haven't tried reproducing the integration testing results; that's next...

const aggregatorInstallation = await E(zoe).installBundleID(
'b1-aggregator',
);
const defaultConfig = {
Copy link
Member

Choose a reason for hiding this comment

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

Should defaultConfig be exposed more widely? Is it something operators should know about? Or is it just arbitrary test constants?

Copy link
Member Author

@turadg turadg Dec 21, 2022

Choose a reason for hiding this comment

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

arbitrary test constants. "default" merely for this module's tests. I think the "in this test module" is implied but let me know if you think it needs a comment or something

Copy link
Member

Choose a reason for hiding this comment

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

That's fine.

I was just asking about the possibility that the numbers are not arbitrary and in fact have some relevance to production operation.

Comment on lines +184 to 146
await E(pricePushAdminA).pushResult({ roundId: 1, unitPrice: 100n });
await E(pricePushAdminB).pushResult({ roundId: 1, unitPrice: 200n });
await E(pricePushAdminC).pushResult({ roundId: 1, unitPrice: 300n });
await oracleTimer.tick();

const round1Attempt1 = await E(aggregator.creatorFacet).getRoundData(1);
Copy link
Member

Choose a reason for hiding this comment

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

100, 200, and 300 seem pretty far apart; does the aggregator consume them no matter how far apart they are? Or is there a point where it says "sources don't agree; nothing available at this time"?

Copy link
Member Author

Choose a reason for hiding this comment

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

Right, it lets the prices push as long as they're within the minSubmissionValue and maxSubmissionValue. Extremity of gap doesn't matter because the feed only exposes the median.

Copy link
Member

Choose a reason for hiding this comment

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

I'm missing something about how scale works in this contract. DAI has 18 decimals. the .value of an ERTP amount of 1 DAI is bigger than 100K. How could this possibly work?

* @param {Timestamp} blockTimestamp
*/
const oracleRoundStateSuggestRound = (_oracle, blockTimestamp) => {
const oracle = oracleStatuses.get(_oracle);
const oracleRoundStateSuggestRound = (oracleAddr, blockTimestamp) => {
Copy link
Member

Choose a reason for hiding this comment

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

I wonder what an oracleAddr is... I think it's: an address to which an invitation to this contract was sent. Why would this contract know the addresses? hm.

Copy link
Member Author

Choose a reason for hiding this comment

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

I wonder what an oracleAddr is... I think it's: an address to which an invitation to this contract was sent.

Correct. I'll make clearer by documentation.

Why would this contract know the addresses?

It needs to know the oracles and it's keyed on their public address. What's the concern?

Copy link
Member

Choose a reason for hiding this comment

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

The fact that the .instance test works suggests that the keys don't need to be addresses of the validators; the validators could be identified as 'A', 'B', and 'C' and the contract would work fine, yes? The convention of using addresses seems to be external to the contract.

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh yes, good point. I'll change it to be oracleKey.

Comment on lines 702 to 705
/**
* An "oracle invitation" is an invitation to be able to submit data to
* include in the priceAggregator's results.
*
* The offer result from this invitation is a OracleAdmin, which can be used
* directly to manage the price submissions as well as to terminate the
* relationship.
*
* @param {string} oracleAddr
*/
makeOracleInvitation: async oracleAddr => {
Copy link
Member

Choose a reason for hiding this comment

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

how refreshing! docs!

* reported data.
*
* @param {ZCFSeat} seat
* @returns {Promise<{admin: OracleAdmin<PriceRound>, invitationMakers: {makePushPriceInvitation: (result: PriceRound) => Promise<Invitation<void>>} }>}
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* @returns {Promise<{admin: OracleAdmin<PriceRound>, invitationMakers: {makePushPriceInvitation: (result: PriceRound) => Promise<Invitation<void>>} }>}
* @returns {Promise<{admin: OracleAdmin<PriceRound>, invitationMakers: {MakePushPriceInvitation: (result: PriceRound) => Promise<Invitation<void>>} }>}

The sparsely-documented continuing invitation pattern seems to including capitalizing the fields of invitation makers. For example:

    invitationMakers: Far('invitation makers', {
      AdjustBalances: vault.makeAdjustBalancesInvitation,
      CloseVault: vault.makeCloseInvitation,
      TransferVault: vault.makeTransferInvitation,
    }),

I think that's to avoid colliding with existing property names, as noted for assertKeywordName in Zoe.

I can imagine this argument is not very strong, but if we're not going to follow the pattern in this respect, I would want to discuss it more widely.

Copy link
Member

Choose a reason for hiding this comment

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

I guess the AdjustBalances example suggests just PushPrice.

Copy link
Member

Choose a reason for hiding this comment

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

Ah... and I think the method name is supposed to match the invitation description, which is also PushPrice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call. invitationMakers.makePushPriceInvitation is redundant. Changed to invitationMakers.PushPrice.

packages/zoe/src/contracts/priceAggregatorChainlink.js Outdated Show resolved Hide resolved
Comment on lines 759 to 756
/** @param {string} oracleAddr */
async initOracle(oracleAddr) {
Copy link
Member

Choose a reason for hiding this comment

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

Is this really a string? The tests suggest otherwise:

  const pricePushAdminA = await E(aggregator.creatorFacet).initOracle(
    priceOracleA.instance,
  );

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks for raising. That test was written for the agsolo era. We only support smart wallet oracles now so I've amended to make that clear. This is also necessary for durability.

@turadg turadg force-pushed the 6628-chainLink-working branch from 6ff21e3 to d0ef5e9 Compare December 21, 2022 18:41
@turadg turadg changed the base branch from master to ta/zoeService-type December 21, 2022 18:41
@turadg turadg marked this pull request as draft December 21, 2022 18:41
Base automatically changed from ta/zoeService-type to master December 21, 2022 19:22
@turadg turadg force-pushed the 6628-chainLink-working branch from d0ef5e9 to 8e61373 Compare December 21, 2022 19:58
@turadg turadg marked this pull request as ready for review December 21, 2022 20:48
@turadg turadg requested a review from dckc December 21, 2022 20:48
Copy link
Member

@dckc dckc left a comment

Choose a reason for hiding this comment

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

let's go.

I'm fuzzy on some of the details. And I didn't reproduce the scripted integration test. But... onward.

@turadg turadg added the automerge:no-update (expert!) Automatically merge without updates label Dec 21, 2022
@mergify mergify bot merged commit e74278c into master Dec 21, 2022
@mergify mergify bot deleted the 6628-chainLink-working branch December 21, 2022 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:no-update (expert!) Automatically merge without updates
Projects
None yet
Development

Successfully merging this pull request may close these issues.

priceAggregatorChainlink working on chain
2 participants