Skip to content
This repository was archived by the owner on Jan 30, 2025. It is now read-only.

Implement chainlink interfaces #3

Merged
merged 7 commits into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ broadcast/
contracts/ImageID.sol
tests/Elf.sol

# Autogenerated addresses
cli/addresses/local

# Data from binance
data/
stripped_prices.json
response.json

# Dotenv file
.env

Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/risc0-ethereum"]
path = lib/risc0-ethereum
url = https://github.com/risc0/risc0-ethereum
[submodule "data-provider"]
path = data-provider
url = [email protected]:Diffuse-fi/data-provider
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ Your new project consists of:
- Test your Solidity contracts, integrated with your zkVM program.

```sh
RISC0_DEV_MODE=true forge test -vvv
RISC0_DEV_MODE=true forge test -vvv
```

- Run the same tests, with the full zkVM prover rather than dev-mode, by setting `RISC0_DEV_MODE=false`.
Expand All @@ -113,14 +113,14 @@ Your new project consists of:
RISC0_DEV_MODE=false forge test -vvv
```

Producing the [Groth16 SNARK proofs][Groth16] for this test requires running on an x86 machine with [Docker] installed, or using [Bonsai](#configuring-bonsai). Apple silicon is currently unsupported for local proving, you can find out more info in the relevant issues [here](https://github.com/risc0/risc0/issues/1520) and [here](https://github.com/risc0/risc0/issues/1749).
Producing the [Groth16 SNARK proofs][Groth16] for this test requires running on an x86 machine with [Docker] installed, or using [Bonsai](#configuring-bonsai). Apple silicon is currently unsupported for local proving, you can find out more info in the relevant issues [here](https://github.com/risc0/risc0/issues/1520) and [here](https://github.com/risc0/risc0/issues/1749).

## Develop Your Application

To build your application using the RISC Zero Foundry Template, you’ll need to make changes in three main areas:

- ***Guest Code***: Write the code you want proven in the [methods/guest](./methods/guest/) folder. This code runs off-chain within the RISC Zero zkVM and performs the actual computations. For example, the provided template includes a computation to check if a given number is even and generate a proof of this computation.
- ***Smart Contracts***: Write the on-chain part of your project in the [contracts](./contracts/) folder. The smart contract verifies zkVM proofs and updates the blockchain state based on the results of off-chain computations. For instance, in the [EvenNumber](./contracts/EvenNumber.sol) example, the smart contract verifies a proof that a number is even and stores that number on-chain if the proof is valid.
- ***Smart Contracts***: Write the on-chain part of your project in the [contracts](./contracts/) folder. The smart contract verifies zkVM proofs and updates the blockchain state based on the results of off-chain computations. For instance, in the [DataFeedFeeder](./contracts/DataFeedFeeder.sol) example, the smart contract verifies a proof that a number is even and stores that number on-chain if the proof is valid.
- ***Publisher Application***: Adjust the publisher example in the [apps](./apps/) folder. The publisher application bridges off-chain computation with on-chain verification by submitting proof requests, receiving proofs, and publishing them to the smart contract on Ethereum.

### Configuring Bonsai
Expand Down Expand Up @@ -168,22 +168,22 @@ Below are the primary files in the project directory
│ ├── Cargo.toml
│ └── src
│ └── lib.rs // Utility functions
│ └── bin
│ └── publisher.rs // Example app to publish program results into your app contract
│ └── bin
│ └── publisher.rs // Example app to publish program results into your app contract
├── contracts
│ ├── EvenNumber.sol // Basic example contract for you to modify
│ ├── DataFeedFeeder.sol // Basic example contract for you to modify
│ └── ImageID.sol // Generated contract with the image ID for your zkVM program
├── methods
│ ├── Cargo.toml
│ ├── guest
│ │ ├── Cargo.toml
│ │ └── src
│ │ └── bin // You can add additional guest programs to this folder
│ │ └── is_even.rs // Example guest program for checking if a number is even
│ │ └── json_parser.rs // Example guest program for checking if a number is even
│ └── src
│ └── lib.rs // Compiled image IDs and tests for your guest programs
└── tests
├── EvenNumber.t.sol // Tests for the basic example contract
├── DataFeedFeeder.t.sol // Tests for the basic example contract
└── Elf.sol // Generated contract with paths the guest program ELF files.
```

