forked from smartcontractkit/documentation
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into dynamic-nft-quickstart
- Loading branch information
Showing
38 changed files
with
1,821 additions
and
618 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+264 KB
public/images/automation/qs-time-based-upkeep/approve-upkeep-pause.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+60.2 KB
public/images/automation/qs-time-based-upkeep/couldnt-fetch-abi-ui.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+120 KB
public/images/automation/qs-time-based-upkeep/metamask-contract-deployment.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+422 KB
public/images/automation/qs-time-based-upkeep/metamask-pause-upkeep.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+36.2 KB
public/images/automation/qs-time-based-upkeep/remix-check-counter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+22.6 KB
public/images/automation/qs-time-based-upkeep/remix-deployed-contracts.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.7; | ||
|
||
/** | ||
* 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. | ||
*/ | ||
|
||
contract Counter { | ||
/** | ||
* Public counter variable | ||
*/ | ||
uint public counter; | ||
|
||
constructor() { | ||
counter = 0; | ||
} | ||
|
||
function updateCounter(uint updateAmount) external { | ||
counter += updateAmount; | ||
} | ||
} |
336 changes: 336 additions & 0 deletions
336
public/samples/Automation/tutorials/VRFSubscriptionBalanceMonitor.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,336 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.6; | ||
|
||
import "@chainlink/contracts/src/v0.8/ConfirmedOwner.sol"; | ||
import "@chainlink/contracts/src/v0.8/interfaces/KeeperCompatibleInterface.sol"; | ||
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol"; | ||
import "@chainlink/contracts/src/v0.8/interfaces/LinkTokenInterface.sol"; | ||
import "@openzeppelin/contracts/security/Pausable.sol"; | ||
|
||
/** | ||
* @title The VRFSubscriptionBalanceMonitor contract. | ||
* @notice A keeper-compatible contract that monitors and funds VRF subscriptions. | ||
*/ | ||
contract VRFSubscriptionBalanceMonitor is | ||
ConfirmedOwner, | ||
Pausable, | ||
KeeperCompatibleInterface | ||
{ | ||
VRFCoordinatorV2Interface public COORDINATOR; | ||
LinkTokenInterface public LINKTOKEN; | ||
|
||
uint256 private constant MIN_GAS_FOR_TRANSFER = 55_000; | ||
|
||
event FundsAdded(uint256 amountAdded, uint256 newBalance, address sender); | ||
event FundsWithdrawn(uint256 amountWithdrawn, address payee); | ||
event TopUpSucceeded(uint64 indexed subscriptionId); | ||
event TopUpFailed(uint64 indexed subscriptionId); | ||
event KeeperRegistryAddressUpdated(address oldAddress, address newAddress); | ||
event VRFCoordinatorV2AddressUpdated( | ||
address oldAddress, | ||
address newAddress | ||
); | ||
event LinkTokenAddressUpdated(address oldAddress, address newAddress); | ||
event MinWaitPeriodUpdated( | ||
uint256 oldMinWaitPeriod, | ||
uint256 newMinWaitPeriod | ||
); | ||
event OutOfGas(uint256 lastId); | ||
|
||
error InvalidWatchList(); | ||
error OnlyKeeperRegistry(); | ||
error DuplicateSubcriptionId(uint64 duplicate); | ||
|
||
struct Target { | ||
bool isActive; | ||
uint96 minBalanceJuels; | ||
uint96 topUpAmountJuels; | ||
uint56 lastTopUpTimestamp; | ||
} | ||
|
||
address public s_keeperRegistryAddress; // the address of the keeper registry | ||
uint256 public s_minWaitPeriodSeconds; // minimum time to wait between top-ups | ||
uint64[] public s_watchList; // the watchlist on which subscriptions are stored | ||
mapping(uint64 => Target) internal s_targets; | ||
|
||
/** | ||
* @param linkTokenAddress the Link token address | ||
* @param coordinatorAddress the address of the vrf coordinator contract | ||
* @param keeperRegistryAddress the address of the keeper registry contract | ||
* @param minWaitPeriodSeconds the minimum wait period for addresses between funding | ||
*/ | ||
constructor( | ||
address linkTokenAddress, | ||
address coordinatorAddress, | ||
address keeperRegistryAddress, | ||
uint256 minWaitPeriodSeconds | ||
) ConfirmedOwner(msg.sender) { | ||
setLinkTokenAddress(linkTokenAddress); | ||
setVRFCoordinatorV2Address(coordinatorAddress); | ||
setKeeperRegistryAddress(keeperRegistryAddress); | ||
setMinWaitPeriodSeconds(minWaitPeriodSeconds); | ||
} | ||
|
||
/** | ||
* @notice Sets the list of subscriptions to watch and their funding parameters. | ||
* @param subscriptionIds the list of subscription ids to watch | ||
* @param minBalancesJuels the minimum balances for each subscription | ||
* @param topUpAmountsJuels the amount to top up each subscription | ||
*/ | ||
function setWatchList( | ||
uint64[] calldata subscriptionIds, | ||
uint96[] calldata minBalancesJuels, | ||
uint96[] calldata topUpAmountsJuels | ||
) external onlyOwner { | ||
if ( | ||
subscriptionIds.length != minBalancesJuels.length || | ||
subscriptionIds.length != topUpAmountsJuels.length | ||
) { | ||
revert InvalidWatchList(); | ||
} | ||
uint64[] memory oldWatchList = s_watchList; | ||
for (uint256 idx = 0; idx < oldWatchList.length; idx++) { | ||
s_targets[oldWatchList[idx]].isActive = false; | ||
} | ||
for (uint256 idx = 0; idx < subscriptionIds.length; idx++) { | ||
if (s_targets[subscriptionIds[idx]].isActive) { | ||
revert DuplicateSubcriptionId(subscriptionIds[idx]); | ||
} | ||
if (subscriptionIds[idx] == 0) { | ||
revert InvalidWatchList(); | ||
} | ||
if (topUpAmountsJuels[idx] <= minBalancesJuels[idx]) { | ||
revert InvalidWatchList(); | ||
} | ||
s_targets[subscriptionIds[idx]] = Target({ | ||
isActive: true, | ||
minBalanceJuels: minBalancesJuels[idx], | ||
topUpAmountJuels: topUpAmountsJuels[idx], | ||
lastTopUpTimestamp: 0 | ||
}); | ||
} | ||
s_watchList = subscriptionIds; | ||
} | ||
|
||
/** | ||
* @notice Gets a list of subscriptions that are underfunded. | ||
* @return list of subscriptions that are underfunded | ||
*/ | ||
function getUnderfundedSubscriptions() | ||
public | ||
view | ||
returns (uint64[] memory) | ||
{ | ||
uint64[] memory watchList = s_watchList; | ||
uint64[] memory needsFunding = new uint64[](watchList.length); | ||
uint256 count = 0; | ||
uint256 minWaitPeriod = s_minWaitPeriodSeconds; | ||
uint256 contractBalance = LINKTOKEN.balanceOf(address(this)); | ||
Target memory target; | ||
for (uint256 idx = 0; idx < watchList.length; idx++) { | ||
target = s_targets[watchList[idx]]; | ||
(uint96 subscriptionBalance, , , ) = COORDINATOR.getSubscription( | ||
watchList[idx] | ||
); | ||
if ( | ||
target.lastTopUpTimestamp + minWaitPeriod <= block.timestamp && | ||
contractBalance >= target.topUpAmountJuels && | ||
subscriptionBalance < target.minBalanceJuels | ||
) { | ||
needsFunding[count] = watchList[idx]; | ||
count++; | ||
contractBalance -= target.topUpAmountJuels; | ||
} | ||
} | ||
if (count < watchList.length) { | ||
assembly { | ||
mstore(needsFunding, count) | ||
} | ||
} | ||
return needsFunding; | ||
} | ||
|
||
/** | ||
* @notice Send funds to the subscriptions provided. | ||
* @param needsFunding the list of subscriptions to fund | ||
*/ | ||
function topUp(uint64[] memory needsFunding) public whenNotPaused { | ||
uint256 minWaitPeriodSeconds = s_minWaitPeriodSeconds; | ||
uint256 contractBalance = LINKTOKEN.balanceOf(address(this)); | ||
Target memory target; | ||
for (uint256 idx = 0; idx < needsFunding.length; idx++) { | ||
target = s_targets[needsFunding[idx]]; | ||
(uint96 subscriptionBalance, , , ) = COORDINATOR.getSubscription( | ||
needsFunding[idx] | ||
); | ||
if ( | ||
target.isActive && | ||
target.lastTopUpTimestamp + minWaitPeriodSeconds <= | ||
block.timestamp && | ||
subscriptionBalance < target.minBalanceJuels && | ||
contractBalance >= target.topUpAmountJuels | ||
) { | ||
bool success = LINKTOKEN.transferAndCall( | ||
address(COORDINATOR), | ||
target.topUpAmountJuels, | ||
abi.encode(needsFunding[idx]) | ||
); | ||
if (success) { | ||
s_targets[needsFunding[idx]].lastTopUpTimestamp = uint56( | ||
block.timestamp | ||
); | ||
contractBalance -= target.topUpAmountJuels; | ||
emit TopUpSucceeded(needsFunding[idx]); | ||
} else { | ||
emit TopUpFailed(needsFunding[idx]); | ||
} | ||
} | ||
if (gasleft() < MIN_GAS_FOR_TRANSFER) { | ||
emit OutOfGas(idx); | ||
return; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @notice Gets list of subscription ids that are underfunded and returns a keeper-compatible payload. | ||
* @return upkeepNeeded signals if upkeep is needed, performData is an abi encoded list of subscription ids that need funds | ||
*/ | ||
function checkUpkeep( | ||
bytes calldata | ||
) | ||
external | ||
view | ||
override | ||
whenNotPaused | ||
returns (bool upkeepNeeded, bytes memory performData) | ||
{ | ||
uint64[] memory needsFunding = getUnderfundedSubscriptions(); | ||
upkeepNeeded = needsFunding.length > 0; | ||
performData = abi.encode(needsFunding); | ||
return (upkeepNeeded, performData); | ||
} | ||
|
||
/** | ||
* @notice Called by the keeper to send funds to underfunded addresses. | ||
* @param performData the abi encoded list of addresses to fund | ||
*/ | ||
function performUpkeep( | ||
bytes calldata performData | ||
) external override onlyKeeperRegistry whenNotPaused { | ||
uint64[] memory needsFunding = abi.decode(performData, (uint64[])); | ||
topUp(needsFunding); | ||
} | ||
|
||
/** | ||
* @notice Withdraws the contract balance in LINK. | ||
* @param amount the amount of LINK (in juels) to withdraw | ||
* @param payee the address to pay | ||
*/ | ||
function withdraw( | ||
uint256 amount, | ||
address payable payee | ||
) external onlyOwner { | ||
require(payee != address(0)); | ||
emit FundsWithdrawn(amount, payee); | ||
LINKTOKEN.transfer(payee, amount); | ||
} | ||
|
||
/** | ||
* @notice Sets the LINK token address. | ||
*/ | ||
function setLinkTokenAddress(address linkTokenAddress) public onlyOwner { | ||
require(linkTokenAddress != address(0)); | ||
emit LinkTokenAddressUpdated(address(LINKTOKEN), linkTokenAddress); | ||
LINKTOKEN = LinkTokenInterface(linkTokenAddress); | ||
} | ||
|
||
/** | ||
* @notice Sets the VRF coordinator address. | ||
*/ | ||
function setVRFCoordinatorV2Address( | ||
address coordinatorAddress | ||
) public onlyOwner { | ||
require(coordinatorAddress != address(0)); | ||
emit VRFCoordinatorV2AddressUpdated( | ||
address(COORDINATOR), | ||
coordinatorAddress | ||
); | ||
COORDINATOR = VRFCoordinatorV2Interface(coordinatorAddress); | ||
} | ||
|
||
/** | ||
* @notice Sets the keeper registry address. | ||
*/ | ||
function setKeeperRegistryAddress( | ||
address keeperRegistryAddress | ||
) public onlyOwner { | ||
require(keeperRegistryAddress != address(0)); | ||
emit KeeperRegistryAddressUpdated( | ||
s_keeperRegistryAddress, | ||
keeperRegistryAddress | ||
); | ||
s_keeperRegistryAddress = keeperRegistryAddress; | ||
} | ||
|
||
/** | ||
* @notice Sets the minimum wait period (in seconds) for subscription ids between funding. | ||
*/ | ||
function setMinWaitPeriodSeconds(uint256 period) public onlyOwner { | ||
emit MinWaitPeriodUpdated(s_minWaitPeriodSeconds, period); | ||
s_minWaitPeriodSeconds = period; | ||
} | ||
|
||
/** | ||
* @notice Gets configuration information for a subscription on the watchlist. | ||
*/ | ||
function getSubscriptionInfo( | ||
uint64 subscriptionId | ||
) | ||
external | ||
view | ||
returns ( | ||
bool isActive, | ||
uint96 minBalanceJuels, | ||
uint96 topUpAmountJuels, | ||
uint56 lastTopUpTimestamp | ||
) | ||
{ | ||
Target memory target = s_targets[subscriptionId]; | ||
return ( | ||
target.isActive, | ||
target.minBalanceJuels, | ||
target.topUpAmountJuels, | ||
target.lastTopUpTimestamp | ||
); | ||
} | ||
|
||
/** | ||
* @notice Gets the list of subscription ids being watched. | ||
*/ | ||
function getWatchList() external view returns (uint64[] memory) { | ||
return s_watchList; | ||
} | ||
|
||
/** | ||
* @notice Pause the contract, which prevents executing performUpkeep. | ||
*/ | ||
function pause() external onlyOwner { | ||
_pause(); | ||
} | ||
|
||
/** | ||
* @notice Unpause the contract. | ||
*/ | ||
function unpause() external onlyOwner { | ||
_unpause(); | ||
} | ||
|
||
modifier onlyKeeperRegistry() { | ||
if (msg.sender != s_keeperRegistryAddress) { | ||
revert OnlyKeeperRegistry(); | ||
} | ||
_; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.