-
Notifications
You must be signed in to change notification settings - Fork 45
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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"; | ||
|
@@ -29,6 +30,7 @@ contract OraclesMainnet is TestAccounts { | |
AggregatorV3Interface rethOracle; | ||
|
||
ChainlinkOracleMock mockOracle; | ||
GasGuzzlerToken gasGuzzlerToken; | ||
|
||
IMainnetPriceFeed wethPriceFeed; | ||
IRETHPriceFeed rethPriceFeed; | ||
|
@@ -93,6 +95,7 @@ contract OraclesMainnet is TestAccounts { | |
stethOracle = AggregatorV3Interface(result.externalAddresses.STETHOracle); | ||
|
||
mockOracle = new ChainlinkOracleMock(); | ||
gasGuzzlerToken = new GasGuzzlerToken(); | ||
|
||
rethToken = IRETHToken(result.externalAddresses.RETHToken); | ||
|
||
|
@@ -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)); | ||
} | ||
|
||
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 { | ||
|
@@ -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 { | ||
|
@@ -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()")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn’t this be |
||
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()")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this a leftover?