-
Notifications
You must be signed in to change notification settings - Fork 1
/
ValidatorProxy.sol
237 lines (215 loc) · 8.68 KB
/
ValidatorProxy.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./ConfirmedOwner.sol";
import "./interfaces/ILetAggregatorValidatorInterface.sol";
import "./interfaces/ILetTypeAndVersionInterface.sol";
contract ValidatorProxy is ILetAggregatorValidatorInterface, ILetTypeAndVersionInterface, ConfirmedOwner {
/// @notice Uses a single storage slot to store the current address
struct AggregatorConfiguration {
address target;
bool hasNewProposal;
}
struct ValidatorConfiguration {
ILetAggregatorValidatorInterface target;
bool hasNewProposal;
}
// Configuration for the current aggregator
AggregatorConfiguration private s_currentAggregator;
// Proposed aggregator address
address private s_proposedAggregator;
// Configuration for the current validator
ValidatorConfiguration private s_currentValidator;
// Proposed validator address
ILetAggregatorValidatorInterface private s_proposedValidator;
event AggregatorProposed(address indexed aggregator);
event AggregatorUpgraded(address indexed previous, address indexed current);
event ValidatorProposed(ILetAggregatorValidatorInterface indexed validator);
event ValidatorUpgraded(ILetAggregatorValidatorInterface indexed previous, ILetAggregatorValidatorInterface indexed current);
/// @notice The proposed aggregator called validate, but the call was not passed on to any validators
event ProposedAggregatorValidateCall(
address indexed proposed,
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
);
/**
* @notice Construct the ValidatorProxy with an aggregator and a validator
* @param aggregator address
* @param validator address
*/
constructor(address aggregator, ILetAggregatorValidatorInterface validator) ConfirmedOwner(msg.sender) {
s_currentAggregator = AggregatorConfiguration({target: aggregator, hasNewProposal: false});
s_currentValidator = ValidatorConfiguration({target: validator, hasNewProposal: false});
}
/**
* @notice Validate a transmission
* @dev Must be called by either the `s_currentAggregator.target`, or the `s_proposedAggregator`.
* If called by the `s_currentAggregator.target` this function passes the call on to the `s_currentValidator.target`
* and the `s_proposedValidator`, if it is set.
* If called by the `s_proposedAggregator` this function emits a `ProposedAggregatorValidateCall` to signal that
* the call was received.
* @dev To guard against external `validate` calls reverting, we use raw calls here.
* We favour `call` over try-catch to ensure that failures are avoided even if the validator address is incorrectly
* set as a non-contract address.
* @dev If the `aggregator` and `validator` are the same contract or collude, this could exhibit reentrancy behavior.
* However, since that contract would have to be explicitly written for reentrancy and that the `owner` would have
* to configure this contract to use that malicious contract, we refrain from using mutex or check here.
* @dev This does not perform any checks on any roundId, so it is possible that a validator receive different reports
* for the same roundId at different points in time. Validator implementations should be aware of this.
* @param previousRoundId uint256
* @param previousAnswer int256
* @param currentRoundId uint256
* @param currentAnswer int256
* @return bool
*/
function validate(
uint256 previousRoundId,
int256 previousAnswer,
uint256 currentRoundId,
int256 currentAnswer
) external override returns (bool) {
address currentAggregator = s_currentAggregator.target;
if (msg.sender != currentAggregator) {
address proposedAggregator = s_proposedAggregator;
require(msg.sender == proposedAggregator, "Not a configured aggregator");
// If the aggregator is still in proposed state, emit an event and don't push to any validator.
// This is to confirm that `validate` is being called prior to upgrade.
emit ProposedAggregatorValidateCall(
proposedAggregator,
previousRoundId,
previousAnswer,
currentRoundId,
currentAnswer
);
return true;
}
// Send the validate call to the current validator
ValidatorConfiguration memory currentValidator = s_currentValidator;
address currentValidatorAddress = address(currentValidator.target);
require(currentValidatorAddress != address(0), "No validator set");
currentValidatorAddress.call(
abi.encodeWithSelector(
ILetAggregatorValidatorInterface.validate.selector,
previousRoundId,
previousAnswer,
currentRoundId,
currentAnswer
)
);
// If there is a new proposed validator, send the validate call to that validator also
if (currentValidator.hasNewProposal) {
address(s_proposedValidator).call(
abi.encodeWithSelector(
ILetAggregatorValidatorInterface.validate.selector,
previousRoundId,
previousAnswer,
currentRoundId,
currentAnswer
)
);
}
return true;
}
/** AGGREGATOR CONFIGURATION FUNCTIONS **/
/**
* @notice Propose an aggregator
* @dev A zero address can be used to unset the proposed aggregator. Only owner can call.
* @param proposed address
*/
function proposeNewAggregator(address proposed) external onlyOwner {
require(s_proposedAggregator != proposed && s_currentAggregator.target != proposed, "Invalid proposal");
s_proposedAggregator = proposed;
// If proposed is zero address, hasNewProposal = false
s_currentAggregator.hasNewProposal = (proposed != address(0));
emit AggregatorProposed(proposed);
}
/**
* @notice Upgrade the aggregator by setting the current aggregator as the proposed aggregator.
* @dev Must have a proposed aggregator. Only owner can call.
*/
function upgradeAggregator() external onlyOwner {
// Get configuration in memory
AggregatorConfiguration memory current = s_currentAggregator;
address previous = current.target;
address proposed = s_proposedAggregator;
// Perform the upgrade
require(current.hasNewProposal, "No proposal");
s_currentAggregator = AggregatorConfiguration({target: proposed, hasNewProposal: false});
delete s_proposedAggregator;
emit AggregatorUpgraded(previous, proposed);
}
/**
* @notice Get aggregator details
* @return current address
* @return hasProposal bool
* @return proposed address
*/
function getAggregators()
external
view
returns (
address current,
bool hasProposal,
address proposed
)
{
current = s_currentAggregator.target;
hasProposal = s_currentAggregator.hasNewProposal;
proposed = s_proposedAggregator;
}
/** VALIDATOR CONFIGURATION FUNCTIONS **/
/**
* @notice Propose an validator
* @dev A zero address can be used to unset the proposed validator. Only owner can call.
* @param proposed address
*/
function proposeNewValidator(ILetAggregatorValidatorInterface proposed) external onlyOwner {
require(s_proposedValidator != proposed && s_currentValidator.target != proposed, "Invalid proposal");
s_proposedValidator = proposed;
// If proposed is zero address, hasNewProposal = false
s_currentValidator.hasNewProposal = (address(proposed) != address(0));
emit ValidatorProposed(proposed);
}
/**
* @notice Upgrade the validator by setting the current validator as the proposed validator.
* @dev Must have a proposed validator. Only owner can call.
*/
function upgradeValidator() external onlyOwner {
// Get configuration in memory
ValidatorConfiguration memory current = s_currentValidator;
ILetAggregatorValidatorInterface previous = current.target;
ILetAggregatorValidatorInterface proposed = s_proposedValidator;
// Perform the upgrade
require(current.hasNewProposal, "No proposal");
s_currentValidator = ValidatorConfiguration({target: proposed, hasNewProposal: false});
delete s_proposedValidator;
emit ValidatorUpgraded(previous, proposed);
}
/**
* @notice Get validator details
* @return current address
* @return hasProposal bool
* @return proposed address
*/
function getValidators()
external
view
returns (
ILetAggregatorValidatorInterface current,
bool hasProposal,
ILetAggregatorValidatorInterface proposed
)
{
current = s_currentValidator.target;
hasProposal = s_currentValidator.hasNewProposal;
proposed = s_proposedValidator;
}
/**
* @notice The type and version of this contract
* @return Type and version string
*/
function typeAndVersion() external pure virtual override returns (string memory) {
return "ValidatorProxy 1.0.0";
}
}