From 35b3a47a8619dbcead53766bcbc90cf4222c28e0 Mon Sep 17 00:00:00 2001 From: poke1994 <139136266+poke1994@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:22:41 +0100 Subject: [PATCH 1/6] Add files via upload Adding 2 files. 1. I added a minPerform() function to the LinkBalMonV2 contract and tested it on Sepolia. The purpose of the minPerform function is to establish a minimum batch size required for the sampleUnderfunded address array. If the array size is less than minPerform, no top-up operation will be performed. This optimization is crucial for deploying the BalanceMonitor contract on Ethereum, which consumes significant gas. 2. Creation of a CCIPOnRampWatchlistUpdate contract to allow auto update of the watchlist on the LinkBalMonV2 when deployed to fund CCIP On-Ramps Testing File Link - https://docs.google.com/spreadsheets/d/1zk4J4xQrhSJhIEt7l1bpsG6_jrl6qTQLNa5EZia3JzU/edit?gid=829494086#gid=829494086 Specification File - https://docs.google.com/document/d/1p4naJOlrdPmZ5xsZkZE7FDw1cEQqWcQ3hgguhI21SNQ/edit#heading=h.3t9vh460y192 --- .../automation/upkeeps/CCIPAutoWatchlist.sol | 94 +++++++++++++++++++ .../upkeeps/LinkAvailableBalanceMonitor.sol | 20 +++- 2 files changed, 111 insertions(+), 3 deletions(-) create mode 100644 contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol diff --git a/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol b/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol new file mode 100644 index 00000000000..1ce844a7ad7 --- /dev/null +++ b/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.19; + +import "../../shared/access/ConfirmedOwner.sol"; +import {AutomationCompatibleInterface} from "../interfaces/AutomationCompatibleInterface.sol"; +import {AccessControl} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/access/AccessControl.sol"; +import {LinkAvailableBalanceMonitor} from "../upkeeps/LinkAvailableBalanceMonitor.sol"; + + +struct Log { + uint256 index; // Index of the log in the block + uint256 timestamp; // Timestamp of the block containing the log + bytes32 txHash; // Hash of the transaction containing the log + uint256 blockNumber; // Number of the block containing the log + bytes32 blockHash; // Hash of the block containing the log + address source; // Address of the contract that emitted the log + bytes32[] topics; // Indexed topics of the log + bytes data; // Data of the log +} + +interface ILogAutomation { + function checkLog( + Log calldata log, + bytes memory checkData + ) external returns (bool upkeepNeeded, bytes memory performData); + + function performUpkeep(bytes calldata performData) external; +} + + contract CCIPOnRampAutoWatchlist is ILogAutomation,ConfirmedOwner { + + + event setWatchlistOnMonitor(uint64 indexed _dstChainSelector,address indexed _onRamp); + + LinkAvailableBalanceMonitor public LinkAvailableBalanceMonitorAddress; + address public routerAddress; + + + constructor(address contractaddress,address _routerAddress) ConfirmedOwner(msg.sender){ + LinkAvailableBalanceMonitorAddress = LinkAvailableBalanceMonitor(contractaddress); + routerAddress = _routerAddress; + } + + // Option to change the Balance Monitor Address + function setBalanceMonitorAddress(address _newBalMonAddress) external onlyOwner{ + LinkAvailableBalanceMonitorAddress = LinkAvailableBalanceMonitor(_newBalMonAddress); + } + + // Option to change the Router Address + // Changing Router Address would also require setting up new Upkeep as Upkeep is linked with the Router and cannot be changed + function setRouterAddress(address _newRouterAddress) external onlyOwner{ + routerAddress = _newRouterAddress; + } + + function updateWatchList(address _targetAddress, uint64 _dstChainSelector) internal{ + LinkAvailableBalanceMonitorAddress.addToWatchListOrDecommission(_targetAddress, _dstChainSelector); + } + + function checkLog( + Log calldata log, + bytes memory checkData +) external view override returns (bool upkeepNeeded, bytes memory performData) { + + // Ensure Router address is set + require(routerAddress != address(0), "Router address not set"); + + // Define the event signature for OnRampSet(uint64,address) + bytes32 eventSignature = keccak256(abi.encodePacked("OnRampSet(uint64,address)")); + + // Check if the log source matches router contract and topics contain the event signature + if (log.source == routerAddress && log.topics.length > 0 && log.topics[0] == eventSignature) { + // Extract the indexed parameter from the log + uint64 destChainSelector = uint64(uint256(log.topics[1])); // cast to uint64 + address onRamp = address(uint160(uint256(bytes32(log.data)))); // extract from log.data + + // Determine if upkeep is needed based on the emitted log + // For simplicity, always return true to trigger performUpkeep + return (true, abi.encode(destChainSelector, onRamp)); + } + + // If the event signature doesn't match or log source is not Router contract, no upkeep is needed + return (false, ""); +} + + + function performUpkeep(bytes memory performData) external override{ + // Decode the data received from checkLog + (uint64 destChainSelector, address onRamp) = abi.decode(performData, (uint64, address)); + // Perform the necessary upkeep actions based on the decoded data + updateWatchList(onRamp,destChainSelector); + emit setWatchlistOnMonitor(destChainSelector,onRamp); + } +} \ No newline at end of file diff --git a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol index 5d8a8d58c8d..7addc48bafc 100644 --- a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol @@ -35,7 +35,7 @@ interface ILinkAvailable { /// this is a "trustless" upkeep, meaning it does not trust the caller of performUpkeep; /// we could save a fair amount of gas and re-write this upkeep for use with Automation v2.0+, /// which has significantly different trust assumptions -contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInterface, Pausable { +contract LinkAvailableBalanceMonitor2 is AccessControl, AutomationCompatibleInterface, Pausable { using EnumerableMap for EnumerableMap.UintToAddressMap; using EnumerableSet for EnumerableSet.AddressSet; @@ -44,6 +44,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter event UpkeepIntervalSet(uint256 oldUpkeepInterval, uint256 newUpkeepInterval); event MaxCheckSet(uint256 oldMaxCheck, uint256 newMaxCheck); event MaxPerformSet(uint256 oldMaxPerform, uint256 newMaxPerform); + event MinPerformSet(uint256 oldMinPerform, uint256 newMinPerform); event MinWaitPeriodSet(uint256 s_minWaitPeriodSeconds, uint256 minWaitPeriodSeconds); event TopUpBlocked(address indexed topUpAddress); event TopUpFailed(address indexed recipient); @@ -77,6 +78,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter uint256 private s_minWaitPeriodSeconds; uint16 private s_maxPerform; + uint16 private s_minPerform; uint16 private s_maxCheck; uint8 private s_upkeepInterval; @@ -105,6 +107,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter IERC20 linkToken, uint256 minWaitPeriodSeconds, uint16 maxPerform, + uint16 minPerform, uint16 maxCheck, uint8 upkeepInterval ) { @@ -333,7 +336,8 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter bytes calldata ) external view override whenNotPaused returns (bool upkeepNeeded, bytes memory performData) { address[] memory needsFunding = sampleUnderfundedAddresses(); - if (needsFunding.length == 0) { + uint16 minPerform = s_minPerform; + if (needsFunding.length <= minPerform) { return (false, ""); } uint96 total_batch_balance; @@ -389,6 +393,12 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter s_maxPerform = maxPerform; } + /// @notice Update s_minPerform + function setMinPerform(uint16 minPerform) public onlyRole(ADMIN_ROLE) { + emit MinPerformSet(s_minPerform, minPerform); + s_minPerform = minPerform; + } + /// @notice Update s_maxCheck function setMaxCheck(uint16 maxCheck) public onlyRole(ADMIN_ROLE) { emit MaxCheckSet(s_maxCheck, maxCheck); @@ -412,6 +422,10 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter function getMaxPerform() external view returns (uint16) { return s_maxPerform; } + /// @notice Gets minPerform + function getMinPerform() external view returns (uint16) { + return s_minPerform; + } /// @notice Gets maxCheck function getMaxCheck() external view returns (uint16) { @@ -466,4 +480,4 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter function unpause() external onlyRole(ADMIN_ROLE) { _unpause(); } -} +} \ No newline at end of file From 57ac5232dce1b5c245c6caf54373442bd03b4411 Mon Sep 17 00:00:00 2001 From: poke1994 <139136266+poke1994@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:23:46 +0100 Subject: [PATCH 2/6] Update LinkAvailableBalanceMonitor.sol --- .../v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol index 7addc48bafc..fe2b80c8daf 100644 --- a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol @@ -35,7 +35,7 @@ interface ILinkAvailable { /// this is a "trustless" upkeep, meaning it does not trust the caller of performUpkeep; /// we could save a fair amount of gas and re-write this upkeep for use with Automation v2.0+, /// which has significantly different trust assumptions -contract LinkAvailableBalanceMonitor2 is AccessControl, AutomationCompatibleInterface, Pausable { +contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInterface, Pausable { using EnumerableMap for EnumerableMap.UintToAddressMap; using EnumerableSet for EnumerableSet.AddressSet; @@ -480,4 +480,4 @@ contract LinkAvailableBalanceMonitor2 is AccessControl, AutomationCompatibleInte function unpause() external onlyRole(ADMIN_ROLE) { _unpause(); } -} \ No newline at end of file +} From 85fcb70fd02e589a45d295e636d73c014093bfea Mon Sep 17 00:00:00 2001 From: poke1994 <139136266+poke1994@users.noreply.github.com> Date: Tue, 18 Jun 2024 14:27:06 +0100 Subject: [PATCH 3/6] Update LinkAvailableBalanceMonitor.sol --- .../src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol index fe2b80c8daf..2d79e613ba3 100644 --- a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol @@ -117,6 +117,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter i_linkToken = linkToken; setMinWaitPeriodSeconds(minWaitPeriodSeconds); setMaxPerform(maxPerform); + setMinPerform(minPerform); setMaxCheck(maxCheck); setUpkeepInterval(upkeepInterval); } From 0b034e380d54e82e650622ac58a8d5d84e74b420 Mon Sep 17 00:00:00 2001 From: poke1994 <139136266+poke1994@users.noreply.github.com> Date: Wed, 19 Jun 2024 19:53:52 +0100 Subject: [PATCH 4/6] Update LinkAvailableBalanceMonitor.sol Made changed as per Felix's comments --- .../automation/upkeeps/LinkAvailableBalanceMonitor.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol index 2d79e613ba3..ad64c3e8d0e 100644 --- a/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol +++ b/contracts/src/v0.8/automation/upkeeps/LinkAvailableBalanceMonitor.sol @@ -337,8 +337,7 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter bytes calldata ) external view override whenNotPaused returns (bool upkeepNeeded, bytes memory performData) { address[] memory needsFunding = sampleUnderfundedAddresses(); - uint16 minPerform = s_minPerform; - if (needsFunding.length <= minPerform) { + if (needsFunding.length <= s_minPerform) { return (false, ""); } uint96 total_batch_balance; @@ -395,9 +394,9 @@ contract LinkAvailableBalanceMonitor is AccessControl, AutomationCompatibleInter } /// @notice Update s_minPerform - function setMinPerform(uint16 minPerform) public onlyRole(ADMIN_ROLE) { - emit MinPerformSet(s_minPerform, minPerform); + function setMinPerform(uint16 minPerform) external onlyRole(ADMIN_ROLE) { s_minPerform = minPerform; + emit MinPerformSet(s_minPerform, minPerform); } /// @notice Update s_maxCheck From d2f2a6326355d4e4b72e1c1dbfb77063061d056a Mon Sep 17 00:00:00 2001 From: poke1994 <139136266+poke1994@users.noreply.github.com> Date: Fri, 21 Jun 2024 17:34:02 +0100 Subject: [PATCH 5/6] Update CCIPAutoWatchlist.sol --- .../automation/upkeeps/CCIPAutoWatchlist.sol | 108 +++++++----------- 1 file changed, 43 insertions(+), 65 deletions(-) diff --git a/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol b/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol index 1ce844a7ad7..86f6853d032 100644 --- a/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol +++ b/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol @@ -6,89 +6,67 @@ import "../../shared/access/ConfirmedOwner.sol"; import {AutomationCompatibleInterface} from "../interfaces/AutomationCompatibleInterface.sol"; import {AccessControl} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/access/AccessControl.sol"; import {LinkAvailableBalanceMonitor} from "../upkeeps/LinkAvailableBalanceMonitor.sol"; +import "../interfaces/ILogAutomation.sol"; +contract CCIPOnRampAutoWatchlist is ILogAutomation, ConfirmedOwner { + event setWatchlistOnMonitor(uint64 indexed _dstChainSelector, address indexed _onRamp); -struct Log { - uint256 index; // Index of the log in the block - uint256 timestamp; // Timestamp of the block containing the log - bytes32 txHash; // Hash of the transaction containing the log - uint256 blockNumber; // Number of the block containing the log - bytes32 blockHash; // Hash of the block containing the log - address source; // Address of the contract that emitted the log - bytes32[] topics; // Indexed topics of the log - bytes data; // Data of the log -} - -interface ILogAutomation { - function checkLog( - Log calldata log, - bytes memory checkData - ) external returns (bool upkeepNeeded, bytes memory performData); - - function performUpkeep(bytes calldata performData) external; -} - - contract CCIPOnRampAutoWatchlist is ILogAutomation,ConfirmedOwner { - - - event setWatchlistOnMonitor(uint64 indexed _dstChainSelector,address indexed _onRamp); - - LinkAvailableBalanceMonitor public LinkAvailableBalanceMonitorAddress; - address public routerAddress; + LinkAvailableBalanceMonitor public linkAvailableBalanceMonitorAddress; + address public routerAddress; + // keccak256 signature for OnRampSet event set as constant + bytes32 private constant EVENT_SIGNATURE = 0x1f7d0ec248b80e5c0dde0ee531c4fc8fdb6ce9a2b3d90f560c74acd6a7202f23; - constructor(address contractaddress,address _routerAddress) ConfirmedOwner(msg.sender){ - LinkAvailableBalanceMonitorAddress = LinkAvailableBalanceMonitor(contractaddress); - routerAddress = _routerAddress; - } + constructor(address contractaddress, address _routerAddress) ConfirmedOwner(msg.sender) { + linkAvailableBalanceMonitorAddress = LinkAvailableBalanceMonitor(contractaddress); + routerAddress = _routerAddress; + } - // Option to change the Balance Monitor Address - function setBalanceMonitorAddress(address _newBalMonAddress) external onlyOwner{ - LinkAvailableBalanceMonitorAddress = LinkAvailableBalanceMonitor(_newBalMonAddress); - } + // Option to change the Balance Monitor Address + function setBalanceMonitorAddress(address _newBalMonAddress) external onlyOwner { + linkAvailableBalanceMonitorAddress = LinkAvailableBalanceMonitor(_newBalMonAddress); + } - // Option to change the Router Address - // Changing Router Address would also require setting up new Upkeep as Upkeep is linked with the Router and cannot be changed - function setRouterAddress(address _newRouterAddress) external onlyOwner{ - routerAddress = _newRouterAddress; - } + // Option to change the Router Address + // Changing Router Address would also require setting up new Upkeep as Upkeep is linked with the Router and cannot be changed + function setRouterAddress(address _newRouterAddress) external onlyOwner { + routerAddress = _newRouterAddress; + } - function updateWatchList(address _targetAddress, uint64 _dstChainSelector) internal{ - LinkAvailableBalanceMonitorAddress.addToWatchListOrDecommission(_targetAddress, _dstChainSelector); - } + function updateWatchList(address _targetAddress, uint64 _dstChainSelector) internal { + linkAvailableBalanceMonitorAddress.addToWatchListOrDecommission(_targetAddress, _dstChainSelector); + } - function checkLog( + function checkLog( Log calldata log, bytes memory checkData -) external view override returns (bool upkeepNeeded, bytes memory performData) { - + ) external view override returns (bool upkeepNeeded, bytes memory performData) { // Ensure Router address is set require(routerAddress != address(0), "Router address not set"); // Define the event signature for OnRampSet(uint64,address) - bytes32 eventSignature = keccak256(abi.encodePacked("OnRampSet(uint64,address)")); + //bytes32 eventSignature = keccak256(abi.encodePacked("OnRampSet(uint64,address)")); // Check if the log source matches router contract and topics contain the event signature - if (log.source == routerAddress && log.topics.length > 0 && log.topics[0] == eventSignature) { - // Extract the indexed parameter from the log - uint64 destChainSelector = uint64(uint256(log.topics[1])); // cast to uint64 - address onRamp = address(uint160(uint256(bytes32(log.data)))); // extract from log.data - - // Determine if upkeep is needed based on the emitted log - // For simplicity, always return true to trigger performUpkeep - return (true, abi.encode(destChainSelector, onRamp)); + if (log.source == routerAddress && log.topics.length > 0 && log.topics[0] == EVENT_SIGNATURE) { + // Extract the indexed parameter from the log + uint64 destChainSelector = uint64(uint256(log.topics[1])); // cast to uint64 + address onRamp = address(uint160(uint256(bytes32(log.data)))); // extract from log.data + + // No sanity checks necessary as the would on to relay information to LinkAvailableBalanceMonitor as it is + // Checking is enabled in LinkAvailableBalanceMonitor contract + return (true, abi.encode(destChainSelector, onRamp)); } // If the event signature doesn't match or log source is not Router contract, no upkeep is needed return (false, ""); + } + + function performUpkeep(bytes memory performData) external override { + // Decode the data received from checkLog + (uint64 destChainSelector, address onRamp) = abi.decode(performData, (uint64, address)); + // Perform the necessary upkeep actions based on the decoded data + updateWatchList(onRamp, destChainSelector); + emit setWatchlistOnMonitor(destChainSelector, onRamp); + } } - - - function performUpkeep(bytes memory performData) external override{ - // Decode the data received from checkLog - (uint64 destChainSelector, address onRamp) = abi.decode(performData, (uint64, address)); - // Perform the necessary upkeep actions based on the decoded data - updateWatchList(onRamp,destChainSelector); - emit setWatchlistOnMonitor(destChainSelector,onRamp); - } -} \ No newline at end of file From 95f46c93d89c8fff91068fefb5161baa13afeabe Mon Sep 17 00:00:00 2001 From: poke1994 <139136266+poke1994@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:18:38 +0100 Subject: [PATCH 6/6] Update CCIPAutoWatchlist.sol Address comments to the updateWatchlist function of the code Imported specific parts of ILogAutomation --- .../src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol b/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol index 86f6853d032..11cb9babc4d 100644 --- a/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol +++ b/contracts/src/v0.8/automation/upkeeps/CCIPAutoWatchlist.sol @@ -6,7 +6,7 @@ import "../../shared/access/ConfirmedOwner.sol"; import {AutomationCompatibleInterface} from "../interfaces/AutomationCompatibleInterface.sol"; import {AccessControl} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/access/AccessControl.sol"; import {LinkAvailableBalanceMonitor} from "../upkeeps/LinkAvailableBalanceMonitor.sol"; -import "../interfaces/ILogAutomation.sol"; +import {ILogAutomation, Log} from "../interfaces/ILogAutomation.sol"; contract CCIPOnRampAutoWatchlist is ILogAutomation, ConfirmedOwner { event setWatchlistOnMonitor(uint64 indexed _dstChainSelector, address indexed _onRamp); @@ -33,6 +33,9 @@ contract CCIPOnRampAutoWatchlist is ILogAutomation, ConfirmedOwner { routerAddress = _newRouterAddress; } + // updateWatchList function takes emitted OnRamp Address and dstChainSelector as input and calls addToWatchListOrDecommission function of LinkAvailableBalanceMonitor passing these paraments to add the OnRamp address to the watchlist + // addToWatchListOrDecommission function of LinkAvailableBalanceMonitor is used for sanity check before adding the address on the watchlist + // updateWatchList Function is set to internal for security purposes function updateWatchList(address _targetAddress, uint64 _dstChainSelector) internal { linkAvailableBalanceMonitorAddress.addToWatchListOrDecommission(_targetAddress, _dstChainSelector); } @@ -44,9 +47,6 @@ contract CCIPOnRampAutoWatchlist is ILogAutomation, ConfirmedOwner { // Ensure Router address is set require(routerAddress != address(0), "Router address not set"); - // Define the event signature for OnRampSet(uint64,address) - //bytes32 eventSignature = keccak256(abi.encodePacked("OnRampSet(uint64,address)")); - // Check if the log source matches router contract and topics contain the event signature if (log.source == routerAddress && log.topics.length > 0 && log.topics[0] == EVENT_SIGNATURE) { // Extract the indexed parameter from the log @@ -67,6 +67,7 @@ contract CCIPOnRampAutoWatchlist is ILogAutomation, ConfirmedOwner { (uint64 destChainSelector, address onRamp) = abi.decode(performData, (uint64, address)); // Perform the necessary upkeep actions based on the decoded data updateWatchList(onRamp, destChainSelector); + // Emitting setWatchlist event to track the last added OnRamp addresses in form of Logs emit setWatchlistOnMonitor(destChainSelector, onRamp); } }