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

Add extra oracle tests #734

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 2 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
92 changes: 88 additions & 4 deletions contracts/test/OracleMainnet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import "src/PriceFeeds/WETHPriceFeed.sol";

import "./TestContracts/Accounts.sol";
import "./TestContracts/ChainlinkOracleMock.sol";
import "./TestContracts/GasGuzzlerToken.sol";
import "./TestContracts/RETHTokenMock.sol";
import "./TestContracts/WSTETHTokenMock.sol";
import "./TestContracts/Deployment.t.sol";
Expand All @@ -29,6 +30,7 @@ contract OraclesMainnet is TestAccounts {
AggregatorV3Interface rethOracle;

ChainlinkOracleMock mockOracle;
GasGuzzlerToken gasGuzzlerToken;

IMainnetPriceFeed wethPriceFeed;
IRETHPriceFeed rethPriceFeed;
Expand Down Expand Up @@ -93,6 +95,7 @@ contract OraclesMainnet is TestAccounts {
stethOracle = AggregatorV3Interface(result.externalAddresses.STETHOracle);

mockOracle = new ChainlinkOracleMock();
gasGuzzlerToken = new GasGuzzlerToken();

rethToken = IRETHToken(result.externalAddresses.RETHToken);

Expand Down Expand Up @@ -189,6 +192,20 @@ contract OraclesMainnet is TestAccounts {
mock.setUpdatedAt(block.timestamp - 7 days);
}

function etchGasGuzzlerMockToRethToken(bytes memory _mockTokenCode) internal {
// Etch the mock code to the RETH token address
vm.etch(address(rethToken), _mockTokenCode);
// // Wrap so we can use the mock's functions
// GasGuzzlerToken mockReth = GasGuzzlerToken(address(rethToken));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is this a leftover?

}

function etchGasGuzzlerMockToWstethToken(bytes memory _mockTokenCode) internal {
// Etch the mock code to the RETH token address
vm.etch(address(wstETH), _mockTokenCode);
// // Wrap so we can use the mock's functions
// GasGuzzlerToken mockWsteth = GasGuzzlerToken(address(wstETH));
}

// --- lastGoodPrice set on deployment ---

function testSetLastGoodPriceOnDeploymentWETH() public view {
Expand Down Expand Up @@ -295,6 +312,41 @@ contract OraclesMainnet is TestAccounts {
assertEq(storedStEthUsdStaleness, _24_HOURS);
}

// --- LST exchange rates and market price oracle sanity checks ---

function testRETHExchangeRateBetween1And2() public {
uint256 rate = rethToken.getExchangeRate();
assertGt(rate, 1e18);
assertLt(rate, 2e18);
}

function testWSTETHExchangeRateBetween1And2() public {
uint256 rate = wstETH.stEthPerToken();
assertGt(rate, 1e18);
assertLt(rate, 2e18);
}

function testRETHOracleAnswerBetween1And2() public {
uint256 answer = _getLatestAnswerFromOracle(rethOracle);
assertGt(answer, 1e18);
assertLt(answer, 2e18);
}

function testSTETHOracleAnswerWithin1PctOfETHOracleAnswer() public {
uint256 stethUsd = _getLatestAnswerFromOracle(stethOracle);
uint256 ethUsd = _getLatestAnswerFromOracle(ethOracle);

uint256 relativeDelta;

if (stethUsd > ethUsd) {
relativeDelta = (stethUsd - ethUsd) * 1e18 / ethUsd;
} else {
relativeDelta = (ethUsd - stethUsd) * 1e18 / stethUsd;
}

assertLt(relativeDelta, 1e16);
}

// // --- Basic actions ---

function testOpenTroveWETH() public {
Expand Down Expand Up @@ -1988,30 +2040,62 @@ contract OraclesMainnet is TestAccounts {
assertEq(contractsArray[1].collToken.balanceOf(A), A_collBefore + expectedCollDelta, "A's coll didn't change");
}

// --- Low gas reverts ---
// --- Low gas market oracle reverts ---

// --- Call these functions with 10k gas - i.e. enough to run out of gas in the Chainlink calls ---
function testRevertLowGasWSTETH() public {
function testRevertLowGasSTETHOracle() public {
vm.expectRevert(MainnetPriceFeedBase.InsufficientGasForExternalCall.selector);
// just catch return val to suppress warning
(bool success,) = address(wstethPriceFeed).call{gas: 10000}(abi.encodeWithSignature("fetchPrice()"));
assertFalse(success);
}

function testRevertLowGasRETH() public {
function testRevertLowGasRETHOracle() public {
vm.expectRevert(MainnetPriceFeedBase.InsufficientGasForExternalCall.selector);
// just catch return val to suppress warning
(bool success,) = address(rethPriceFeed).call{gas: 10000}(abi.encodeWithSignature("fetchPrice()"));
assertFalse(success);
}

function testRevertLowGasWETH() public {
function testRevertLowGasETHOracle() public {
vm.expectRevert(MainnetPriceFeedBase.InsufficientGasForExternalCall.selector);
// just catch return val to suppress warning
(bool success,) = address(wethPriceFeed).call{gas: 10000}(abi.encodeWithSignature("fetchPrice()"));
assertFalse(success);
}

// --- Test with a gas guzzler token, and confirm revert ---

function testRevertLowGasWSTETHToken() public {
// Confirm call to the real external contracts succeeds with sufficient gas i.e. 500k
(bool success,) = address(rethPriceFeed).call{gas: 500000}(abi.encodeWithSignature("fetchPrice()"));
assertTrue(success);

// Etch gas guzzler to the LST
etchGasGuzzlerMockToWstethToken(address(gasGuzzlerToken).code);

// After etching the gas guzzler to the LST, confirm the same call with 500k gas now reverts due to OOG
vm.expectRevert(MainnetPriceFeedBase.InsufficientGasForExternalCall.selector);
// just catch return val to suppress warning
(success,) = address(wstethPriceFeed).call{gas: 10000}(abi.encodeWithSignature("fetchPrice()"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment says “the same call with 500k”, but we are sending only 10k?

assertFalse(success);
}

function testRevertLowGasRETHToken() public {
// Confirm call to the real external contracts succeeds with sufficient gas i.e. 500k
(bool success,) = address(wstethPriceFeed).call{gas: 500000}(abi.encodeWithSignature("fetchPrice()"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Shouldn’t this be rethPrice?

assertTrue(success);

// Etch gas guzzler to the LST
etchGasGuzzlerMockToRethToken(address(gasGuzzlerToken).code);

// After etching the gas guzzler to the LST, confirm the same call with 500k gas now reverts due to OOG
vm.expectRevert(MainnetPriceFeedBase.InsufficientGasForExternalCall.selector);
// just catch return val to suppress warning
(success,) = address(rethPriceFeed).call{gas: 10000}(abi.encodeWithSignature("fetchPrice()"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above

assertFalse(success);
}

// - More basic actions tests (adjust, close, etc)
// - liq tests (manipulate aggregator stored price)
}
1 change: 0 additions & 1 deletion contracts/test/TestContracts/ChainlinkOracleMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import "src/Dependencies/AggregatorV3Interface.sol";

// Mock Chainlink oracle that returns a stale price answer.
// this contract code is etched over mainnet oracle addresses in mainnet fork tests.
// As such, we use bools for staleness and decimals to save us having to set some contract state each time after etching.
contract ChainlinkOracleMock is AggregatorV3Interface {
uint8 decimal;

Expand Down
28 changes: 28 additions & 0 deletions contracts/test/TestContracts/GasGuzzlerToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.24;

// Mock token that uses all available gas on exchange rate calls.
// This contract code is etched over LST token addresses in mainnet fork tests.
// Has exchange rate functions for WSTETH and RETH.
contract GasGuzzlerToken {
uint256 pointlessStorageVar = 42;

// RETH exchange rate getter
function getExchangeRate() external view returns (uint256) {
// Expensive SLOAD loop that hits the block gas limit before completing
for (uint256 i = 0; i < 1000000; i++) {
uint256 unusedVar = pointlessStorageVar + i;
}
return 11e17;
}

// WSTETH exchange rate getter
function stEthPerToken() external view returns (uint256) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Don’t we need one for RETH?

// Expensive SLOAD loop that hits the block gas limit before completing
for (uint256 i = 0; i < 1000000; i++) {
uint256 unusedVar = pointlessStorageVar + i;
}
return 11e17;
}
}
Loading