Skip to content

Commit

Permalink
Merge branch 'release/v4.7.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
TylerEther committed May 23, 2024
2 parents 4e092bb + bf545ee commit dca565e
Show file tree
Hide file tree
Showing 17 changed files with 577 additions and 383 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# Changelog

## 4.7.1
### Accumulators
- Add constructor sanitization
- Fix EIP-165 compliance issue

### Interfaces
- Add a warning about using 0 for maxAge.

### Oracles
- Update PythOracleView
- Add exponent validation
- Add support for Pyth confidence intervals

## v4.7.0
### Accumulators
- Modify AlocUtilizationAndErrorAccumulator: Add constructor flag to treat an empty ALOC as 0% utilized.
Expand Down
6 changes: 5 additions & 1 deletion contracts/accumulators/AbstractAccumulator.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ abstract contract AbstractAccumulator is IERC165, IAccumulator {

/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return interfaceId == type(IAccumulator).interfaceId;
if (interfaceId == 0xffffffff) {
return false;
}

return interfaceId == type(IAccumulator).interfaceId || interfaceId == type(IERC165).interfaceId;
}

function _updateThreshold() internal view virtual returns (uint256) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ contract AdrastiaPriceAccumulator is PriceAccumulator {

bool public immutable validationDisabled;

error InvalidAveragingStrategy(address strategy);

constructor(
bool validationDisabled_,
IAveragingStrategy averagingStrategy_,
Expand All @@ -30,6 +32,10 @@ contract AdrastiaPriceAccumulator is PriceAccumulator {
maxUpdateDelay_
)
{
if (address(averagingStrategy_) == address(0)) {
revert InvalidAveragingStrategy(address(averagingStrategy_));
}

validationDisabled = validationDisabled_;
adrastiaOracle = adrastiaOracle_;
}
Expand Down
19 changes: 8 additions & 11 deletions contracts/interfaces/ILiquidityOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,22 @@ abstract contract ILiquidityOracle is IUpdateable, IQuoteToken {
/// @param token The token to get liquidity levels of (along with the quote token).
/// @return tokenLiquidity The amount of the token that is liquid in the underlying pool, in wei.
/// @return quoteTokenLiquidity The amount of the quote token that is liquid in the underlying pool, in wei.
function consultLiquidity(address token)
public
view
virtual
returns (uint112 tokenLiquidity, uint112 quoteTokenLiquidity);
function consultLiquidity(
address token
) public view virtual returns (uint112 tokenLiquidity, uint112 quoteTokenLiquidity);

/**
* @notice Gets the liquidity levels of the token and the quote token in the underlying pool, reverting if the
* quotation is older than the maximum allowable age.
* @dev Using maxAge of 0 can be gas costly and the returned data is easier to manipulate.
* @param token The token to get liquidity levels of (along with the quote token).
* @param maxAge The maximum age of the quotation, in seconds. If 0, the function gets the instant rates as of the
* latest block, straight from the source.
* latest block, straight from the source. WARNING: Using a maxAge of 0 is expensive and is generally insecure.
* @return tokenLiquidity The amount of the token that is liquid in the underlying pool, in wei.
* @return quoteTokenLiquidity The amount of the quote token that is liquid in the underlying pool, in wei.
*/
function consultLiquidity(address token, uint256 maxAge)
public
view
virtual
returns (uint112 tokenLiquidity, uint112 quoteTokenLiquidity);
function consultLiquidity(
address token,
uint256 maxAge
) public view virtual returns (uint112 tokenLiquidity, uint112 quoteTokenLiquidity);
}
2 changes: 1 addition & 1 deletion contracts/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ abstract contract IOracle is IUpdateable, IPriceOracle, ILiquidityOracle {
* @dev Using maxAge of 0 can be gas costly and the returned data is easier to manipulate.
* @param token The token to get the price of.
* @param maxAge The maximum age of the quotation, in seconds. If 0, the function gets the instant rates as of the
* latest block, straight from the source.
* latest block, straight from the source. WARNING: Using a maxAge of 0 is expensive and is generally insecure.
* @return price The quote token denominated price for a whole token.
* @return tokenLiquidity The amount of the token that is liquid in the underlying pool, in wei.
* @return quoteTokenLiquidity The amount of the quote token that is liquid in the underlying pool, in wei.
Expand Down
2 changes: 1 addition & 1 deletion contracts/interfaces/IPriceOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ abstract contract IPriceOracle is IUpdateable, IQuoteToken {
* @dev Using maxAge of 0 can be gas costly and the returned data is easier to manipulate.
* @param token The token to get the price of.
* @param maxAge The maximum age of the quotation, in seconds. If 0, the function gets the instant rates as of the
* latest block, straight from the source.
* latest block, straight from the source. WARNING: Using a maxAge of 0 is expensive and is generally insecure.
* @return price The quote token denominated price for a whole token.
*/
function consultPrice(address token, uint256 maxAge) public view virtual returns (uint112 price);
Expand Down
5 changes: 5 additions & 0 deletions contracts/oracles/views/DiaOracleView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ contract DiaOracleView is AbstractOracle {
error AnswerTooLarge(int256 answer);
error InvalidTimestamp(uint256 timestamp);
error UnsupportedToken(address token);
error InvalidConstructorArgument();

/**
* @notice Constructs a new DiaOracleView contract.
Expand All @@ -35,6 +36,10 @@ contract DiaOracleView is AbstractOracle {
uint8 feedDecimals_,
address quoteToken_
) AbstractOracle(quoteToken_) {
if (diaAddress_ == address(0) || feedToken_ == address(0)) {
revert InvalidConstructorArgument();
}

diaAddress = diaAddress_;
feedId = feedId_;
feedToken = feedToken_;
Expand Down
58 changes: 55 additions & 3 deletions contracts/oracles/views/PythOracleView.sol
Original file line number Diff line number Diff line change
Expand Up @@ -22,29 +22,45 @@ contract PythOracleView is AbstractOracle {
address internal immutable feedToken;
address internal immutable pythAddress;
bytes32 internal immutable feedId;
uint64 internal immutable minConfidence;
uint8 internal immutable feedTokenDecimals;

uint8 public constant CONFIDENCE_DECIMALS = 8;
uint256 internal constant CONFIDENCE_MULTIPLIER = 10 ** CONFIDENCE_DECIMALS;

error AnswerCannotBeNegative(int256 answer);
error AnswerTooLarge(int256 answer);
error InvalidTimestamp(uint256 timestamp);
error UnsupportedToken(address token);
error InvalidConstructorArgument();
error ConfidenceTooLow(uint256 confidence);
error InvalidExponent(int32 exponent);

/**
* @notice Constructs a new PythOracleView contract.
* @param pythAddress_ The address of the Pyth contract.
* @param feedId_ The ID of the Pyth feed.
* @param feedToken_ The address of the token that the feed describes.
* @param minConfidence_ The minimum confidence level required for the oracle to return a price.
* 1e`CONFIDENCE_DECIMALS` represents 100% confidence and 0 represents 0% confidence. 0% confidence is not
* allowed.
* @param quoteToken_ (Optional) The address of the quote token.
*/
constructor(
address pythAddress_,
bytes32 feedId_,
address feedToken_,
uint64 minConfidence_,
address quoteToken_
) AbstractOracle(quoteToken_) {
if (pythAddress_ == address(0) || feedToken_ == address(0) || minConfidence_ == 0) {
revert InvalidConstructorArgument();
}

pythAddress = pythAddress_;
feedId = feedId_;
feedToken = feedToken_;
minConfidence = minConfidence_;
feedTokenDecimals = super.quoteTokenDecimals();
}

Expand Down Expand Up @@ -111,6 +127,15 @@ contract PythOracleView is AbstractOracle {
return feedToken;
}

/**
* @notice Gets the minimum confidence level required for the oracle to return a price.
* @return The minimum confidence level, with 1e`CONFIDENCE_DECIMALS` representing 100% confidence and 0
* representing 0% confidence.
*/
function getMinConfidence() public view virtual returns (uint64) {
return minConfidence;
}

/// @inheritdoc AbstractOracle
function instantFetch(
address token
Expand All @@ -133,10 +158,18 @@ contract PythOracleView is AbstractOracle {

uint256 ourDecimals = quoteTokenDecimals();
uint256 workingPrice = uint256(int256(data.price)) * (10 ** ourDecimals);
if (data.expo > 0) {
workingPrice *= 10 ** uint32(data.expo);
uint256 confidenceInterval = uint256(data.conf) * (10 ** ourDecimals);
if (data.expo > 12 || data.expo < -12) {
// The range of exponents supported by the Pyth Network client code is [-12, 12]
revert InvalidExponent(data.expo);
} else if (data.expo < 0) {
workingPrice /= 10 ** uint32(-data.expo);
uint256 divisor = 10 ** uint32(-data.expo);
workingPrice /= divisor;
confidenceInterval /= divisor;
} else if (data.expo > 0) {
uint256 multiplier = 10 ** uint32(data.expo);
workingPrice *= multiplier;
confidenceInterval *= multiplier;
}

if (workingPrice > type(uint112).max) {
Expand All @@ -147,6 +180,25 @@ contract PythOracleView is AbstractOracle {
revert InvalidTimestamp(data.publishTime);
}

if (workingPrice > 0) {
// Inverse confidence: 0 is 100% confidence; CONFIDENCE_MULTIPLIER or above is 0% confidence
uint256 confidence = ((confidenceInterval * CONFIDENCE_MULTIPLIER) / workingPrice);
if (confidence > CONFIDENCE_MULTIPLIER) {
revert ConfidenceTooLow(0);
}
// Confidence: 0 is 0% confidence; CONFIDENCE_MULTIPLIER is 100% confidence
confidence = CONFIDENCE_MULTIPLIER - confidence;
if (confidence < getMinConfidence()) {
revert ConfidenceTooLow(confidence);
}
} else {
if (data.conf > 0) {
// If the confidence interval is non-zero, we can't trust the price
// The price can only be zero if we're 100% confident that it's zero
revert ConfidenceTooLow(0);
}
}

price = uint112(workingPrice);
timestamp = uint32(data.publishTime);
}
Expand Down
4 changes: 4 additions & 0 deletions contracts/test/InterfaceIds.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,8 @@ contract InterfaceIds {
function iValidationStrategy() external pure returns (bytes4) {
return type(IValidationStrategy).interfaceId;
}

function invalidInterface() external pure returns (bytes4) {
return 0xffffffff;
}
}
21 changes: 19 additions & 2 deletions hardhat.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ require("dotenv").config();
require("@nomiclabs/hardhat-waffle");
require("solidity-coverage");
require("hardhat-gas-reporter");
require("hardhat-tracer");
// require("hardhat-tracer");
require("@atixlabs/hardhat-time-n-mine");
require("hardhat-contract-sizer");
require("@nomiclabs/hardhat-etherscan");
Expand Down Expand Up @@ -50,7 +50,7 @@ module.exports = {
networks: {
hardhat: {
gas: 10000000,
hardfork: process.env.HARDHAT_HARDFORK || "london",
hardfork: process.env.HARDHAT_HARDFORK || "shanghai",
forking: {
url: process.env.ETHEREUM_URL || "",
blockNumber: 17500000,
Expand Down Expand Up @@ -86,6 +86,14 @@ module.exports = {
chainId: 9001,
url: process.env.EVMOS_URL || "",
},
mode: {
chainId: 34443,
url: process.env.MODE_URL || "",
},
bsc: {
chainId: 56,
url: process.env.BSC_URL || "",
},
},
etherscan: {
apiKey: {
Expand All @@ -95,6 +103,7 @@ module.exports = {
arbitrumOne: process.env.ARBISCAN_API_KEY,
optimisticEthereum: process.env.OPTIMISTIC_ETHERSCAN_API_KEY,
evmos: process.env.ESCAN_API_KEY,
mode: "placeholder",
},
customChains: [
{
Expand All @@ -113,6 +122,14 @@ module.exports = {
browserURL: "https://escan.live",
},
},
{
network: "mode",
chainId: 34443,
urls: {
apiURL: "https://explorer.mode.network/api",
browserURL: "https://explorer.mode.network",
},
},
],
},
mocha: {
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@adrastia-oracle/adrastia-core",
"version": "4.7.0",
"version": "4.7.1",
"main": "index.js",
"author": "TRILEZ SOFTWARE INC.",
"license": "BUSL-1.1",
Expand Down Expand Up @@ -47,10 +47,10 @@
"eth-gas-reporter": "^0.2.25",
"ethereum-waffle": "4.0.0-alpha.0",
"ethers": "^5.7.2",
"hardhat": "2.14.0",
"hardhat": "2.22.3",
"hardhat-contract-sizer": "^2.10.0",
"hardhat-gas-reporter": "^1.0.9",
"hardhat-tracer": "^2.4.0",
"hardhat-tracer": "^2.8.2",
"prettier": "^2.7.1",
"prettier-plugin-solidity": "^1.0.0-rc.1",
"solhint": "^3.3.7",
Expand Down
15 changes: 12 additions & 3 deletions scripts/consult-pyth.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const pythAddress = "0x4305FB66699C3B2702D4d05CF36551390A4c69C6";
const wethFeedId = "0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace";
const wbtcFeedId = "0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43";

const minConfidence = ethers.utils.parseUnits("0.9", 8);

function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Expand All @@ -23,8 +25,15 @@ async function createContract(name, ...deploymentArgs) {
return contract;
}

async function createPythOracleView(pythAddress, feedId, tokenAddress, quoteTokenAddress) {
const oracle = await createContract("PythOracleView", pythAddress, feedId, tokenAddress, quoteTokenAddress);
async function createPythOracleView(pythAddress, feedId, tokenAddress, minConfidence, quoteTokenAddress) {
const oracle = await createContract(
"PythOracleView",
pythAddress,
feedId,
tokenAddress,
minConfidence,
quoteTokenAddress
);

return {
oracle: oracle,
Expand All @@ -38,7 +47,7 @@ async function main() {
//const feedId = wethFeedId;
const feedId = wbtcFeedId;

const oracle = await createPythOracleView(pythAddress, feedId, token, quoteToken);
const oracle = await createPythOracleView(pythAddress, feedId, token, minConfidence, quoteToken);

const quoteTokenDecimals = await oracle.oracle.quoteTokenDecimals();

Expand Down
10 changes: 10 additions & 0 deletions test/liquidity-accumulator/liquidity-accumulator-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1579,6 +1579,16 @@ function describeLiquidityAccumulatorTests(
const interfaceId = await interfaceIds.iAccumulator();
expect(await liquidityAccumulator["supportsInterface(bytes4)"](interfaceId)).to.equal(true);
});

it("Should support IERC165", async () => {
const interfaceId = await interfaceIds.iERC165();
expect(await liquidityAccumulator["supportsInterface(bytes4)"](interfaceId)).to.equal(true);
});

it("Should return false for the invalid interface", async () => {
const interfaceId = await interfaceIds.invalidInterface();
expect(await liquidityAccumulator["supportsInterface(bytes4)"](interfaceId)).to.equal(false);
});
});

describe(contractName + "#consultLiquidity(token)", function () {
Expand Down
Loading

0 comments on commit dca565e

Please sign in to comment.