Skip to content

Commit

Permalink
grinding VRF page
Browse files Browse the repository at this point in the history
  • Loading branch information
MattPereira committed Oct 14, 2023
1 parent b7778c9 commit 5b92d01
Show file tree
Hide file tree
Showing 11 changed files with 406 additions and 1,012 deletions.
63 changes: 29 additions & 34 deletions packages/hardhat/contracts/FeedRegistryConsumer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,41 @@ import "@chainlink/contracts/src/v0.8/interfaces/FeedRegistryInterface.sol";
import "@chainlink/contracts/src/v0.8/Denominations.sol";

/**Contract to consume the Chainlink FeedRegistry
*
*
* @notice FeedRegistry address only exists on the Ethereum Mainnet
* @notice https://docs.chain.link/data-feeds/feed-registry#contract-addresses
*/
contract FeedRegistryConsumer {

FeedRegistryInterface internal immutable i_registry;

constructor(address registryAddress) {
i_registry = FeedRegistryInterface(registryAddress);
}

/**
* Get the latest price of ETH/USD
*/
function getEthUsdPrice() public view returns (int) {
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = i_registry.latestRoundData(Denominations.ETH, Denominations.USD);
return price;
}

/** General method capable of returning the relative price of any available asset pair
*
* @param base any denomination from the library or a valid ERC20 token address
* @param quote the quote denomination from the Denominations library or a valid ERC20 token address
*/

function getPrice(address base, address quote) public view returns (int) {
// prettier-ignore
(
FeedRegistryInterface internal immutable i_registry;

constructor(address registryAddress) {
i_registry = FeedRegistryInterface(registryAddress);
}

/**
* Get the latest price of ETH/USD
*/
function getEthUsdPrice() public view returns (int) {
(, /*uint80 roundID*/ int price, , , ) = /*uint startedAt*/ /*uint timeStamp*/ /*uint80 answeredInRound*/
i_registry.latestRoundData(Denominations.ETH, Denominations.USD);
return price;
}

/** General method capable of returning the relative price of any available asset pair
*
* @param base any denomination from the library or a valid ERC20 token address
* @param quote the quote denomination from the Denominations library or a valid ERC20 token address
*/

function getPrice(address base, address quote) public view returns (int) {
// prettier-ignore
(
/*uint80 roundID*/,
int price,
/*uint startedAt*/,
/*uint timeStamp*/,
/*uint80 answeredInRound*/
) = i_registry.latestRoundData(base, quote);
return price;
}
}
return price;
}
}
79 changes: 79 additions & 0 deletions packages/hardhat/contracts/VRFConsumer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";

/** Simple contract that requests a random number and stores it as a state variable
*
* @dev the arguments for "requestRandomWords()" don't have to be immutable but it saves on gas
*/
contract VRFConsumer is VRFConsumerBaseV2 {
// State variables
VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
uint64 private immutable i_subscriptionId;
bytes32 private immutable i_keyHash; // gas lane ?
uint32 private immutable i_callbackGasLimit;
uint16 private constant REQUEST_CONFIRMATIONS = 3; // # of blocks to wait before the request can be fulfilled
uint32 private constant NUM_WORDS = 1; // how many random numbers to request

mapping(uint256 => address) public s_requestIdToSender; // tracks the sender of the request using the requestId
mapping(address => uint256) public s_senderToResult; // stores the resulting random number for each requester

// Events
event RequestRandomNumber(
uint256 indexed requestId,
address indexed requester
);
event RandomNumberReceived(
uint256 indexed requestId,
uint256 indexed result
);

constructor(
address vrfCoordinatorV2,
bytes32 keyHash,
uint64 subscriptionId,
uint32 callbackGasLimit
) VRFConsumerBaseV2(vrfCoordinatorV2) {
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
i_keyHash = keyHash;
i_subscriptionId = subscriptionId;
i_callbackGasLimit = callbackGasLimit;
}

/** This function triggers the request to chainlink node that generates the random number
* @return requestId each request has a unique ID
*/
function requestRandomNumber() public returns (uint256 requestId) {
// Will revert if subscription is not setup and funded
requestId = i_vrfCoordinator.requestRandomWords(
i_keyHash,
i_subscriptionId,
REQUEST_CONFIRMATIONS,
i_callbackGasLimit,
NUM_WORDS
);

// keep track of who sent the request
s_requestIdToSender[requestId] = msg.sender;
emit RequestRandomNumber(requestId, msg.sender);
}

/** Chainlink oracle calls this function to deliver the random number
* @param requestId The ID of the request
* @param randomWords Array containing the random number(s)
*
* @dev use the random number to change state of your contract here
* @dev the random number is huge so you'll want to use modulo to constrain the range
*/
function fulfillRandomWords(
uint256 requestId,
uint256[] memory randomWords
) internal override {
uint256 randomNumber = (randomWords[0] % 100) + 1; // 1 - 100
// update mapping to record who received which random number by using the requestId
s_senderToResult[s_requestIdToSender[requestId]] = randomNumber;
emit RandomNumberReceived(requestId, randomNumber);
}
}
2 changes: 1 addition & 1 deletion packages/hardhat/deploy/01_AggregatorV3Consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const deployAggregatorV3Consumer: DeployFunction = async function (hre: HardhatR
priceFeedAddress = MockV3Aggregator.address;
} else {
// use address from helper-hardhat-config if on testnet or live network
priceFeedAddress = networkConfig[chainId].priceFeeds?.ETH_USD;
priceFeedAddress = networkConfig[chainId].priceFeedAddress?.ETH_USD;
}

