Skip to content

Commit

Permalink
wrap up automatin page
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Oct 25, 2023
1 parent 0e98ac3 commit dc8efc1
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 115 deletions.
70 changes: 41 additions & 29 deletions packages/hardhat/contracts/AutomationConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,10 @@ interface AutomationRegistrarInterface {
) external returns (uint256);
}

// interface AutomationRegistryInterface {
// function getUpkeep(uint256 id) external view returns (UpkeepInfo memory upkeepInfo);
// }

/**
* Simple counter contract using chainlink automation
*
* the upkeep registration process is integrated within this contract
/** Simple counter contract using chainlink automation
*
*/

contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
event UpkeepPerformed(uint256 indexed timestamp, uint256 indexed counter);
event CounterStarted(uint256 indexed counter);
Expand All @@ -63,14 +57,15 @@ contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
i_registry = registry;
}

/**
* - checkData param is not used in this example, but it can be set when the Upkeep is registered
/** This function is called automatically by chainlink keeper nodes
*
* @param checkData 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 */
bytes memory checkData
)
public
view
Expand All @@ -84,7 +79,7 @@ contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
return (upkeepNeeded, performData);
}

/** performUpkeep is called by a chainlink node on the condition that the checkupkeep function returns true
/** This function is called by chainlink keeper when the checkupkeep() function returns true
*
* @param performData returned value from checkUpkeep
*/
Expand All @@ -98,23 +93,27 @@ contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
if (s_counter >= s_maxCounterValue) {
s_isCounting = false;
s_counter = 0;
} else {
uint incrementValue = abi.decode(performData, (uint));
s_counter += incrementValue;
s_lastTimestamp = block.timestamp;
}

uint incrementValue = abi.decode(performData, (uint));
s_counter += incrementValue;
s_lastTimestamp = block.timestamp;
emit UpkeepPerformed(block.timestamp, s_counter);
}

/**
/** This function allows the registration of a new upkeep
*
* @param params required params for registering an upkeep
*
* DEBUG NOTES:
* - this contract successfully approves registrar to spend link
* - UNPREDICTABLE_GAS_LIMIT must be coming from the i_registrar contract
* - UNPREDICTABLE_GAS_LIMIT must be coming from i_registrar.registerUpkeep()
*/

