Skip to content

Commit 3b00b25

Browse files
fireboss777beeb
andauthored
test(middleware): chainlink middleware (#893)
* chore: ffi api * test: parsing data * test: parseAndValidate * fix: decode * docs: natspecs * docs: natspecs * feat: custom errors * test: rust improvement * test: rust errors * fix: error names * docs: title * fix: contracts names * docs: comments * fix: interface names * fix: interfaces * test: constant names * fix: timestamp * test: delete logs * chore: flake * chore: flake typo * fix: valid from liquidation * fix: sanity timestamp check * feat: common middleware * refactor(middleware): reduce code duplication and simplify inheritance graph * perf: actions order * fix: _getLiquidationPrice * refactor: enum * refactor: contract name * docs: comments * fix: import * test: handler * test: _getChainlinkDataStreamPrice * refactor: formatted struct * docs: typo * test: initiate * test: chainlink tests * fix: remove contract * fix: remove interface * refactor: name * docs: natspec * docs: natspec * test: mock * test: remove weth * test: native address * test: remove successful * test: move interface * test: import * chore: envs --------- Co-authored-by: beeb <[email protected]>
1 parent 5529e28 commit 3b00b25

15 files changed

+867
-6
lines changed

.github/workflows/heavy_ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ env:
99
FOUNDRY_PROFILE: heavy_ci
1010
URL_ETH_MAINNET: ${{ secrets.URL_ETH_MAINNET }}
1111
HERMES_RA2_NODE_URL: ${{ secrets.HERMES_RA2_NODE_URL }}
12+
CHAINLINK_DATA_STREAMS_API_URL: ${{ secrets.CHAINLINK_DATA_STREAMS_API_URL }}
13+
CHAINLINK_DATA_STREAMS_API_KEY: ${{ secrets.CHAINLINK_DATA_STREAMS_API_KEY }}
14+
CHAINLINK_DATA_STREAMS_API_SECRET: ${{ secrets.CHAINLINK_DATA_STREAMS_API_SECRET }}
1215

1316
jobs:
1417
heavy-ci:
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity 0.8.26;
3+
4+
import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol";
5+
6+
import {
7+
FormattedDataStreamsPrice,
8+
PriceAdjustment,
9+
PriceInfo
10+
} from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol";
11+
12+
/// @custom:feature The `_adjustDataStreamPrice` function of the `OracleMiddlewareWithDataStreams`.
13+
contract TestOracleMiddlewareWithDataStreamsAdjustDataStream is OracleMiddlewareWithDataStreamsFixture {
14+
FormattedDataStreamsPrice internal formattedPrice;
15+
16+
function setUp() public override {
17+
super.setUp();
18+
19+
formattedPrice = FormattedDataStreamsPrice({
20+
timestamp: report.observationsTimestamp,
21+
price: uint192(report.price),
22+
ask: uint192(report.ask),
23+
bid: uint192(report.bid)
24+
});
25+
}
26+
27+
/**
28+
* @custom:scenario Tests the `_adjustDataStreamPrice` without any adjustments.
29+
* @custom:when The function is called without direction of the price adjustment.
30+
* @custom:then The returned price is the `price` attribute of the report.
31+
*/
32+
function test_adjustDataStreamPriceWithoutAdjustment() public view {
33+
PriceInfo memory price = oracleMiddleware.i_adjustDataStreamPrice(formattedPrice, PriceAdjustment.None);
34+
assertEq(price.price, formattedPrice.price, "Invalid price");
35+
assertEq(price.neutralPrice, formattedPrice.price, "Invalid neutral price");
36+
assertEq(price.timestamp, formattedPrice.timestamp, "Invalid timestamp");
37+
}
38+
39+
/**
40+
* @custom:scenario Tests the `_adjustDataStreamPrice` with a `Up` adjustment.
41+
* @custom:when The function is called with `Up` for the direction of the price adjustment.
42+
* @custom:then The returned price is the `ask` attribute of the report.
43+
*/
44+
function test_adjustDataStreamPriceWithUpAdjustment() public view {
45+
PriceInfo memory price = oracleMiddleware.i_adjustDataStreamPrice(formattedPrice, PriceAdjustment.Up);
46+
assertEq(price.price, formattedPrice.ask, "Invalid price");
47+
assertEq(price.neutralPrice, formattedPrice.price, "Invalid neutral price");
48+
assertEq(price.timestamp, formattedPrice.timestamp, "Invalid timestamp");
49+
}
50+
51+
/**
52+
* @custom:scenario Tests the `_adjustDataStreamPrice` with a `Down` adjustment.
53+
* @custom:when The function is called with `Down` for the direction of the price adjustment.
54+
* @custom:then The returned price is the `bid` attribute of the report.
55+
*/
56+
function test_adjustDataStreamPriceWithDownAdjustment() public view {
57+
PriceInfo memory price = oracleMiddleware.i_adjustDataStreamPrice(formattedPrice, PriceAdjustment.Down);
58+
assertEq(price.price, formattedPrice.bid, "Invalid price");
59+
assertEq(price.neutralPrice, formattedPrice.price, "Invalid neutral price");
60+
assertEq(price.timestamp, formattedPrice.timestamp, "Invalid timestamp");
61+
}
62+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity 0.8.26;
3+
4+
import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol";
5+
6+
import { IFeeManager } from "../../../../../src/interfaces/OracleMiddleware/IFeeManager.sol";
7+
8+
import { IMockFeeManager } from "../../utils/MockFeeManager.sol";
9+
10+
/// @custom:feature The `_getChainlinkDataStreamFeeData` function of the `ChainlinkDataStreamsOracle`.
11+
contract TestOracleMiddlewareWithDataStreamFeeData is OracleMiddlewareWithDataStreamsFixture {
12+
function setUp() public override {
13+
super.setUp();
14+
}
15+
16+
/**
17+
* @custom:scenario Tests the `_getChainlinkDataStreamFeeData` function.
18+
* @custom:when The function is called.
19+
* @custom:then The `feeData.assetAddress` should match the native address of the fee manager.
20+
* @custom:and The `feeData.amount` should be equal to the `report.nativeFee`.
21+
*/
22+
function test_getChainlinkDataStreamFeeData() public view {
23+
IFeeManager.Asset memory feeData = oracleMiddleware.i_getChainlinkDataStreamFeeData(payload);
24+
25+
assertEq(feeData.assetAddress, mockFeeManager.i_nativeAddress(), "Wrong fee native address");
26+
assertEq(feeData.amount, report.nativeFee, "Wrong fee amount");
27+
}
28+
29+
/**
30+
* @custom:scenario Tests the `_getChainlinkDataStreamFeeData` function with an empty fee manager.
31+
* @custom:when The function is called with an empty fee manager.
32+
* @custom:then The fee data must be empty.
33+
*/
34+
function test_getChainlinkDataStreamFeeDataWithoutFeeManager() public {
35+
IMockFeeManager emptyFeeManager = IMockFeeManager(address(0));
36+
mockStreamVerifierProxy.setFeeManager(emptyFeeManager);
37+
IFeeManager.Asset memory feeData = oracleMiddleware.i_getChainlinkDataStreamFeeData(payload);
38+
39+
assertEq(feeData.assetAddress, address(emptyFeeManager), "Wrong fee native address");
40+
assertEq(feeData.amount, 0, "Wrong fee amount");
41+
}
42+
}
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// SPDX-License-Identifier: UNLICENSED
2+
pragma solidity 0.8.26;
3+
4+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
6+
import { EMPTY_STREAM_V3, EMPTY_STREAM_V4 } from "../../utils/Constants.sol";
7+
import { OracleMiddlewareWithDataStreamsFixture } from "../../utils/Fixtures.sol";
8+
9+
import { FormattedDataStreamsPrice } from "../../../../../src/interfaces/OracleMiddleware/IOracleMiddlewareTypes.sol";
10+
11+
/// @custom:feature The `_getChainlinkDataStreamPrice` function of the `ChainlinkDataStreamsOracle`.
12+
contract TestChainlinkDataStreamsOracleGetPrice is OracleMiddlewareWithDataStreamsFixture {
13+
function setUp() public override {
14+
super.setUp();
15+
}
16+
17+
/**
18+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a large fee.
19+
* @custom:when The function is called with a large fee.
20+
* @custom:then The call should revert with `OracleMiddlewareDataStreamFeeSafeguard`.
21+
*/
22+
function test_RevertWhen_getChainlinkDataStreamPriceFeeSafeguard() public {
23+
report.nativeFee = type(uint192).max;
24+
(, payload) = _encodeReport(report);
25+
vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareDataStreamFeeSafeguard.selector, report.nativeFee));
26+
oracleMiddleware.i_getChainlinkDataStreamPrice(payload, 0, 0);
27+
}
28+
29+
/**
30+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an incorrect fee.
31+
* @custom:when The function is called with an incorrect fee.
32+
* @custom:then The call should revert with `OracleMiddlewareIncorrectFee`.
33+
*/
34+
function test_RevertWhen_getChainlinkDataStreamPriceIncorrectFee() public {
35+
vm.expectRevert(OracleMiddlewareIncorrectFee.selector);
36+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee + 1 }(payload, 0, 0);
37+
}
38+
39+
/**
40+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid report version.
41+
* @custom:when The function is called with an invalid report version.
42+
* @custom:then The call should revert with `OracleMiddlewareInvalidReportVersion`.
43+
*/
44+
function test_RevertWhen_getChainlinkDataStreamPriceIncorrectReportVersion() public {
45+
report.feedId = EMPTY_STREAM_V4;
46+
(, payload) = _encodeReport(report);
47+
vm.expectRevert(OracleMiddlewareInvalidReportVersion.selector);
48+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
49+
}
50+
51+
/**
52+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid stream id.
53+
* @custom:when The function is called with an invalid stream id.
54+
* @custom:then The call should revert with `OracleMiddlewareInvalidStreamId`.
55+
*/
56+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidStreamId() public {
57+
report.feedId = bytes32(uint256(EMPTY_STREAM_V3) | 1);
58+
(, payload) = _encodeReport(report);
59+
vm.expectRevert(OracleMiddlewareInvalidStreamId.selector);
60+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
61+
}
62+
63+
/**
64+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp
65+
* payload that lacks a target timestamp.
66+
* @custom:when The function is called.
67+
* @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`.
68+
*/
69+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithoutTargetTimestamp() public {
70+
report.validFromTimestamp = 0;
71+
(, payload) = _encodeReport(report);
72+
vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector);
73+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
74+
}
75+
76+
/**
77+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp
78+
* payload and a target timestamp that is lower than the report's `validFromTimestamp`.
79+
* @custom:when The function is called.
80+
* @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`.
81+
*/
82+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithTargetTimestampLtValidFromTimestamp()
83+
public
84+
{
85+
vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector);
86+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(
87+
payload, report.validFromTimestamp - 1, 0
88+
);
89+
}
90+
91+
/**
92+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp
93+
* payload and a target timestamp that is greater than the report's `observationsTimestamp`.
94+
* @custom:when The function is called.
95+
* @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`.
96+
*/
97+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithTargetTimestampGtObservationsTimestamp()
98+
public
99+
{
100+
vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector);
101+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(
102+
payload, report.observationsTimestamp + 1, 0
103+
);
104+
}
105+
106+
/**
107+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with an invalid timestamp
108+
* payload and a target limit that is lower than the report's `observationsTimestamp`.
109+
* @custom:when The function is called.
110+
* @custom:then The call should revert with `OracleMiddlewareDataStreamInvalidTimestamp`.
111+
*/
112+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithTargetLimitLtObservationsTimestamp()
113+
public
114+
{
115+
vm.expectRevert(OracleMiddlewareDataStreamInvalidTimestamp.selector);
116+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(
117+
payload, report.validFromTimestamp, report.observationsTimestamp - 1
118+
);
119+
}
120+
121+
/**
122+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a report price equal to zero.
123+
* @custom:when The function is called.
124+
* @custom:then The call should revert with `OracleMiddlewareWrongPrice`.
125+
*/
126+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithInvalidPrice() public {
127+
report.price = 0;
128+
(, payload) = _encodeReport(report);
129+
vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongPrice.selector, report.price));
130+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
131+
}
132+
133+
/**
134+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a report ask price equal to zero.
135+
* @custom:when The function is called.
136+
* @custom:then The call should revert with `OracleMiddlewareWrongAskPrice`.
137+
*/
138+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithInvalidAskPrice() public {
139+
report.ask = 0;
140+
(, payload) = _encodeReport(report);
141+
vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongAskPrice.selector, report.ask));
142+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
143+
}
144+
145+
/**
146+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function with a report bid price equal to zero.
147+
* @custom:when The function is called.
148+
* @custom:then The call should revert with `OracleMiddlewareWrongBidPrice`.
149+
*/
150+
function test_RevertWhen_getChainlinkDataStreamPriceInvalidTimestampWithInvalidBidPrice() public {
151+
report.bid = 0;
152+
(, payload) = _encodeReport(report);
153+
vm.expectRevert(abi.encodeWithSelector(OracleMiddlewareWrongBidPrice.selector, report.bid));
154+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
155+
}
156+
157+
/**
158+
* @custom:scenario Tests the `_getChainlinkDataStreamPrice` function.
159+
* @custom:when The function is called.
160+
* @custom:then The verified report must match the Chainlink data streams report.
161+
*/
162+
function test_getChainlinkDataStreamPrice() public {
163+
FormattedDataStreamsPrice memory formattedReport =
164+
oracleMiddleware.i_getChainlinkDataStreamPrice{ value: report.nativeFee }(payload, 0, 0);
165+
166+
assertEq(formattedReport.timestamp, report.observationsTimestamp, "Invalid observationsTimestamp");
167+
assertEq(int192(int256(formattedReport.price)), report.price, "Invalid price");
168+
assertEq(int192(int256(formattedReport.bid)), report.bid, "Invalid bid");
169+
assertEq(int192(int256(formattedReport.ask)), report.ask, "Invalid ask");
170+
}
171+
}

0 commit comments

Comments
 (0)