const args = [priceFeedAddress];
Expand Down
59 changes: 59 additions & 0 deletions packages/hardhat/deploy/03_VRFConsumer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { developmentChains, networkConfig } from "../helper-hardhat-config";
import { network, ethers } from "hardhat";

// only for vrfCoordinatorMock deployment
const FUND_AMOUNT = ethers.utils.parseUnits("10", "ether");

/** Deploy the "VRFConsumer" contract
*
* @param hre HardhatRuntimeEnvironment object.
*/
const deployVRFConsumer: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployer } = await hre.getNamedAccounts();
const { deploy, log } = hre.deployments;
const { ethers } = hre;

const chainId = await hre.ethers.provider.getNetwork().then(network => network.chainId);
console.log(`The current chain ID is: ${chainId}`);

log("------------------------------------");
// use values from hepler-hardhat-config.ts
const { vrfCoordinatorV2 } = networkConfig[chainId];

let vrfCoordinatorV2Address, subscriptionId;

if (developmentChains.includes(network.name)) {
const vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock");
vrfCoordinatorV2Address = vrfCoordinatorV2Mock.address;
const tx = await vrfCoordinatorV2Mock.createSubscription();
const txReceipt = await tx.wait(1);
subscriptionId = txReceipt.events[0].args.subId.toString(); // grab subId from the event logs of .createSubscription()
await vrfCoordinatorV2Mock.fundSubscription(subscriptionId, FUND_AMOUNT);
} else {
vrfCoordinatorV2Address = vrfCoordinatorV2.address;
subscriptionId = vrfCoordinatorV2.subscriptionId;
}

const { keyHash, callbackGasLimit } = vrfCoordinatorV2;

const args = [vrfCoordinatorV2Address, keyHash, subscriptionId, callbackGasLimit];

const VRFConsumer = await deploy("VRFConsumer", {
from: deployer,
args: args,
log: true,
autoMine: true,
});

// For testing on local network, add nft contract as a valid consumer of the VRFCoordinatorV2Mock contract
if (developmentChains.includes(network.name)) {
const vrfCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock");
await vrfCoordinatorV2Mock.addConsumer(subscriptionId, VRFConsumer.address);
}

// HOW TO VERIFY : https://docs.scaffoldeth.io/deploying/deploy-smart-contracts#4-verify-your-smart-contract
};

