Skip to content

Commit

Permalink
rough outline for automation consumer showcase
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Oct 20, 2023
1 parent 1cda182 commit 0541db5
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 314 deletions.
171 changes: 51 additions & 120 deletions packages/hardhat/contracts/AutomationConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,159 +2,90 @@
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/automation/AutomationCompatible.sol";
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

// Errors
error AutomationConsumer__UpkeepNotNeeded();

/**
* user guesses if the next price returned by chainlink will be higher or lower than the current price
* bets are placed in LINK
* Simple counter contract using chainlink automation
* - try to figure out how to use performData
*/
contract AutomationConsumer is AutomationCompatibleInterface {
// Types
enum Prediction {
Lower, // 0
Higher // 1
}
struct Bet {
address bettor;
uint256 amount;
int referencePrice;
Prediction prediction;
}
contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
event UpkeepPerformed(uint256 counter);
event CounterStarted(uint256 counter);
event CounterStopped(uint256 counter);

// State variables
AggregatorV3Interface internal immutable i_priceFeed;
uint public counter = 0;
uint public interval = 5 seconds;
uint public maxCounterValue = 10;
bool public isCounting = false;
uint public lastTimeStamp;
IERC20 internal immutable i_linkToken;
mapping(uint => Bet) public bets; // betId => Bet struct
uint[] public activeBets; // betIds of bets that are still active
uint public nextBetId = 0;

// Events
event BetPlaced(
uint256 indexed betId,
address indexed bettor,
int256 indexed referencePrice,
uint256 amount,
Prediction prediction
);
event BetWon(uint betId, address indexed bettor, uint256 payout);
event BetLost(uint betId, address indexed bettor, uint256 amountLost);

// Functions
constructor(address priceFeedAddress, address linkTokenAddress) {
i_priceFeed = AggregatorV3Interface(priceFeedAddress);
constructor(address linkTokenAddress) {
i_linkToken = IERC20(linkTokenAddress);
lastTimeStamp = block.timestamp;
}

/** Allows user to place bet on whether price of asset will be higher or lower than current price
* @param amount amount of LINK to wager
* @param prediction Lower or Higher than the price at time of bet
*/
function placeBet(uint256 amount, Prediction prediction) public {
require(
amount >= 1 ether && amount <= 5 ether,
"You must wager between 1 and 5 LINK"
);
require(
i_linkToken.allowance(msg.sender, address(this)) >= amount,
"Insufficient LINK token allowance"
);
require(
i_linkToken.transferFrom(msg.sender, address(this), amount),
"Failed to transfer LINK"
);
(, int answer, , , ) = i_priceFeed.latestRoundData();
bets[nextBetId] = Bet({
bettor: msg.sender,
referencePrice: answer,
amount: amount,
prediction: prediction
});
activeBets.push(nextBetId);
emit BetPlaced(nextBetId, msg.sender, answer, amount, prediction);
nextBetId++;
function startCounting() public {
isCounting = true;
emit CounterStarted(counter);
}

/** Chainlink Keeper nodes call checkUpkeep to see if it returns true
* https://docs.chain.link/chainlink-automation/compatible-contracts#checkupkeep-function
*
* @dev returns true if there are active bets and the latest price is different than
* the reference price of the most recently placed bet
function stopCounting() public {
isCounting = false;
emit CounterStopped(counter);
}

/**
* - checkData param is not used in this example, but it can be set when the Upkeep is registered
* @return upkeepNeeded - if true, performUpkeep() will be called
* @return performData - data passed to performUpkeep() (can be dynamically computed within checkUpkeep())
*/
function checkUpkeep(
bytes memory /* checkData */
)
public
view
override
returns (bool upkeepNeeded, bytes memory /* performData */)
returns (bool upkeepNeeded, bytes memory performData)
{
if (activeBets.length == 0) {
upkeepNeeded = false;
}
(, int latestPrice, , , ) = i_priceFeed.latestRoundData();
// execute the automation if the latest price is different than the reference price of the most recently placed bet
upkeepNeeded =
bets[activeBets[activeBets.length - 1]].referencePrice !=
latestPrice;
bool isPastInterval = (block.timestamp - lastTimeStamp) > interval;
upkeepNeeded = isPastInterval && isCounting;
uint incrementValue = 1;
performData = abi.encode(incrementValue);
return (upkeepNeeded, performData);
}

/** Once `checkUpkeep` returns true, `performUpkeep` will be triggered by chainlink node
* https://docs.chain.link/chainlink-automation/compatible-contracts#performupkeep-function
*
* @notice pays out winners and remove bets that have been settled
/** performUpkeep is called by a chainlink node on the condition that the checkupkeep function returns true
*
* - winners get paid double their bet amount
* - losers lose their bet amount
*
* TODO: handle if price is still the same somehow?
* @param performData returned value from checkUpkeep
*/
function performUpkeep(bytes calldata /* performData */) external override {
// best practice to revalidate the upkeepNeeded
(bool upkeepNeeded, ) = checkUpkeep(bytes("0x"));
function performUpkeep(bytes calldata performData) external override {
// best practice to revalidate upkeep needed before executing any logic in performUpkeep
(bool upkeepNeeded, ) = checkUpkeep(bytes(""));
if (!upkeepNeeded) {
revert AutomationConsumer__UpkeepNotNeeded();
}
if (counter >= maxCounterValue) {
isCounting = false;
counter = 0;
}

(, int latestPrice, , , ) = i_priceFeed.latestRoundData();

while (activeBets.length > 0) {
// pointer to end of array enables .pop() for efficiency
uint betId = activeBets[activeBets.length - 1];
Bet memory bet = bets[betId];

bool isWinner = (bet.referencePrice > latestPrice &&
bet.prediction == Prediction.Lower) ||
(bet.referencePrice < latestPrice &&
bet.prediction == Prediction.Higher);
uint incrementValue = abi.decode(performData, (uint));
lastTimeStamp = block.timestamp;
counter += incrementValue;

if (isWinner) {
uint256 payout = bet.amount * 2;
i_linkToken.transfer(bet.bettor, payout);
emit BetWon(betId, bet.bettor, payout);
} else {
emit BetLost(betId, bet.bettor, bet.amount);
}
emit UpkeepPerformed(counter);
}

activeBets.pop();
}
// Utilitiy functions
function getLinkBalance() public view returns (uint256) {
return i_linkToken.balanceOf(address(this));
}

// Getters
function getLatestRoundData()
public
view
returns (
uint80 roundId,
int price,
uint256 startedAt,
uint256 updatedAt
)
{
(roundId, price, startedAt, updatedAt, ) = i_priceFeed
.latestRoundData();
function withdrawLink() public onlyOwner {
i_linkToken.transfer(msg.sender, i_linkToken.balanceOf(address(this)));
}
}
61 changes: 61 additions & 0 deletions packages/hardhat/contracts/RegisterCustomLogicUpkeep.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.6;

import { LinkTokenInterface } from "@chainlink/contracts/src/v0.8/shared/interfaces/LinkTokenInterface.sol";

struct RegistrationParams {
string name;
bytes encryptedEmail;
address upkeepContract;
uint32 gasLimit;
address adminAddress;
uint8 triggerType;
bytes checkData;
bytes triggerConfig;
bytes offchainConfig;
uint96 amount;
}

/**
* string name = "test upkeep";
* bytes encryptedEmail = 0x;
* address upkeepContract = 0x...;
* uint32 gasLimit = 500000;
* address adminAddress = 0x....;
* uint8 triggerType = 0;
* bytes checkData = 0x;
* bytes triggerConfig = 0x;
* bytes offchainConfig = 0x;
* uint96 amount = 1000000000000000000;
*/

interface AutomationRegistrarInterface {
function registerUpkeep(
RegistrationParams calldata requestParams
) external returns (uint256);
}

contract UpkeepIDConditionalExample {
LinkTokenInterface public immutable i_link;
AutomationRegistrarInterface public immutable i_registrar;

constructor(
LinkTokenInterface link,
AutomationRegistrarInterface registrar
) {
i_link = link;
i_registrar = registrar;
}

function registerAndPredictID(RegistrationParams memory params) public {
// LINK must be approved for transfer - this can be done every time or once
// with an infinite approval
i_link.approve(address(i_registrar), params.amount);
uint256 upkeepID = i_registrar.registerUpkeep(params);
if (upkeepID != 0) {
// DEV - Use the upkeepID however you see fit
} else {
revert("auto-approve disabled");
}
}
}
4 changes: 1 addition & 3 deletions packages/hardhat/contracts/VRFConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,7 @@ contract VRFConsumer is VRFV2WrapperConsumerBase, ConfirmedOwner {
// State variables
address public linkAddress;
uint32 callbackGasLimit = 100000; // limit for gas can be used when chainlink node calls fulfillRandomWords()
// how many blocks chainlink node waits before responding
// more blocks is safer to avoid chain reorgs but increases response time
uint16 requestConfirmations = 2;
uint16 requestConfirmations = 2; // blocks before chainlink node responds
uint32 numValues = 1; // how many random numbers to generate

mapping(uint256 => address) public s_spinners; // requestId => msg.sender
Expand Down
4 changes: 2 additions & 2 deletions packages/hardhat/deploy/04_AutomationConsumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ const deployAutomationConsumer: DeployFunction = async function (hre: HardhatRun
const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);

log("------------------------------------");
const { priceFeedAddress, linkTokenAddress } = networkConfig[chainId].AutomationConsumer;
const { linkTokenAddress } = networkConfig[chainId].AutomationConsumer;

const args = [priceFeedAddress, linkTokenAddress];
const args = [linkTokenAddress];

const AutomationConsumer = await deploy("AutomationConsumer", {
from: deployer,
Expand Down
4 changes: 2 additions & 2 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,8 @@ export const Header = () => {
<Image alt="SE2 logo" className="cursor-pointer" fill src="/logo.svg" />
</div>
<div className="flex flex-col">
<span className="font-bold leading-tight">Scaffold-ETH</span>
<span className="text-xs">Ethereum dev stack</span>
<span className="font-bold leading-tight">Speedrun Chainlink</span>
<span className="text-xs">with scaffold eth 2</span>
</div>
</Link>
<ul className="hidden lg:flex lg:flex-nowrap menu menu-horizontal px-1 gap-2">{navLinks}</ul>
Expand Down
Loading

0 comments on commit 0541db5

Please sign in to comment.