Skip to content

Commit

Permalink
Merge pull request #38 from perpetual-protocol/feature/price-feed-upd…
Browse files Browse the repository at this point in the history
…ater

PriceFeedUpdater
  • Loading branch information
tailingchen authored Aug 23, 2022
2 parents 3945f1f + fc78ee5 commit 5042fb8
Show file tree
Hide file tree
Showing 6 changed files with 164 additions and 13 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [unreleased]
## [0.5.0] - 2022-08-23

- Add `PriceFeedUpdater`

## [0.4.2] - 2022-06-08

Expand Down
42 changes: 42 additions & 0 deletions contracts/PriceFeedUpdater.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.7.6;

import { Address } from "@openzeppelin/contracts/utils/Address.sol";
import { IPriceFeedUpdate } from "./interface/IPriceFeedUpdate.sol";

contract PriceFeedUpdater {
using Address for address;

address[] internal _priceFeeds;

constructor(address[] memory priceFeedsArg) {
// PFU_PFANC: price feed address is not contract
for (uint256 i = 0; i < priceFeedsArg.length; i++) {
require(priceFeedsArg[i].isContract(), "PFU_PFANC");
}

_priceFeeds = priceFeedsArg;
}

//
// EXTERNAL NON-VIEW
//

/* solhint-disable payable-fallback */
fallback() external {
for (uint256 i = 0; i < _priceFeeds.length; i++) {
// Updating PriceFeed might be failed because of price not changed,
// Add try-catch here to update all markets anyway
/* solhint-disable no-empty-blocks */
try IPriceFeedUpdate(_priceFeeds[i]).update() {} catch {}
}
}

//
// EXTERNAL VIEW
//

function getPriceFeeds() external view returns (address[] memory) {
return _priceFeeds;
}
}
7 changes: 7 additions & 0 deletions contracts/interface/IPriceFeedUpdate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.7.6;

interface IPriceFeedUpdate {
/// @dev Update latest price.
function update() external;
}
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@perp/perp-oracle-contract",
"version": "0.4.3",
"description": "Perpetual Protocol Curie (v2) oracle contracts - v0.3.4 is not an audited version",
"version": "0.5.0",
"description": "Perpetual Protocol Curie (v2) oracle contracts - v0.5.0 is not an audited version",
"license": "GPL-3.0-or-later",
"author": {
"name": "Perpetual Protocol",
Expand Down Expand Up @@ -42,7 +42,7 @@
},
"devDependencies": {
"@chainlink/contracts": "0.1.7",
"@defi-wonderland/smock": "2.0.7",
"@defi-wonderland/smock": "2.2.0",
"@nomiclabs/hardhat-ethers": "2.0.5",
"@nomiclabs/hardhat-waffle": "2.0.3",
"@openzeppelin/contracts": "3.4.0",
Expand Down
100 changes: 100 additions & 0 deletions test/PriceFeedUpdater.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { MockContract, smock } from "@defi-wonderland/smock"
import chai, { expect } from "chai"
import { Wallet } from "ethers"
import { parseEther } from "ethers/lib/utils"
import { ethers, waffle } from "hardhat"
import {
ChainlinkPriceFeedV2,
ChainlinkPriceFeedV2__factory,
PriceFeedUpdater,
TestAggregatorV3__factory,
} from "../typechain"

chai.use(smock.matchers)

interface PriceFeedUpdaterFixture {
ethPriceFeed: MockContract<ChainlinkPriceFeedV2>
btcPriceFeed: MockContract<ChainlinkPriceFeedV2>
priceFeedUpdater: PriceFeedUpdater
admin: Wallet
alice: Wallet
}

describe("PriceFeedUpdater Spec", () => {
const loadFixture: ReturnType<typeof waffle.createFixtureLoader> = waffle.createFixtureLoader()
let fixture: PriceFeedUpdaterFixture

beforeEach(async () => {
fixture = await loadFixture(createFixture)
})
afterEach(async () => {
fixture.btcPriceFeed.update.reset()
fixture.ethPriceFeed.update.reset()
})

async function executeFallback(priceFeedUpdater: PriceFeedUpdater) {
const { alice } = fixture
await alice.sendTransaction({
to: priceFeedUpdater.address,
value: 0,
gasLimit: 150000, // Give gas limit to force run transaction without dry run
})
}

async function createFixture(): Promise<PriceFeedUpdaterFixture> {
const [admin, alice] = waffle.provider.getWallets()

const aggregatorFactory = await smock.mock<TestAggregatorV3__factory>("TestAggregatorV3")
const aggregator = await aggregatorFactory.deploy()

const chainlinkPriceFeedV2Factory = await smock.mock<ChainlinkPriceFeedV2__factory>("ChainlinkPriceFeedV2")
const ethPriceFeed = await chainlinkPriceFeedV2Factory.deploy(aggregator.address, 900)
const btcPriceFeed = await chainlinkPriceFeedV2Factory.deploy(aggregator.address, 900)

await ethPriceFeed.deployed()
await btcPriceFeed.deployed()

const priceFeedUpdaterFactory = await ethers.getContractFactory("PriceFeedUpdater")
const priceFeedUpdater = (await priceFeedUpdaterFactory.deploy([
ethPriceFeed.address,
btcPriceFeed.address,
])) as PriceFeedUpdater

return { ethPriceFeed, btcPriceFeed, priceFeedUpdater, admin, alice }
}
it("the result of getPriceFeeds should be same as priceFeeds given when deployment", async () => {
const { ethPriceFeed, btcPriceFeed, priceFeedUpdater } = fixture
const priceFeeds = await priceFeedUpdater.getPriceFeeds()
expect(priceFeeds).deep.equals([ethPriceFeed.address, btcPriceFeed.address])
})

it("force error, when someone sent eth to contract", async () => {
const { alice, priceFeedUpdater } = fixture
const tx = alice.sendTransaction({
to: priceFeedUpdater.address,
value: parseEther("0.1"),
gasLimit: 150000, // Give gas limit to force run transaction without dry run
})
await expect(tx).to.be.reverted
})

describe("When priceFeedUpdater fallback execute", () => {
it("should success if all priceFeed are updated successfully", async () => {
const { ethPriceFeed, btcPriceFeed, priceFeedUpdater } = fixture

await executeFallback(priceFeedUpdater)

expect(ethPriceFeed.update).to.have.been.calledOnce
expect(btcPriceFeed.update).to.have.been.calledOnce
})
it("should still success if any one of priceFeed is updated fail", async () => {
const { ethPriceFeed, btcPriceFeed, priceFeedUpdater } = fixture

ethPriceFeed.update.reverts()
await executeFallback(priceFeedUpdater)

expect(ethPriceFeed.update).to.have.been.calledOnce
expect(btcPriceFeed.update).to.have.been.calledOnce
})
})
})

0 comments on commit 5042fb8

Please sign in to comment.