Skip to content

Commit

Permalink
Add extra oracle tests
Browse files Browse the repository at this point in the history
  • Loading branch information
RickGriff committed Jan 21, 2025
1 parent 204a3de commit b1e188d
Show file tree
Hide file tree
Showing 3 changed files with 125 additions and 5 deletions.
99 changes: 95 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 All @@ -101,6 +104,8 @@ contract OraclesMainnet is TestAccounts {
mockRethToken = new RETHTokenMock();
mockWstethToken = new WSTETHTokenMock();



// Record contracts
for (uint256 c = 0; c < vars.numCollaterals; c++) {
contractsArray.push(result.contractsArray[c]);
Expand Down Expand Up @@ -189,6 +194,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));
}

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 +314,43 @@ 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 +2044,65 @@ 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()"));
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()"));
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);
}




// - 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
30 changes: 30 additions & 0 deletions contracts/test/TestContracts/GasGuzzlerToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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) {
// Expensive SLOAD loop that hits the block gas limit before completing
for (uint256 i = 0; i < 1000000; i++) {
uint256 unusedVar = pointlessStorageVar + i;
}
return 11e17;
}
}

0 comments on commit b1e188d

Please sign in to comment.