From 1df2ff97893cd6be76645afbece9ed5f872bb2d1 Mon Sep 17 00:00:00 2001 From: PatrickAlphac <54278053+PatrickAlphaC@users.noreply.github.com> Date: Tue, 2 Aug 2022 21:35:42 -0400 Subject: [PATCH] fixed function selectors on dynamic arrays, added scripts to "see" the whole process end to end --- README.md | 74 ++++++++++++++++++++++++++ contracts/KeepersConsumer.vy | 2 +- contracts/VRFConsumerV2.vy | 17 ++++-- scripts/create_subscription.py | 2 + scripts/helper_config.py | 2 +- scripts/read_counter.py | 17 ++++++ scripts/read_price_feed.py | 17 ++++++ scripts/request_and_read_randomness.py | 38 +++++++++++++ 8 files changed, 162 insertions(+), 7 deletions(-) create mode 100644 scripts/read_counter.py create mode 100644 scripts/read_price_feed.py create mode 100644 scripts/request_and_read_randomness.py diff --git a/README.md b/README.md index 0e91a7a..d5b5a9f 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ It shows how to use the these frameworks and languages as well as the following - [Deploy to a local or adhoc network](#deploy-to-a-local-or-adhoc-network) - [Deploy to a mainnet or test network](#deploy-to-a-mainnet-or-test-network) - [Interacting with Contracts](#interacting-with-contracts) + - [Price Feed Consumer](#price-feed-consumer-1) + - [VRF Consumer](#vrf-consumer) + - [Keeper Consumer](#keeper-consumer) - [Miscellaneous](#miscellaneous) - [Contributing](#contributing) - [Resources](#resources) @@ -189,6 +192,77 @@ To interact with contracts, we recommend using the console. ape console --network ethereum:rinkeby:alchemy ``` +Or, you can follow along and run the scripts to see the end-to-end functionaltiy. + +#### Price Feed Consumer + +1. Deploy the contract + +``` +ape run scripts/deploy_price_feed_consumer.py --network ethereum:rinkeby:alchemy +``` + +2. Read it + +``` +ape run scripts/read_price_feed.py --network ethereum:rinkeby:alchemy +``` + + + +#### VRF Consumer + +1. Create subscription and fund it with LINK. + +You can do that with the script, or going to the UI at [vrf.chain.link](https://vrf.chain.link) + +``` +ape run scripts/create_subscription.py --network ethereum:rinkeby:alchemy +``` + +2. Update your `helper_config.py` with your subscription Id. + +3. Deploy vrf consumer + +``` +ape run scripts/deploy_vrf_consumer.py --network ethereum:rinkeby:alchemy +``` + +4. [Add your contract/consumer to the VRF UI](https://docs.chain.link/docs/get-a-random-number/#create-and-fund-a-subscription) + + +5. Request a random number and wait for a response + + +``` +ape run scripts/request_and_read_randomness.py --network ethereum:rinkeby:alchemy +``` + +#### Keeper Consumer + +1. Deploy the contract + +``` +ape run scripts/deploy_keepers_consumer.py --network ethereum:rinkeby:alchemy +``` + +2. Register your upkeep on the Keepers UI + +You can go to [keepers.chain.link](https://keepers.chain.link/new-custom-logic) + +3. Watch for your counter to automatically start going up! + +After a delay, run: + +``` +ape run scripts/read_counter.py --network ethereum:rinkeby:alchemy +``` + +And it should be updated! + + + + # Miscellaneous 1. Testing and forking is a bit tricky at the moment. diff --git a/contracts/KeepersConsumer.vy b/contracts/KeepersConsumer.vy index 7e37cd4..c7e8c4b 100644 --- a/contracts/KeepersConsumer.vy +++ b/contracts/KeepersConsumer.vy @@ -13,7 +13,7 @@ def __init__(update_interval: uint256): @external @view -def checkUpkeep(checkData: Bytes[1]) -> (bool, Bytes[1]): +def checkUpkeep(checkData: Bytes[32]) -> (bool, Bytes[32]): upkeep_needed: bool = (block.timestamp - self.last_time_stamp) > INTERVAL return(upkeep_needed, b"\x00") diff --git a/contracts/VRFConsumerV2.vy b/contracts/VRFConsumerV2.vy index b6e7eed..27cd983 100644 --- a/contracts/VRFConsumerV2.vy +++ b/contracts/VRFConsumerV2.vy @@ -3,6 +3,7 @@ import interfaces.VRFCoordinatorV2 as VRFCoordinatorV2 NUM_WORDS: constant(uint32) = 1 +MAX_ARRAY_SIZE: constant(uint256) = 10 REQUEST_CONFIRMATIONS: constant(uint16) = 3 CALLBACK_GAS_LIMIT: constant(uint32) = 100000 @@ -31,13 +32,19 @@ def request_random_words(): ) @internal -def fulfillRandomWords(request_id: uint256, _random_words: uint256[NUM_WORDS]): - self.random_words = _random_words - log ReturnedRandomness(_random_words) +def fulfillRandomWords(request_id: uint256, _random_words: DynArray[uint256, MAX_ARRAY_SIZE]): + self.random_words = [_random_words[0]] + log ReturnedRandomness(self.random_words) # In solidity, this is the equivalent of inheriting the VRFConsumerBaseV2 # Vyper doesn't have inheritance, so we just add the function here +# Also, we need to use `DynArray` so our function selector is the same as the one Chainlink VRF is looking for: +# Function Signature: rawFulfillRandomWords(uint256,uint256[]) +# Function selector: 0x1fe543e3 @external -def rawFulfillRandomWords(requestId: uint256, randomWords: uint256[NUM_WORDS]): +def rawFulfillRandomWords(requestId: uint256, randomWords: DynArray[uint256, MAX_ARRAY_SIZE]): assert msg.sender == self.vrf_coordinator.address, "Only coordinator can fulfill!" - self.fulfillRandomWords(requestId, randomWords) \ No newline at end of file + myBytes: Bytes[4] = b"\x00" + myString: String[4] = convert(myBytes, String[4]) + self.fulfillRandomWords(requestId, randomWords) + diff --git a/scripts/create_subscription.py b/scripts/create_subscription.py index 96f6b54..50c4a2e 100644 --- a/scripts/create_subscription.py +++ b/scripts/create_subscription.py @@ -29,6 +29,8 @@ def create_subscription(): def fund_subscription(subscription_id=1): print("Funding subscription...") + ecosystem = networks.active_provider.network.ecosystem.name + chain_name = networks.active_provider.network.name account = get_account() fund_amount = network_config[ecosystem][chain_name].get("fund_amount", False) link_token = get_or_deploy_contract("LinkToken") diff --git a/scripts/helper_config.py b/scripts/helper_config.py index d9e9ffc..7a1e11c 100644 --- a/scripts/helper_config.py +++ b/scripts/helper_config.py @@ -13,7 +13,7 @@ "fund_amount": 5_000000000000000000, }, "rinkeby": { - "subscription_id": 0, # Add your own subscription Id here! + "subscription_id": 9757, # Add your own subscription Id here! "key_hash": "0xd89b2bf150e3b9e13446986e571fb9cab24b13cea0a43ea20a6049a85cc807cc", "update_interval": 60, "fund_amount": 5_000000000000000000, diff --git a/scripts/read_counter.py b/scripts/read_counter.py new file mode 100644 index 0000000..3ee1ed1 --- /dev/null +++ b/scripts/read_counter.py @@ -0,0 +1,17 @@ +from ape import project, accounts, networks +from scripts.helper_functions import get_account, get_or_deploy_contract + + +def read_counter(): + account = get_account() + ecosystem = networks.active_provider.network.ecosystem.name + chain_name = networks.active_provider.network.name + + keepers_consumer = project.KeepersConsumer.deployments[-1] + response = keepers_consumer.counter() + print(f"The current count is {response}") + return keepers_consumer + + +def main(): + read_counter() diff --git a/scripts/read_price_feed.py b/scripts/read_price_feed.py new file mode 100644 index 0000000..ee4e887 --- /dev/null +++ b/scripts/read_price_feed.py @@ -0,0 +1,17 @@ +from ape import project, accounts, networks +from scripts.helper_functions import get_account, get_or_deploy_contract + + +def read_price_feed(): + account = get_account() + ecosystem = networks.active_provider.network.ecosystem.name + chain_name = networks.active_provider.network.name + + price_feed_consumer = project.PriceConsumer.deployments[-1] + response = price_feed_consumer.get_latest_price() + print(f"The current price of ETH is {response}") + return price_feed_consumer + + +def main(): + read_price_feed() diff --git a/scripts/request_and_read_randomness.py b/scripts/request_and_read_randomness.py new file mode 100644 index 0000000..c920c64 --- /dev/null +++ b/scripts/request_and_read_randomness.py @@ -0,0 +1,38 @@ +from ape import project, accounts, networks +from scripts.helper_functions import get_account, get_or_deploy_contract +import time + + +def request_and_read_randomness(): + account = get_account() + ecosystem = networks.active_provider.network.ecosystem.name + chain_name = networks.active_provider.network.name + + vrf_consumer = project.VRConsumerV2.deployments[-1] + request_tx = vrf_consumer.request_random_words(sender=account) + + print("Request sent! Let's wait for a response...") + + randomness = wait_for_randomness(vrf_consumer) + if randomness: + print(f"The random number was {randomness}") + else: + print("No random number found") + + +def wait_for_randomness(vrf_consumer, timeout=200, poll_interval=2): + print("Waiting for random response...") + start_time = time.time() + current_time = time.time() + while current_time - start_time < timeout: + response = vrf_consumer.random_words(0) + if response > 0: + return response + time.sleep(poll_interval) + current_time = time.time() + print("Done waiting!") + return None + + +def main(): + request_and_read_randomness()