Expand Down
109 changes: 79 additions & 30 deletions apps/src/bin/publisher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,22 @@ use alloy::{
network::EthereumWallet, providers::ProviderBuilder, signers::local::PrivateKeySigner,
sol_types::SolValue,
};
use alloy_primitives::{Address, U256};
// use alloy_primitives::{Address, U256};
use alloy_primitives::Address;
use anyhow::{Context, Result};
use clap::Parser;
use methods::IS_EVEN_ELF;
use methods::JSON_PARSER_ELF;
use methods::guest_data_structs::{GuestInputType, GuestOutputType};
use risc0_ethereum_contracts::encode_seal;
use risc0_zkvm::{default_prover, ExecutorEnv, ProverOpts, VerifierContext};
use url::Url;
use std::fs;
use std::path::Path;

// `IEvenNumber` interface automatically generated via the alloy `sol!` macro.
// `IDataFeedFeeder` interface automatically generated via the alloy `sol!` macro.
alloy::sol!(
#[sol(rpc, all_derives)]
"../contracts/IEvenNumber.sol"
"../contracts/IDataFeedFeeder.sol"
);

/// Arguments of the publisher CLI.
Expand All @@ -54,10 +57,6 @@ struct Args {
/// Application's contract address on Ethereum
#[clap(long)]
contract: Address,

/// Path to json
#[clap(long)]
json_path: String,
}

