Secure Upkeeps Using the Forwarder
This tutorial explains how to use the Forwarder
to add additional security to your Automation upkeeps. To learn how other Chainlink Automation contracts work, click here.
What is a Forwarder? When is it used?
Each registered upkeep under the Chainlink Automation network has its own unique Forwarder
contract. The Forwarder
address will only be known after registration, as we deploy a new forwarder for each upkeep. The Forwarder
contract is the intermediary between the Automation Registry and your Upkeep contract. The Forwarder
is always the msg.Sender
for your upkeep.
If your performUpkeep
function is open and callable by anyone without risk of accepting unintentional external data, you don't need to use the Forwarder
.
Securing your upkeep
If your upkeep's perform function needs to be permissioned, please consider adding msg.sender = forwarder
at the top of your performUpkeep
function.
To make this work you will need to:
- Create
forwarder
as a mutable address variable on your contract that only you can update.forwarder
is a unique value that cannot change for your upkeep. - Create
setForwarder
function so you can update theforwarder
address. - After registration run
setForwarder
with the forwarder address in your UI, or programmatically fetch it usingregistry.getForwarder(upkeepID)
using the Registry interface.
Code example
The code sample below uses the Forwarder:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
/**
* @dev Example contract which uses the Forwarder
*
* @notice important to implement {AutomationCompatibleInterface}
*/
/**
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/
import {AutomationCompatibleInterface} from "@chainlink/contracts/src/v0.8/automation/interfaces/AutomationCompatibleInterface.sol";
import {OwnerIsCreator} from "@chainlink/contracts/src/v0.8/shared/access/OwnerIsCreator.sol";
contract CounterwForwarder is AutomationCompatibleInterface, OwnerIsCreator {
uint256 public counter; // counter counts the number of upkeeps performed
uint256 public interval; // interval specifies the time between upkeeps
uint256 public lastTimeStamp; // lastTimeStamp tracks the last upkeep performed
address public s_forwarderAddress;
constructor(uint256 updateInterval) {
interval = updateInterval;
}
function checkUpkeep(
bytes calldata /*checkData*/
) external override returns (bool, bytes memory) {
bool needsUpkeep = (block.timestamp - lastTimeStamp) > interval;
return (needsUpkeep, bytes(""));
}
function performUpkeep(bytes calldata /*performData*/) external override {
require(
msg.sender == s_forwarderAddress,
"This address does not have permission to call performUpkeep"
);
lastTimeStamp = block.timestamp;
counter = counter + 1;
}
/// @notice Set the address that `performUpkeep` is called from
/// @dev Only callable by the owner
/// @param forwarderAddress the address to set
function setForwarderAddress(address forwarderAddress) external onlyOwner {
s_forwarderAddress = forwarderAddress;
}
}