export default deployVRFConsumer;
42 changes: 22 additions & 20 deletions packages/hardhat/helper-hardhat-config.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,44 @@
interface NetworkConfigEntryTypes {
name?: string;
vrfCoordinatorV2?: string;
subscriptionId?: string;
gasLane?: string;
callbackGasLimit?: string;
interval?: string;
mintFee?: string;
priceFeeds?: {
name: string;
priceFeedAddress?: {
BTC_USD: string;
ETH_USD: string;
LINK_USD: string;
};
vrfCoordinatorV2: {
address?: string;
subscriptionId?: string;
keyHash?: string;
requestConfirmations?: number;
callbackGasLimit?: string;
numWords?: number;
};
}

const networkConfig: { [key: number]: NetworkConfigEntryTypes } = {
31337: {
name: "hardhat",
gasLane: "0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c",
interval: "30",
callbackGasLimit: "500000", // 500,000 gas
mintFee: "100000000000000000", // 0.1 ETH
vrfCoordinatorV2: {
keyHash: "0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c", // doesn't matter for local network
callbackGasLimit: "500000", // doesn't matter for local network
},
},
11155111: {
name: "sepolia",
vrfCoordinatorV2: "0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625",
subscriptionId: "4707", // vrf.chain.link/sepolia/4707
gasLane: "0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c",
callbackGasLimit: "500000", // 500,000
interval: "30",
mintFee: "100000000000000000", // 0.1 ETH
priceFeeds: {
vrfCoordinatorV2: {
address: "0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625",
keyHash: "0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c", // gas lane ?
subscriptionId: "4707", // replace with your subscription id
callbackGasLimit: "500000", // 500,000 gas
},
priceFeedAddress: {
BTC_USD: "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43",
ETH_USD: "0x694AA1769357215DE4FAC081bf1f309aDC325306",
LINK_USD: "0xc59E3633BAAC79493d908e63626716e204A45EdF",
},
},
};

const developmentChains: string[] = ["hardhat", "localhost"];
const developmentChains: string[] = ["hardhat", "foundry", "localhost"];

export { networkConfig, developmentChains };
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ import { useScaffoldContractRead } from "~~/hooks/scaffold-eth/useScaffoldContra

export const AggregatorV3Consumer = () => {
const { data: latestPrice } = useScaffoldContractRead({
contractName: "PriceFeedConsumer",
contractName: "AggregatorV3Consumer",
functionName: "getLatestPrice",
});

const { data: decimals } = useScaffoldContractRead({
contractName: "PriceFeedConsumer",
contractName: "AggregatorV3Consumer",
functionName: "getDecimals",
});

const { data: description } = useScaffoldContractRead({
contractName: "PriceFeedConsumer",
functionName: "getDescripition", // fix spelling lol
contractName: "AggregatorV3Consumer",
functionName: "getDescription", // fix spelling lol
});

const items = [
Expand Down
66 changes: 66 additions & 0 deletions packages/nextjs/components/vrf/ActionPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { InformationCircleIcon } from "@heroicons/react/24/outline";
import { useScaffoldContractWrite } from "~~/hooks/scaffold-eth/useScaffoldContractWrite";

// import { useScaffoldEventHistory } from "~~/hooks/scaffold-eth/useScaffoldEventHistory";

/**
* TODO: Figure out how to show result using eventSubscriber (gotta handle state!)
* - you gotta combine eventHistory with eventSubscriber to make it work right!
*/
export const ActionPanel = () => {
// useScaffoldEventSubscriber({
// contractName: "YourContract",
// eventName: "GreetingChange",
// // The listener function is called whenever a GreetingChange event is emitted by the contract.
// // Parameters emitted by the event can be destructed using the below example
// // for this example: event GreetingChange(address greetingSetter, string newGreeting, bool premium, uint256 value);
// listener: (logs) => {
// logs.map((log) => {
// const { greetingSetter, value, premium, newGreeting } = log.args;
// console.log(
// "📡 GreetingChange event",
// greetingSetter,
// value,
// premium,
// newGreeting
// );
// });
// },
// });

// const { data: events } = useScaffoldEventHistory({
// contractName: "VRFConsumer",
// eventName: "RandomNumberReceived",
// fromBlock: 4485346n,
// });

const { writeAsync: triggerRequest } = useScaffoldContractWrite({
contractName: "VRFConsumer",
functionName: "requestRandomNumber",
blockConfirmations: 1,
onBlockConfirmation: txnReceipt => {
console.log("Transaction blockHash", txnReceipt.blockHash);
},
});

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

return (
<div className="bg-base-100 rounded-xl p-10 shadow-lg">
<div className="flex justify-center items-center mb-10 gap-2">
<h3 className="text-2xl md:text-3xl text-center font-bold">VRFConsumer</h3>
<div className="tooltip tooltip-accent" data-tip={`put link to contract here`}>
<button>
<InformationCircleIcon className="h-7 w-7" />
</button>
</div>
</div>

<div className="flex justify-center">
<button className="btn btn-accent text-base-200 text-lg" onClick={() => triggerRequest()}>
Request Random
</button>
</div>
</div>
);
};
Loading

0 comments on commit 5b92d01

Please sign in to comment.