Skip to content

Commit 38dae01

Browse files
bowdm-chrzanchapati23nvtaverasbaroooo
authored
Feat: Make ChainlinkRelayer support implicit pairs (#487)
### Description Late last week it became apparent that Chainlink won't provide all derived rates that we need, and that we will need to compute implicit rates on-chain. This PR extends the relayer such that it can aggregate up to 4 chainlink date feeds into a single price. Each data feed can be inverted (1 / p) to make the dominos line up. ### Other changes Upgraded the tests to solidity 0.8 and fixed the issue with deploying SortedOracles via the factory by pre-deploying libraries as well because then factory links them automatically when calling `vm.getCode`. ### Tested Tests have been extended, and I've also used a slightly new pattern that I didn't use before which I like, and might not be evident from reading the code. Specifically for the relayer we have 4 scenarios depending on how many price feeds it aggregates, so I wrote test contracts that extend each other in such a way where test cases from N-1 price feeds get executed also in the test for N price feeds. ### Related issues N/A ### Backwards compatibility Not backwards compatible but not deployed yet. ### Documentation N/A --------- Co-authored-by: Marcin Chrzanowski <[email protected]> Co-authored-by: Martin <[email protected]> Co-authored-by: chapati <[email protected]> Co-authored-by: Nelson Taveras <[email protected]> Co-authored-by: baroooo <[email protected]>
1 parent 31e5f01 commit 38dae01

15 files changed

+1254
-270
lines changed

.gitmodules

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,7 @@
1919
[submodule "lib/foundry-chainlink-toolkit"]
2020
path = lib/foundry-chainlink-toolkit
2121
url = https://github.com/smartcontractkit/foundry-chainlink-toolkit
22+
[submodule "lib/prb-math"]
23+
branch = "release-v4"
24+
path = lib/prb-math
25+
url = https://github.com/PaulRBerg/prb-math
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,25 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22
pragma solidity >=0.5.13 <0.9;
3+
pragma experimental ABIEncoderV2;
34

45
interface IChainlinkRelayer {
6+
/**
7+
* @notice Struct used to represent a segment in the price path.
8+
* @member aggregator The address of the Chainlink aggregator.
9+
* @member invert Wether to invert the aggregator's price feed, i.e. convert CELO/USD to USD/CELO.
10+
*/
11+
struct ChainlinkAggregator {
12+
address aggregator;
13+
bool invert;
14+
}
15+
516
function rateFeedId() external returns (address);
617

18+
function rateFeedDescription() external returns (string memory);
19+
720
function sortedOracles() external returns (address);
821

9-
function chainlinkAggregator() external returns (address);
22+
function getAggregators() external returns (ChainlinkAggregator[] memory);
1023

1124
function relay() external;
1225
}

contracts/interfaces/IChainlinkRelayerFactory.sol

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
// SPDX-License-Identifier: GPL-3.0-or-later
22
pragma solidity >=0.5.13 <0.9;
3+
pragma experimental ABIEncoderV2;
4+
5+
import "./IChainlinkRelayer.sol";
36

47
interface IChainlinkRelayerFactory {
58
/**
69
* @notice Emitted when a relayer is deployed.
710
* @param relayerAddress Address of the newly deployed relayer.
811
* @param rateFeedId Rate feed ID for which the relayer will report.
9-
* @param chainlinkAggregator Address of the Chainlink aggregator the relayer will fetch prices from.
12+
* @param rateFeedDescription Human-readable rate feed, which the relayer will report on, i.e. "CELO/USD"
13+
* @param aggregators List of ChainlinkAggregator that the relayer chains.
1014
*/
1115
event RelayerDeployed(
1216
address indexed relayerAddress,
1317
address indexed rateFeedId,
14-
address indexed chainlinkAggregator
18+
string rateFeedDescription,
19+
IChainlinkRelayer.ChainlinkAggregator[] aggregators
1520
);
1621

1722
/**
@@ -25,15 +30,27 @@ interface IChainlinkRelayerFactory {
2530

2631
function sortedOracles() external returns (address);
2732

28-
function deployRelayer(address rateFeedId, address chainlinkAggregator) external returns (address);
33+
function deployRelayer(
34+
address rateFeedId,
35+
string calldata rateFeedDescription,
36+
IChainlinkRelayer.ChainlinkAggregator[] calldata aggregators
37+
) external returns (address);
2938

3039
function removeRelayer(address rateFeedId) external;
3140

32-
function redeployRelayer(address rateFeedId, address chainlinkAggregator) external returns (address);
41+
function redeployRelayer(
42+
address rateFeedId,
43+
string calldata rateFeedDescription,
44+
IChainlinkRelayer.ChainlinkAggregator[] calldata aggregators
45+
) external returns (address);
3346

3447
function getRelayer(address rateFeedId) external view returns (address);
3548

3649
function getRelayers() external view returns (address[] memory);
3750

38-
function computedRelayerAddress(address rateFeedId, address chainlinkAggregator) external returns (address);
51+
function computedRelayerAddress(
52+
address rateFeedId,
53+
string calldata rateFeedDescription,
54+
IChainlinkRelayer.ChainlinkAggregator[] calldata aggregators
55+
) external returns (address);
3956
}

contracts/oracles/ChainlinkRelayerFactory.sol

Lines changed: 36 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pragma solidity 0.8.18;
33

44
import { OwnableUpgradeable } from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
55
import { ChainlinkRelayerV1 } from "./ChainlinkRelayerV1.sol";
6+
import { IChainlinkRelayer } from "../interfaces/IChainlinkRelayer.sol";
67
import { IChainlinkRelayerFactory } from "../interfaces/IChainlinkRelayerFactory.sol";
78

89
/**
@@ -26,9 +27,8 @@ contract ChainlinkRelayerFactory is IChainlinkRelayerFactory, OwnableUpgradeable
2627
* @notice Thrown when trying to deploy a relayer to an address that already has code.
2728
* @param contractAddress Address at which the relayer could not be deployed.
2829
* @param rateFeedId Rate feed ID for which the relayer would have reported.
29-
* @param chainlinkAggregator Address of the Chainlink aggregator the relayer would have fetched prices from.
3030
*/
31-
error ContractAlreadyExists(address contractAddress, address rateFeedId, address chainlinkAggregator);
31+
error ContractAlreadyExists(address contractAddress, address rateFeedId);
3232

3333
/**
3434
* @notice Thrown when trying to deploy a relayer for a rate feed ID that already has a relayer.
@@ -77,25 +77,33 @@ contract ChainlinkRelayerFactory is IChainlinkRelayerFactory, OwnableUpgradeable
7777
/**
7878
* @notice Deploys a new relayer contract.
7979
* @param rateFeedId The rate feed ID for which the relayer will report.
80-
* @param chainlinkAggregator The Chainlink aggregator from which the relayer will fetch prices.
80+
* @param rateFeedDescription Human-readable rate feed, which the relayer will report on, i.e. "CELO/USD".
81+
* @param aggregators Array of ChainlinkAggregator structs defining the price path.
82+
* See contract-level @dev comment in the ChainlinkRelayerV1 contract,
83+
* for an explanation on price paths.
8184
* @return relayerAddress The address of the newly deployed relayer contract.
8285
*/
8386
function deployRelayer(
8487
address rateFeedId,
85-
address chainlinkAggregator
88+
string calldata rateFeedDescription,
89+
IChainlinkRelayer.ChainlinkAggregator[] calldata aggregators
8690
) public onlyOwner returns (address relayerAddress) {
87-
address expectedAddress = computedRelayerAddress(rateFeedId, chainlinkAggregator);
88-
89-
if (address(deployedRelayers[rateFeedId]) == expectedAddress || expectedAddress.code.length > 0) {
90-
revert ContractAlreadyExists(expectedAddress, rateFeedId, chainlinkAggregator);
91-
}
92-
9391
if (address(deployedRelayers[rateFeedId]) != address(0)) {
9492
revert RelayerForFeedExists(rateFeedId);
9593
}
9694

95+
address expectedAddress = computedRelayerAddress(rateFeedId, rateFeedDescription, aggregators);
96+
if (expectedAddress.code.length > 0) {
97+
revert ContractAlreadyExists(expectedAddress, rateFeedId);
98+
}
99+
97100
bytes32 salt = _getSalt();
98-
ChainlinkRelayerV1 relayer = new ChainlinkRelayerV1{ salt: salt }(rateFeedId, sortedOracles, chainlinkAggregator);
101+
ChainlinkRelayerV1 relayer = new ChainlinkRelayerV1{ salt: salt }(
102+
rateFeedId,
103+
rateFeedDescription,
104+
sortedOracles,
105+
aggregators
106+
);
99107

100108
if (address(relayer) != expectedAddress) {
101109
revert UnexpectedAddress(expectedAddress, address(relayer));
@@ -104,7 +112,7 @@ contract ChainlinkRelayerFactory is IChainlinkRelayerFactory, OwnableUpgradeable
104112
deployedRelayers[rateFeedId] = relayer;
105113
rateFeeds.push(rateFeedId);
106114

107-
emit RelayerDeployed(address(relayer), rateFeedId, chainlinkAggregator);
115+
emit RelayerDeployed(address(relayer), rateFeedId, rateFeedDescription, aggregators);
108116

109117
return address(relayer);
110118
}
@@ -139,32 +147,34 @@ contract ChainlinkRelayerFactory is IChainlinkRelayerFactory, OwnableUpgradeable
139147
* @notice Removes the current relayer and redeploys a new one with a different
140148
* Chainlink aggregator (and/or different bytecode if the factory
141149
* has been upgraded since the last deployment of the relayer).
142-
* @param rateFeedId The rate feed for which the relayer should be redeployed.
143-
* @param chainlinkAggregator Address of the Chainlink aggregator the new relayer version will fetch prices from.
150+
* @param rateFeedId The rate feed ID for which the relayer will report.
151+
* @param rateFeedDescription Human-readable rate feed, which the relayer will report on, i.e. "CELO/USD".
152+
* @param aggregators Array of ChainlinkAggregator structs defining the price path.
144153
* @return relayerAddress The address of the newly deployed relayer contract.
145154
*/
146155
function redeployRelayer(
147156
address rateFeedId,
148-
address chainlinkAggregator
157+
string calldata rateFeedDescription,
158+
IChainlinkRelayer.ChainlinkAggregator[] calldata aggregators
149159
) external onlyOwner returns (address relayerAddress) {
150160
removeRelayer(rateFeedId);
151-
return deployRelayer(rateFeedId, chainlinkAggregator);
161+
return deployRelayer(rateFeedId, rateFeedDescription, aggregators);
152162
}
153163

154164
/**
155165
* @notice Returns the address of the currently deployed relayer for a given rate feed ID.
156166
* @param rateFeedId The rate feed ID whose relayer we want to get.
157167
* @return relayerAddress Address of the relayer contract.
158168
*/
159-
function getRelayer(address rateFeedId) public view returns (address relayerAddress) {
169+
function getRelayer(address rateFeedId) external view returns (address relayerAddress) {
160170
return address(deployedRelayers[rateFeedId]);
161171
}
162172

163173
/**
164174
* @notice Returns a list of all currently deployed relayers.
165175
* @return relayerAddresses An array of all relayer contract addresses.
166176
*/
167-
function getRelayers() public view returns (address[] memory relayerAddresses) {
177+
function getRelayers() external view returns (address[] memory relayerAddresses) {
168178
address[] memory relayers = new address[](rateFeeds.length);
169179
for (uint256 i = 0; i < rateFeeds.length; i++) {
170180
relayers[i] = address(deployedRelayers[rateFeeds[i]]);
@@ -186,10 +196,15 @@ contract ChainlinkRelayerFactory is IChainlinkRelayerFactory, OwnableUpgradeable
186196
/**
187197
* @notice Computes the expected CREATE2 address for given relayer parameters.
188198
* @param rateFeedId The rate feed ID.
189-
* @param chainlinkAggregator Address of the Chainlink aggregator.
199+
* @param rateFeedDescription The human readable description of the reported rate feed.
200+
* @param aggregators Array of ChainlinkAggregator structs defining the price path.
190201
* @dev See https://eips.ethereum.org/EIPS/eip-1014.
191202
*/
192-
function computedRelayerAddress(address rateFeedId, address chainlinkAggregator) public view returns (address) {
203+
function computedRelayerAddress(
204+
address rateFeedId,
205+
string calldata rateFeedDescription,
206+
IChainlinkRelayer.ChainlinkAggregator[] calldata aggregators
207+
) public view returns (address) {
193208
bytes32 salt = _getSalt();
194209
return
195210
address(
@@ -203,7 +218,7 @@ contract ChainlinkRelayerFactory is IChainlinkRelayerFactory, OwnableUpgradeable
203218
keccak256(
204219
abi.encodePacked(
205220
type(ChainlinkRelayerV1).creationCode,
206-
abi.encode(rateFeedId, sortedOracles, chainlinkAggregator)
221+
abi.encode(rateFeedId, rateFeedDescription, sortedOracles, aggregators)
207222
)
208223
)
209224
)

0 commit comments

Comments
 (0)