@@ -71,8 +71,8 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
71
71
72
72
bytes32 private constant ADMIN_ROLE = keccak256 ("ADMIN_ROLE " );
73
73
bytes32 private constant EXECUTOR_ROLE = keccak256 ("EXECUTOR_ROLE " );
74
- uint96 private constant DEFAULT_TOP_UP_AMOUNT_JULES = 9000000000000000000 ;
75
- uint96 private constant DEFAULT_MIN_BALANCE_JULES = 1000000000000000000 ;
74
+ uint96 private constant DEFAULT_TOP_UP_AMOUNT_JUELS = 9000000000000000000 ;
75
+ uint96 private constant DEFAULT_MIN_BALANCE_JUELS = 1000000000000000000 ;
76
76
IERC20 private immutable i_linkToken;
77
77
78
78
uint256 private s_minWaitPeriodSeconds;
@@ -142,14 +142,15 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
142
142
}
143
143
// s_onRampAddresses is not the same length as s_watchList, so it has
144
144
// to be clean in a separate loop
145
- for (uint256 idx = 0 ; idx < s_onRampAddresses.length (); idx++ ) {
146
- (uint256 key , ) = s_onRampAddresses.at (idx);
145
+ for (uint256 idx = s_onRampAddresses.length (); idx > 0 ; idx -- ) {
146
+ (uint256 key , ) = s_onRampAddresses.at (idx - 1 );
147
147
s_onRampAddresses.remove (key);
148
148
}
149
149
for (uint256 idx = 0 ; idx < addresses.length ; idx++ ) {
150
150
address targetAddress = addresses[idx];
151
151
if (s_targets[targetAddress].isActive) revert DuplicateAddress (targetAddress);
152
152
if (targetAddress == address (0 )) revert InvalidWatchList ();
153
+ if (minBalances[idx] == 0 ) revert InvalidWatchList ();
153
154
if (topUpAmounts[idx] == 0 ) revert InvalidWatchList ();
154
155
s_targets[targetAddress] = MonitoredAddress ({
155
156
isActive: true ,
@@ -173,6 +174,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
173
174
/// in which case it will carry the proper dstChainSelector along with the 0x0 address
174
175
function addToWatchListOrDecomission (address targetAddress , uint64 dstChainSelector ) public onlyAdminOrExecutor {
175
176
if (s_targets[targetAddress].isActive) revert DuplicateAddress (targetAddress);
177
+ if (targetAddress == address (0 ) && dstChainSelector == 0 ) revert InvalidAddress (targetAddress);
176
178
bool onRampExists = s_onRampAddresses.contains (dstChainSelector);
177
179
// if targetAddress is an existing onRamp, there's a need of cleaning the previous onRamp associated to this dstChainSelector
178
180
// there's no need to remove any other address that's not an onRamp
@@ -182,16 +184,19 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
182
184
}
183
185
// only add the new address if it's not 0x0
184
186
if (targetAddress != address (0 )) {
185
- s_onRampAddresses.set (dstChainSelector, targetAddress);
186
187
s_targets[targetAddress] = MonitoredAddress ({
187
188
isActive: true ,
188
- minBalance: DEFAULT_MIN_BALANCE_JULES ,
189
- topUpAmount: DEFAULT_TOP_UP_AMOUNT_JULES ,
189
+ minBalance: DEFAULT_MIN_BALANCE_JUELS ,
190
+ topUpAmount: DEFAULT_TOP_UP_AMOUNT_JUELS ,
190
191
lastTopUpTimestamp: 0
191
192
});
192
193
s_watchList.add (targetAddress);
193
- } else {
194
- // if the address is 0x0, it means the onRamp has ben decomissioned and has to be cleaned
194
+ // add the contract to onRampAddresses if it carries a valid dstChainSelector
195
+ if (dstChainSelector > 0 ) {
196
+ s_onRampAddresses.set (dstChainSelector, targetAddress);
197
+ }
198
+ // else if is refundant as this is the only corner case left, maintaining it for legibility
199
+ } else if (targetAddress == address (0 ) && dstChainSelector > 0 ) {
195
200
s_onRampAddresses.remove (dstChainSelector);
196
201
}
197
202
}
@@ -219,16 +224,24 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
219
224
uint256 idx = uint256 (blockhash (block .number - (block .number % s_upkeepInterval) - 1 )) % numTargets;
220
225
uint256 numToCheck = numTargets < maxCheck ? numTargets : maxCheck;
221
226
uint256 numFound = 0 ;
227
+ uint256 minWaitPeriod = s_minWaitPeriodSeconds;
222
228
address [] memory targetsToFund = new address [](maxPerform);
223
- MonitoredAddress memory target ;
229
+ MonitoredAddress memory contractToFund ;
224
230
for (
225
231
uint256 numChecked = 0 ;
226
232
numChecked < numToCheck;
227
233
(idx, numChecked) = ((idx + 1 ) % numTargets, numChecked + 1 )
228
234
) {
229
235
address targetAddress = s_watchList.at (idx);
230
- target = s_targets[targetAddress];
231
- if (_needsFunding (targetAddress, target.minBalance)) {
236
+ contractToFund = s_targets[targetAddress];
237
+ if (
238
+ _needsFunding (
239
+ targetAddress,
240
+ contractToFund.lastTopUpTimestamp + minWaitPeriod,
241
+ contractToFund.minBalance,
242
+ contractToFund.isActive
243
+ )
244
+ ) {
232
245
targetsToFund[numFound] = targetAddress;
233
246
numFound++ ;
234
247
if (numFound == maxPerform) {
@@ -247,15 +260,24 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
247
260
/// @notice tries to fund an array of target addresses, checking if they're underfunded in the process
248
261
/// @param targetAddresses is an array of contract addresses to be funded in case they're underfunded
249
262
function topUp (address [] memory targetAddresses ) public whenNotPaused {
250
- MonitoredAddress memory target;
263
+ MonitoredAddress memory contractToFund;
264
+ uint256 minWaitPeriod = s_minWaitPeriodSeconds;
251
265
uint256 localBalance = i_linkToken.balanceOf (address (this ));
252
266
for (uint256 idx = 0 ; idx < targetAddresses.length ; idx++ ) {
253
267
address targetAddress = targetAddresses[idx];
254
- target = s_targets[targetAddress];
255
- if (localBalance >= target.topUpAmount && _needsFunding (targetAddress, target.minBalance)) {
256
- bool success = i_linkToken.transfer (targetAddress, target.topUpAmount);
268
+ contractToFund = s_targets[targetAddress];
269
+ if (
270
+ localBalance >= contractToFund.topUpAmount &&
271
+ _needsFunding (
272
+ targetAddress,
273
+ contractToFund.lastTopUpTimestamp + minWaitPeriod,
274
+ contractToFund.minBalance,
275
+ contractToFund.isActive
276
+ )
277
+ ) {
278
+ bool success = i_linkToken.transfer (targetAddress, contractToFund.topUpAmount);
257
279
if (success) {
258
- localBalance -= target .topUpAmount;
280
+ localBalance -= contractToFund .topUpAmount;
259
281
s_targets[targetAddress].lastTopUpTimestamp = uint56 (block .timestamp );
260
282
emit TopUpSucceeded (targetAddress);
261
283
} else {
@@ -271,30 +293,32 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
271
293
/// if it is elligible for funding
272
294
/// @param targetAddress the target to check
273
295
/// @param minBalance minimum balance required for the target
296
+ /// @param minWaitPeriodPassed the minimum wait period (target lastTopUpTimestamp + minWaitPeriod)
274
297
/// @return bool whether the target needs funding or not
275
- function _needsFunding (address targetAddress , uint256 minBalance ) private view returns (bool ) {
298
+ function _needsFunding (
299
+ address targetAddress ,
300
+ uint256 minWaitPeriodPassed ,
301
+ uint256 minBalance ,
302
+ bool contractIsActive
303
+ ) private view returns (bool ) {
276
304
// Explicitly check if the targetAddress is the zero address
277
305
// or if it's not a contract. In both cases return with false,
278
306
// to prevent target.linkAvailableForPayment from running,
279
307
// which would revert the operation.
280
308
if (targetAddress == address (0 ) || targetAddress.code.length == 0 ) {
281
309
return false ;
282
310
}
283
- MonitoredAddress memory addressToCheck = s_targets[targetAddress];
284
311
ILinkAvailable target;
285
312
IAggregatorProxy proxy = IAggregatorProxy (targetAddress);
286
313
try proxy.aggregator () returns (address aggregatorAddress ) {
314
+ // proxy.aggregator() can return a 0 address if the address is not an aggregator
287
315
if (aggregatorAddress == address (0 )) return false ;
288
316
target = ILinkAvailable (aggregatorAddress);
289
317
} catch {
290
318
target = ILinkAvailable (targetAddress);
291
319
}
292
320
try target.linkAvailableForPayment () returns (int256 balance ) {
293
- if (
294
- balance < int256 (minBalance) &&
295
- addressToCheck.lastTopUpTimestamp + s_minWaitPeriodSeconds <= block .timestamp &&
296
- addressToCheck.isActive
297
- ) {
321
+ if (balance < int256 (minBalance) && minWaitPeriodPassed <= block .timestamp && contractIsActive) {
298
322
return true ;
299
323
}
300
324
} catch {}
@@ -408,9 +432,9 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter
408
432
/// @notice Gets configuration information for an address on the watchlist
409
433
function getAccountInfo (
410
434
address targetAddress
411
- ) external view returns (bool isActive , uint256 minBalance , uint256 topUpAmount ) {
435
+ ) external view returns (bool isActive , uint96 minBalance , uint96 topUpAmount , uint56 lastTopUpTimestamp ) {
412
436
MonitoredAddress memory target = s_targets[targetAddress];
413
- return (target.isActive, target.minBalance, target.topUpAmount);
437
+ return (target.isActive, target.minBalance, target.topUpAmount, target.lastTopUpTimestamp );
414
438
}
415
439
416
440
/// @dev Modifier to make a function callable only by executor role or the
0 commit comments