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 1 commit
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
96 changes: 73 additions & 23 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/GasGuzzlerOracle.sol";
import "./TestContracts/GasGuzzlerToken.sol";
import "./TestContracts/RETHTokenMock.sol";
import "./TestContracts/WSTETHTokenMock.sol";
Expand All @@ -31,6 +32,7 @@ contract OraclesMainnet is TestAccounts {

ChainlinkOracleMock mockOracle;
GasGuzzlerToken gasGuzzlerToken;
GasGuzzlerOracle gasGuzzlerOracle;

IMainnetPriceFeed wethPriceFeed;
IRETHPriceFeed rethPriceFeed;
Expand Down Expand Up @@ -96,6 +98,7 @@ contract OraclesMainnet is TestAccounts {

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

rethToken = IRETHToken(result.externalAddresses.RETHToken);

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

function etchGasGuzzlerToEthOracle(bytes memory _mockOracleCode) internal {
// Etch the mock code to the ETH-USD oracle address
vm.etch(address(ethOracle), _mockOracleCode);
GasGuzzlerOracle mock = GasGuzzlerOracle(address(ethOracle));
mock.setDecimals(8);
// Fake ETH-USD price of 2000 USD
mock.setPrice(2000e8);
mock.setUpdatedAt(block.timestamp);
}


function etchGasGuzzlerToRethOracle(bytes memory _mockOracleCode) internal {
// Etch the mock code to the RETH-ETH oracle address
vm.etch(address(rethOracle), _mockOracleCode);
// Wrap so we can use the mock's setters
GasGuzzlerOracle mock = GasGuzzlerOracle(address(rethOracle));
mock.setDecimals(18);
// Set 1 RETH = 1.1 ETH
mock.setPrice(11e17);
mock.setUpdatedAt(block.timestamp);
}

function etchGasGuzzlerToStethOracle(bytes memory _mockOracleCode) internal {
// Etch the mock code to the STETH-USD oracle address
vm.etch(address(stethOracle), _mockOracleCode);
// Wrap so we can use the mock's setters
GasGuzzlerOracle mock = GasGuzzlerOracle(address(stethOracle));
mock.setDecimals(8);
// Set 1 STETH = 2000 USD
mock.setPrice(2000e8);
mock.setUpdatedAt(block.timestamp);
}

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));
}

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 ---
Expand Down Expand Up @@ -2042,58 +2074,76 @@ contract OraclesMainnet is TestAccounts {

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

// --- Call these functions with 10k gas - i.e. enough to run out of gas in the Chainlink calls ---
function testRevertLowGasSTETHOracle() 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()"));
assertTrue(success);

// Etch gas guzzler to the oracle
etchGasGuzzlerToStethOracle(address(gasGuzzlerOracle).code);

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

Choose a reason for hiding this comment

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

Interesting, so when the failure is catched by vm.expecRevert then bool return value is true?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes, strange design choice in Foundry IMO. See the "Gotcha: low level calls" section here:
https://book.getfoundry.sh/cheatcodes/expect-revert

}

function testRevertLowGasRETHOracle() 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 oracle
etchGasGuzzlerToRethOracle(address(gasGuzzlerOracle).code);

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

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

// Etch gas guzzler to the oracle
etchGasGuzzlerToEthOracle(address(gasGuzzlerOracle).code);

// After etching the gas guzzler to the oracle, confirm the same call with 500k gas now reverts due to OOG
vm.expectRevert(MainnetPriceFeedBase.InsufficientGasForExternalCall.selector);
// just catch return val to suppress warning
(bool success,) = address(wethPriceFeed).call{gas: 10000}(abi.encodeWithSignature("fetchPrice()"));
assertFalse(success);
(bool revertAsExpected,) = address(wethPriceFeed).call{gas: 500000}(abi.encodeWithSignature("fetchPrice()"));
assertTrue(revertAsExpected);
}

// --- 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()"));
(bool success,) = address(wstethPriceFeed).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()"));
assertFalse(success);
(bool revertsAsExpected,) = address(wstethPriceFeed).call{gas: 500000}(abi.encodeWithSignature("fetchPrice()"));
assertTrue(revertsAsExpected);
}

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()"));
(bool success,) = address(rethPriceFeed).call{gas: 500000}(abi.encodeWithSignature("fetchPrice()"));
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()"));
assertFalse(success);
(bool revertsAsExpected,) = address(rethPriceFeed).call{gas: 500000}(abi.encodeWithSignature("fetchPrice()"));
assertTrue(revertsAsExpected);
}

// - More basic actions tests (adjust, close, etc)
Expand Down
47 changes: 47 additions & 0 deletions contracts/test/TestContracts/GasGuzzlerOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: BUSL-1.1

pragma solidity 0.8.24;

import "src/Dependencies/AggregatorV3Interface.sol";

// Mock oracle that consumes all gas in the price getter.
// this contract code is etched over mainnet oracle addresses in mainnet fork tests.
contract GasGuzzlerOracle is AggregatorV3Interface {
uint8 decimal;

int256 price;

uint256 lastUpdateTime;

uint256 pointlessStorageVar = 42;

// We use 8 decimals unless set to 18
function decimals() external view returns (uint8) {
return decimal;
}

function latestRoundData()
external
view
returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound)
{
// Expensive SLOAD loop that hits the block gas limit before completing
for (uint256 i = 0; i < 1000000; i++) {
uint256 unusedVar = pointlessStorageVar + i;
}

return (0, price, 0, lastUpdateTime, 0);
}

function setDecimals(uint8 _decimals) external {
decimal = _decimals;
}

function setPrice(int256 _price) external {
price = _price;
}

function setUpdatedAt(uint256 _updatedAt) external {
lastUpdateTime = _updatedAt;
}
}
Loading