function registerNewUpkeep(RegistrationParams memory params) public {
function registerNewUpkeep(
RegistrationParams memory params
) public onlyOwner {
require(
i_link.approve(address(i_registrar), params.amount),
"Failed to approve registrar contract to spend LINK"
Expand All @@ -125,11 +124,24 @@ contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
s_upkeepID = upkeepID;
}

// SETTERS
function setUpkeepID(uint256 _upkeepID) public {
// Setters
function setUpkeepID(uint256 _upkeepID) public onlyOwner {
s_upkeepID = _upkeepID;
}

/**
* @param amount amount of LINK to fund upkeep with
*/
function fundUpkeep(uint96 amount) public {
// Transfer LINK from EOA to this contract
require(
i_link.transferFrom(msg.sender, address(this), amount),
"Transfer failed. Ensure this contract is approved to spend LINK"
);
i_link.approve(address(i_registry), amount);
i_registry.addFunds(s_upkeepID, amount);
}

function startCounting() public {
s_isCounting = true;
s_lastTimestamp = block.timestamp;
Expand All @@ -150,16 +162,16 @@ contract AutomationConsumer is AutomationCompatibleInterface, Ownable {
emit IntervalUpdated(s_interval);
}

// GETTERS
function withdrawLink() public onlyOwner {
i_link.transfer(msg.sender, i_link.balanceOf(address(this)));
// Getters
function getUpkeepInfo() public view returns (UpkeepInfo memory) {
return i_registry.getUpkeep(s_upkeepID);
}

function getLinkBalance() public view returns (uint256) {
return i_link.balanceOf(address(this));
}
// function getLinkBalance() public view returns (uint256) {
// return i_link.balanceOf(address(this));
// }

function getUpkeepBalance() public view returns (UpkeepInfo memory) {
return i_registry.getUpkeep(s_upkeepID);
}
// function withdrawLink() public onlyOwner {
// i_link.transfer(msg.sender, i_link.balanceOf(address(this)));
// }
}
41 changes: 24 additions & 17 deletions packages/nextjs/components/automation/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,29 @@ import { useEffect, useState } from "react";
import { Spinner } from "~~/components/assets/Spinner";
import { useScaffoldEventHistory, useScaffoldEventSubscriber } from "~~/hooks/scaffold-eth/";

type Event = {
type EventData = {
timestamp: bigint | undefined;
blockNumber: bigint;
counter: bigint | undefined;
};

export const Events = () => {
const [events, setEvents] = useState<Event[]>([]);
const [events, setEvents] = useState<EventData[]>([]);

useScaffoldEventSubscriber({
contractName: "AutomationConsumer",
eventName: "UpkeepPerformed",
listener: logs => {
logs.forEach(log => {
console.log("LOG", log);
const { timestamp, counter } = log.args;
if (timestamp && counter) {
setEvents(prevEvents => {
const updatedEvents = [...prevEvents, { timestamp, counter }];

// Sort the updatedEvents array based on the timestamp
return updatedEvents.sort((a: Event, b: Event) => {
const blockNumber = log.blockNumber;
if (timestamp && counter && blockNumber) {
setEvents(prevEvents => {
const updatedEvents = [...prevEvents, { timestamp, blockNumber, counter }];
// Sort the events array based on the timestamp
return updatedEvents.sort((a: EventData, b: EventData) => {
if (a.timestamp && b.timestamp) {
return a.timestamp > b.timestamp ? -1 : a.timestamp < b.timestamp ? 1 : 0;
}
Expand All @@ -42,37 +45,41 @@ export const Events = () => {
useEffect(() => {
if (!events?.length && !!eventsData?.length && !isLoadingEvents) {
setEvents(
eventsData.map(({ args }) => ({
timestamp: args.timestamp,
counter: args.counter,
})) || [],
eventsData.map(({ args, log }) => {
return {
timestamp: args.timestamp,
blockNumber: log.blockNumber,
counter: args.counter,
};
}) || [],
);
}
}, [events.length, eventsData, isLoadingEvents]);

console.log("events", events);
console.log("eventsData", eventsData);

return (
<section className="mb-5 grow bg-base-200 rounded-xl">
<div className="">
<section className="grow bg-base-200 rounded-xl h-[350px]">
<div className="h-full">
{!eventsData || isLoadingEvents ? (
<div className="w-full h-full flex flex-col justify-center items-center">
<Spinner width="75" height="75" />
</div>
) : (
<div className="overflow-x-auto overflow-y-auto h-full hide-scrollbar ">
<table className="table">
<table className="table table-pin-rows">
<thead className="text-lg">
<tr className="border-b border-base-100">
<tr className="bg-base-300 ">
<th>Timestamp</th>
<th>Block #</th>
<th>Block</th>
<th>Count</th>
</tr>
</thead>
<tbody className="text-lg">
{events.map((event, idx) => (
<tr key={idx} className="border-b border-base-100">
<td>{event.timestamp ? new Date(Number(event.timestamp) * 1000).toLocaleTimeString() : "N/A"}</td>
<td>{event.blockNumber ? event.blockNumber.toString() : "N/A"}</td>
<td>{event.counter ? event?.counter?.toString() : "N/A"}</td>
</tr>
))}
Expand Down
53 changes: 30 additions & 23 deletions packages/nextjs/components/automation/Showcase.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { ExternalLinkButton } from "../common";
import { InlineCode } from "../common";
import { Events } from "./Events";
import { formatEther } from "viem";
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { Address } from "~~/components/scaffold-eth";
import { useScaffoldContract, useScaffoldContractRead, useScaffoldContractWrite } from "~~/hooks/scaffold-eth";

export const Showcase = () => {
const { data: vrfConsumerContract } = useScaffoldContract({ contractName: "AutomationConsumer" });

// const { data: linkBalance } = useScaffoldContractRead({
// contractName: "AutomationConsumer",
// functionName: "getLinkBalance",
// });
const { data: upkeepInfo } = useScaffoldContractRead({
contractName: "AutomationConsumer",
functionName: "getUpkeepInfo",
});

const { data: currentCount } = useScaffoldContractRead({
contractName: "AutomationConsumer",
Expand Down Expand Up @@ -43,6 +45,8 @@ export const Showcase = () => {
},
});

console.log("upkeepInfo", upkeepInfo);

return (
<div className="bg-base-100 rounded-xl mb-10 p-5 lg:p-10">
<div className="flex flex-wrap justify-between gap-2 items-center mb-10">
Expand All @@ -58,28 +62,18 @@ export const Showcase = () => {
<div className="grid grid-cols-1 lg:grid-cols-2 gap-8">
<div>
<p className="text-xl">
Since smart contracts cannot initiate functions or update their state by themselves, they require externally
owned accounts to trigger their execution. Chainlink automation is a reliable way to outsource smart
contract operations.
Since smart contracts cannot initate transactions without the help of an externally owned account, a service
like chainlink automation is a good option for executing transactions via time based or conditional logic
triggers.
</p>

<p className="text-xl">
Click start to update boolean state variable integrated with the <InlineCode text="checkUpkeep" />{" "}
function&apos;s return value that controls if chainlink nodes should call the{" "}
<InlineCode text="performUpkeep" /> function.
Click <InlineCode text="start" /> to update boolean state variable integrated with the{" "}
<InlineCode text="checkUpkeep" /> function&apos;s return value that controls if chainlink nodes should call
the <InlineCode text="performUpkeep" /> function every <InlineCode text="interval" /> seconds. The contract
is set up to stop counting at 10
</p>

<p className="text-xl">
Modify the <InlineCode text="s_interval" /> to get a sense of how often the chainlink keeper nodes will
trigger the <InlineCode text="performUpkeep" /> function.
</p>
</div>

<div className="flex flex-col">
<h4 className="text-center font-medium text-xl">UpkeepPerformed Events</h4>

<Events />

<div className="bg-base-200 rounded-xl flex flex-wrap justify-around items-center">
<div className="stats">
<div className="stat bg-base-200">
Expand Down Expand Up @@ -112,10 +106,23 @@ export const Showcase = () => {
</button>
)}
</div>
<div className="alert mt-5 text-xl">
<InformationCircleIcon className="stroke-current shrink-0 w-6 h-6" />
<div>
<span className="font-bold mr-2">
{upkeepInfo?.balance ? parseFloat(formatEther(upkeepInfo.balance)).toFixed(2) : "0.0"} LINK
</span>
left in upkeep to fund automation
</div>
</div>
</div>

<div className="flex flex-col">
<h4 className="text-center font-medium text-xl">UpkeepPerformed Events</h4>

<Events />
</div>
</div>
</div>
);
};

// parseFloat(formatUnits(latestPrice, decimals)).toFixed(2)
46 changes: 22 additions & 24 deletions packages/nextjs/components/vrf/ResultsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,28 @@ interface ResultsTableProps {
export const ResultsTable: React.FC<ResultsTableProps> = ({ results, wheelOptions }) => {
return (
<div>
<div className="sm:p-5 p-8">
<div className="overflow-x-auto">
<table className="table text-2xl">
<thead>
<tr className="text-xl">
<th>Spinner</th>
<th>Result</th>
</tr>
</thead>
<tbody>
{results.slice(0, 5).map((result, i) => {
const { spinner, randomValue } = result;
return (
<tr key={i}>
<td>
<Address size="xl" address={spinner} />
</td>
<td>{wheelOptions[Number(randomValue)].option}</td>
</tr>
);
})}
</tbody>
</table>
</div>
<div className="overflow-x-auto">
<table className="table text-2xl table-pin-rows">
<thead>
<tr className="text-lg bg-base-300 ">
<th>Spinner</th>
<th>Result</th>
</tr>
</thead>
<tbody>
{results.slice(0, 5).map((result, i) => {
const { spinner, randomValue } = result;
return (
<tr key={i} className="border-b border-base-100">
<td>
<Address size="xl" address={spinner} />
</td>
<td>{wheelOptions[Number(randomValue)].option}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
Expand Down
Loading

0 comments on commit dc8efc1

Please sign in to comment.