fn main() -> Result<()> {
Expand All @@ -72,42 +71,92 @@ fn main() -> Result<()> {
.wallet(wallet)
.on_http(args.rpc_url);

// path to json file where information about prices is stored
let json_path = args.json_path;
let data = fs::read_to_string(json_path).expect("Unable to read file");
let env = ExecutorEnv::builder().write(&data).unwrap().build()?;

let receipt = default_prover()
.prove_with_ctx(
env,
&VerifierContext::default(),
IS_EVEN_ELF,
&ProverOpts::groth16(),
)?
.receipt;
let data_storage_path = Path::new("data/");

let mut max_number = 0;

if data_storage_path.is_dir() {
for entry in fs::read_dir(data_storage_path)? {
let entry = entry?;
if let Some(folder_name) = entry.file_name().to_str() {
if let Ok(number) = folder_name.parse::<u32>() {
if number > max_number {
max_number = number;
}
}
}
}
}

let max_folder_path = data_storage_path.join(format!("{}", max_number));
let seal_path = max_folder_path.join("seal.bin");
let journal_path = max_folder_path.join("journal.bin");
let json_path = max_folder_path.join("prover_input.json");

let already_proven = seal_path.exists() && journal_path.exists();

if !already_proven {
let json_string = fs::read_to_string(json_path).expect("Unable to read file");

let guest_input = GuestInputType {
json_string: String::from(json_string),
currency_pairs: vec![String::from("ETHBTC"), String::from("BTCUSDT"), String::from("ETHUSDT"), String::from("ETHUSDC")],
};

// Encode the seal with the selector.
let seal = encode_seal(&receipt)?;
let env = ExecutorEnv::builder().write(&guest_input).unwrap().build()?;

// Extract the journal from the receipt.
let journal = receipt.journal.bytes.clone();
let receipt = default_prover()
.prove_with_ctx(
env,
&VerifierContext::default(),
JSON_PARSER_ELF,
&ProverOpts::groth16(),
)?
.receipt;

// Encode the seal with the selector.
let seal = encode_seal(&receipt)?;

// Extract the journal from the receipt.
let journal = receipt.journal.bytes.clone();

fs::write(seal_path.clone(), &seal)?;
fs::write(journal_path.clone(), &journal)?;
}

let seal = fs::read(seal_path.clone())?;
let journal = fs::read(journal_path.clone())?;

// Decode Journal: Upon receiving the proof, the application decodes the journal to extract
// the verified numbers. This ensures that the numbers being submitted to the blockchain match
// the numbers that were verified off-chain.
let (btc_price, eth_price, timestamp): (U256, U256, U256) = <(U256, U256, U256)>::abi_decode(&journal, true).context("decoding journal data")?;
let guest_output: GuestOutputType = <GuestOutputType>::abi_decode(&journal, true).context("decoding journal data")?;

const ARRAY_REPEAT_VALUE: std::string::String = String::new();
let mut pair_names = [ARRAY_REPEAT_VALUE; 4];
let mut prices = [0u64; 4];
let mut timestamps = [0u64; 4];

for (i, (first, second, third)) in guest_output.into_iter().enumerate() {
pair_names[i] = first;
prices[i] = second;
timestamps[i] = third;
println!("pair {}. name: {}, price: {}, timestamp:{}", i, pair_names[i], prices[i], timestamps[i]);
}


// Construct function call: Using the IEvenNumber interface, the application constructs
// the ABI-encoded function call for the set function of the EvenNumber contract.
// Construct function call: Using the IDataFeedFeeder interface, the application constructs
// the ABI-encoded function call for the set function of the DataFeedFeeder contract.
// This call includes the verified numbers, the post-state digest, and the seal (proof).
let contract = IEvenNumber::new(args.contract, provider);
let call_builder = contract.set(btc_price, eth_price, timestamp, seal.into());
let contract = IDataFeedFeeder::new(args.contract, provider);
let call_builder = contract.set(pair_names, prices, timestamps, seal.into());

// Initialize the async runtime environment to handle the transaction sending.
let runtime = tokio::runtime::Runtime::new()?;

// Send transaction: Finally, send the transaction to the Ethereum blockchain,
// effectively calling the set function of the EvenNumber contract with the verified number and proof.
// effectively calling the set function of the DataFeedFeeder contract with the verified number and proof.
let pending_tx = runtime.block_on(call_builder.send())?;
runtime.block_on(pending_tx.get_receipt())?;

Expand Down
1 change: 1 addition & 0 deletions cli/addresses/sepolia/BTCUSDT.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x0E6fCA9871cc802cBC22c491B1Fb50574dE00dA7
1 change: 1 addition & 0 deletions cli/addresses/sepolia/ETHBTC.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x62E4901444BcA98974d36F9633339A4Bd9292448
1 change: 1 addition & 0 deletions cli/addresses/sepolia/ETHUSDC.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0xf406556Ec65bA5f45293ad56eF85c64E2188c4cC
1 change: 1 addition & 0 deletions cli/addresses/sepolia/ETHUSDT.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0xe08756b621eaC34BDFBD21B63B1D06d7a0f3C7e1
1 change: 1 addition & 0 deletions cli/addresses/sepolia/feeder.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0x61fF66e21F37F13C87985f95f33C9e22736D7797
1 change: 1 addition & 0 deletions cli/anvil_private_key.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
63 changes: 63 additions & 0 deletions cli/deploy_feeder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import subprocess
import sys
import os
from utils.network import *

def deploy_data_feeder(net):

if net == network_enum.LOCAL:
print("deploying DataFeedFeeder to", net.value)
else:
while(True):
user_input = input("You are deploying DataFeeder to " + net.value + " network. Are you sure? [y/n]: ").lower()
if user_input == 'y':
break
elif user_input == 'n':
print("cancelled execution", file=sys.stderr)
sys.exit(1)
else:
print("Please enter 'y' or 'n'.")
continue


deployment_command = ["forge", "script", rpc_url(net), chain_id(net), "--broadcast", "script/Deploy.s.sol"]
result = run_subprocess(deployment_command, "DataFeedFeeder deployment")
data_feeder_address = result.split("Deployed DataFeedFeeder to ")[1].split("\n")[0].strip()

if (data_feeder_address.find('0x') != 0 or len(data_feeder_address) != 42):
print("Deployment was successfull, but address parsing from server response FAILED", file=sys.stderr)
print("expected address afrer \"Deployed DataFeedFeeder to\"\n", file=sys.stderr)
print("response:", result, file=sys.stderr)
sys.exit(1)

if not os.path.exists(os.path.dirname(address_path(net.value, "feeder"))):
os.makedirs(os.path.dirname(address_path(net.value, "feeder")))

file = open(address_path(net.value, "feeder"), 'w')
file.write(data_feeder_address)
print("wrote address to", address_path(net.value, "feeder"), "\n======================================")
file.close()

file.close()


def request_storage_addresses(net, pair_name):
command = [ "cast", "call", rpc_url(net), get_feeder_address(net.value), "getPairStorageAddress(string)(address)", pair_name]
result = run_subprocess(command, "request DataFeedStorage address for " + pair_name + " ")

file = open(address_path(net.value, pair_name), 'w')
print("wrote address to", address_path(net.value, pair_name), "\n======================================")
file.write(result.strip())
file.close()


parser = argparse.ArgumentParser(description="Data feeder parameters")
parser.add_argument('-n', '--network', type=parse_network, required=True, help="Choose network (local, sepolia, eth_mainnet)")

args = parser.parse_args()


deploy_data_feeder(args.network)

for p in pair_name_enum:
request_storage_addresses(args.network, p.value)
67 changes: 67 additions & 0 deletions cli/feed_feeder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import sys
import argparse
from utils.network import *


def prepare_json (_binance_flag, test_data_1, test_data_2):

latest_data = -1

os.makedirs("data/", exist_ok=True)

existing_data = os.listdir("data/")
for d in existing_data:
if (d.isdigit() == True):
latest_data = max(latest_data, int(d))

new_data_dir = "data/" + str(latest_data + 1) + "/"
os.makedirs(new_data_dir)

files = ["prover_input.json", "seal.bin", "journal.bin"]

if test_data_1 == True:
for f in files:
run_subprocess(["cp", "test_data_1/0/" + f, new_data_dir + f], "copy" + " test_data_1/0/" + f + " to" + new_data_dir + f)

elif test_data_2 == True:
for f in files:
run_subprocess(["cp", "test_data_2/0/" + f, new_data_dir + f], "copy" + " test_data_2/0/" + f + " to " + new_data_dir + f)

elif _binance_flag == True:
run_subprocess(["python3", "data-provider/script.py"], "request from binance")
run_subprocess(["cp", "stripped_prices.json", new_data_dir + "prover_input.json"], "copy binance data to " + new_data_dir)

else:
print("expected either --test-data-1 or --test-data-2 or --binance flag")
sys.exit(1)


def feed_data(net):
command = [
"cargo",
"run",
"--bin",
"publisher",
"--",
chain_id(net),
rpc_url(net),
"--contract=" + get_feeder_address(net.value)
]

run_subprocess(command, "DataFeeder feeding")


parser = argparse.ArgumentParser(description="Data feeder parameters")
parser.add_argument('-n', '--network', type=parse_network, required=True, help="Choose network (local, sepolia, eth_mainnet)")

data_source_group = parser.add_mutually_exclusive_group()
data_source_group.add_argument('--binance', action='store_true', help='Request data from binance and feed')
data_source_group.add_argument('--test-data-1', action='store_true', help='Take dataset from test_data_1/')
data_source_group.add_argument('--test-data-2', action='store_true', help='Take dataset from test_data_2')

args = parser.parse_args()


prepare_json(args.binance, args.test_data_1, args.test_data_2)
feed_data(args.network)

Loading
Loading