From 8208a82f1f0235d5c22e7b178f3b9553901d20d2 Mon Sep 17 00:00:00 2001 From: bitful-pannul Date: Thu, 2 May 2024 11:47:18 -0400 Subject: [PATCH 1/2] add write to eth cookbook --- src/SUMMARY.md | 1 + src/cookbook/writing_data_to_eth.md | 310 ++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 src/cookbook/writing_data_to_eth.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 67ce3b9e..0ae733b4 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -49,6 +49,7 @@ - [Intro to Web UI with File Transfer](./cookbook/file_transfer_ui.md) - [Writing and Running Scripts](./cookbook/writing_scripts.md) - [Reading Data from ETH](./cookbook/reading_data_from_eth.md) + - [Writing Data to ETH](./cookbook/writing_data_to_eth.md) - [Use ZK proofs with SP1](./cookbook/zk_with_sp1.md) - [API Reference](./api_reference.md) - [ETH Provider API](./apis/eth_provider.md) diff --git a/src/cookbook/writing_data_to_eth.md b/src/cookbook/writing_data_to_eth.md new file mode 100644 index 00000000..c073fe93 --- /dev/null +++ b/src/cookbook/writing_data_to_eth.md @@ -0,0 +1,310 @@ +# Writing Data to ETH + +For this cookbook entry, we will create and deploy a simple `Counter` contract onto a fake local chain, and write a kinode app to interact with it. + +Using `kit`, create a new project with the `echo` template: + +``` +kit new counter --template echo +``` + +We can now create a `contracts` directory within our counter, using `forge init contracts`. If foundry is not installed, it can be installed with: + +``` +curl -L https://foundry.paradigm.xyz | bash +``` + +You can see the simple `Counter.sol` contract in `contracts/src/Counter.sol`: + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } +} +``` + +We can write a simple script to deploy it at a predictable address, create the file `scripts/Deploy.s.sol`: + +```solidity +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script, console, VmSafe} from "forge-std/Script.sol"; +import {Counter} from "../src/Counter.sol"; + +contract DeployScript is Script { + function setUp() public {} + + function run() public { + VmSafe.Wallet memory wallet = vm.createWallet( + 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ); + vm.startBroadcast(wallet.privateKey); + + Counter counter = new Counter(); + console.log("Counter deployed at address: ", address(counter)); + vm.stopBroadcast(); + } +} +``` + +Now we boot a fakechain, either with `kit f` which boots one at port 8545 in the background, or manually with `anvil --load-state kinostate.json`. + +Then we can run `forge script --rpc-url http://localhost:8545 script/Deploy.s.sol --broadcast` we'll see a printout that looks something like this: + +``` +== Logs == + Counter deployed at address: 0x610178dA211FEF7D417bC0e6FeD39F05609AD788 +``` + +Great! Now let's write the kinode app to interact with it! + +We're going to use some functions from the `eth` library in `kinode_process_lib`: + +```rust +use kinode_process_lib::eth; +``` + +Also we'll need to request the capability to message `eth:distro:sys`, so we can add it to the `request_capabilities` field in `pkg/manifest.json`. + +Next, we'll need some sort of ABI in order to interact with the contracts. The crate `alloy-sol-types` gives us a solidity macro to either define contracts from JSON, or directly in the rust code. We'll add it to `counter/Cargo.toml`: + +``` +alloy-sol-types = "0.7.0" +``` + +Now, importing the following types from the crate: + +```rust +use alloy_sol_types::{sol, SolCall, SolValue}; +``` + +We can do the following: + +```rust +sol! { + contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } + } +} +``` + +Pretty cool huh? We can now do things like define a setNumber() call just like this: + +```rust +let contract_call = setNumberCall { newNumber: U256::from(58)}; +``` + +We'll start with a simple setup to read the current count, and print it out! + +```rust +use kinode_process_lib::{await_message, call_init, eth::{Address as EthAddress, Provider, TransactionInput, TransactionRequest, U256}, println, Address, Response}; +use alloy_sol_types::{sol, SolCall, SolValue}; +use std::str::FromStr; + +wit_bindgen::generate!({ + path: "wit", + world: "process", +}); + +sol! { + contract Counter { + uint256 public number; + + function setNumber(uint256 newNumber) public { + number = newNumber; + } + + function increment() public { + number++; + } + } +} + +pub const COUNTER_ADDRESS: &str = "0x610178dA211FEF7D417bC0e6FeD39F05609AD788"; + +fn read(provider: &Provider) -> anyhow::Result { + let counter_address = EthAddress::from_str(COUNTER_ADDRESS).unwrap(); + let count = Counter::numberCall {}.abi_encode(); + + let tx = TransactionRequest::default() + .to(counter_address) + .input(TransactionInput::new(count.into())); + let x = provider.call(tx, None); + + match x { + Ok(b) => { + let number = U256::abi_decode(&b, false).unwrap(); + println!("current count: {:?}", number.to::()); + Ok(number) + } + Err(e) => { + println!("error getting current count: {:?}", e); + Err(anyhow::anyhow!("error getting current count: {:?}", e)) + } + } +} + +call_init!(init); +fn init(our: Address) { + println!("begin"); + + let provider = Provider::new(31337, 5); + + let _count = read(&provider); + + loop { + match handle_message(&our, &provider) { + Ok(()) => {} + Err(e) => { + println!("error: {:?}", e); + } + }; + } +} +``` + +Now, we'll add the 2 writes that are possible: increment() and setNumber(newNumber). To do this, we'll need to define a wallet, and import a few new crates: + +``` +alloy-primitives = "0.7.0" +alloy-rlp = "0.3.4" +alloy-signer-wallet = { git = "https://github.com/alloy-rs/alloy", rev = "cad7935" } +alloy-consensus = { git = "https://github.com/alloy-rs/alloy", rev = "cad7935" } +alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "cad7935" } +alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "cad7935" } +``` + +We'll also define a simple enum so we can call the program with each of the 3 actions: + +```rust +#[derive(Debug, Deserialize, Serialize)] +pub enum CounterAction { + Increment, + Read, + SetNumber(u64), +} +``` + +When creating a wallet, we can use one of the funded addresses on the anvil fakechain, like so: + +```rust +use alloy_consensus::{SignableTransaction, TxEnvelope, TxLegacy}; +use alloy_network::TxSignerSync; +use alloy_primitives::TxKind; +use alloy_rlp::Encodable; +use alloy_rpc_types::TransactionRequest; +use alloy_signer_wallet::LocalWallet; +use alloy_sol_types::{sol, SolCall, SolValue}; +use kinode_process_lib::{ + await_message, call_init, + eth::{Address as EthAddress, Provider, TransactionInput, U256}, + println, Address, Response, +}; + +let wallet = + LocalWallet::from_str("0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80") + .unwrap(); +``` + +First, branching on the enum type `Increment`, let's call the increment() function with no arguments: + +```rust + CounterAction::Increment => { + let increment = Counter::incrementCall {}.abi_encode(); + let nonce = provider + .get_transaction_count(wallet.address(), None) + .unwrap() + .to::(); + + let mut tx = TxLegacy { + chain_id: Some(31337), + nonce: nonce, + to: TxKind::Call(EthAddress::from_str(COUNTER_ADDRESS).unwrap()), + gas_limit: 100000, + gas_price: 100000000, + input: increment.into(), + ..Default::default() + }; + + let sig = wallet.sign_transaction_sync(&mut tx)?; + let signed = TxEnvelope::from(tx.into_signed(sig)); + let mut buf = vec![]; + signed.encode(&mut buf); + + let tx_hash = provider.send_raw_transaction(buf.into()); + println!("tx_hash: {:?}", tx_hash); + } +``` + +Note how we do provider.get_transaction_count() to get our current nonce! + +Next, let's do the same for setNumber! + +```rust + CounterAction::SetNumber(n) => { + let set_number = Counter::setNumberCall { + newNumber: U256::from(n), + } + .abi_encode(); + + let nonce = provider + .get_transaction_count(wallet.address(), None) + .unwrap() + .to::(); + + let mut tx = TxLegacy { + chain_id: Some(31337), + nonce: nonce, + to: TxKind::Call(EthAddress::from_str(COUNTER_ADDRESS).unwrap()), + gas_limit: 100000, + gas_price: 100000000, + input: set_number.into(), + ..Default::default() + }; + + let sig = wallet.sign_transaction_sync(&mut tx)?; + let signed = TxEnvelope::from(tx.into_signed(sig)); + let mut buf = vec![]; + signed.encode(&mut buf); + + let tx_hash = provider.send_raw_transaction(buf.into()); + println!("tx_hash: {:?}", tx_hash); + } +``` + +Nice! Putting it all together, we can build and start the package on a fake node (`kit f` if you don't have one running), `kit bs`. + +``` +fake.dev > m our@counter:counter:template.os '{"SetNumber": 55}' +counter:template.os: tx_hash: Ok(0x5dba574f2a9a2c095cee960868433e23c64b685966fba57568c4d6a0fd99ef6c) + +fake.dev > m our@counter:counter:template.os "Read" +counter:template.os: current count: 55 + +fake.dev > m our@counter:counter:template.os "Increment" +counter:template.os: tx_hash: Ok(0xc38ee230c2605c294a37794244334c0d20a5b5e090704b34f4a7998021418d7b) + +fake.dev > m our@counter:counter:template.os "Read" +counter:template.os: current count: 56 +``` + +You can find these steps outlined by commit in the counter [example repo!](https://github.com/bitful-pannul/counterexample) From b7e337e295383945716f5ce18badba7739b6ab95 Mon Sep 17 00:00:00 2001 From: bitful-pannul Date: Thu, 9 May 2024 18:58:59 -0400 Subject: [PATCH 2/2] formatting --- src/cookbook/writing_data_to_eth.md | 33 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/cookbook/writing_data_to_eth.md b/src/cookbook/writing_data_to_eth.md index c073fe93..5d658ee5 100644 --- a/src/cookbook/writing_data_to_eth.md +++ b/src/cookbook/writing_data_to_eth.md @@ -1,6 +1,6 @@ # Writing Data to ETH -For this cookbook entry, we will create and deploy a simple `Counter` contract onto a fake local chain, and write a kinode app to interact with it. +For this cookbook entry, let's create and deploy a simple `Counter` contract onto a fake local chain, and write a kinode app to interact with it. Using `kit`, create a new project with the `echo` template: @@ -8,7 +8,7 @@ Using `kit`, create a new project with the `echo` template: kit new counter --template echo ``` -We can now create a `contracts` directory within our counter, using `forge init contracts`. If foundry is not installed, it can be installed with: +Now let's create a `contracts` directory within our counter, using `forge init contracts`. If foundry is not installed, it can be installed with: ``` curl -L https://foundry.paradigm.xyz | bash @@ -33,7 +33,7 @@ contract Counter { } ``` -We can write a simple script to deploy it at a predictable address, create the file `scripts/Deploy.s.sol`: +You can write a simple script to deploy it at a predictable address, create the file `scripts/Deploy.s.sol`: ```solidity // SPDX-License-Identifier: UNLICENSED @@ -58,9 +58,15 @@ contract DeployScript is Script { } ``` -Now we boot a fakechain, either with `kit f` which boots one at port 8545 in the background, or manually with `anvil --load-state kinostate.json`. +Now let's boot a fakechain, either with `kit f` which boots one at port 8545 in the background, or with `kit c`. -Then we can run `forge script --rpc-url http://localhost:8545 script/Deploy.s.sol --broadcast` we'll see a printout that looks something like this: +Then you can run: + +``` +forge script --rpc-url http://localhost:8545 script/Deploy.s.sol --broadcast +``` + +you'll see a printout that looks something like this: ``` == Logs == @@ -69,7 +75,7 @@ Then we can run `forge script --rpc-url http://localhost:8545 script/Deploy.s.so Great! Now let's write the kinode app to interact with it! -We're going to use some functions from the `eth` library in `kinode_process_lib`: +You're going to use some functions from the `eth` library in `kinode_process_lib`: ```rust use kinode_process_lib::eth; @@ -107,13 +113,13 @@ sol! { } ``` -Pretty cool huh? We can now do things like define a setNumber() call just like this: +Pretty cool, you can now do things like define a setNumber() call just like this: ```rust let contract_call = setNumberCall { newNumber: U256::from(58)}; ``` -We'll start with a simple setup to read the current count, and print it out! +Start with a simple setup to read the current count, and print it out! ```rust use kinode_process_lib::{await_message, call_init, eth::{Address as EthAddress, Provider, TransactionInput, TransactionRequest, U256}, println, Address, Response}; @@ -182,7 +188,8 @@ fn init(our: Address) { } ``` -Now, we'll add the 2 writes that are possible: increment() and setNumber(newNumber). To do this, we'll need to define a wallet, and import a few new crates: +Now, let's add the 2 writes that are possible: increment() and setNumber(newNumber). +To do this, you'll need to define a wallet, and import a few new crates: ``` alloy-primitives = "0.7.0" @@ -193,7 +200,7 @@ alloy-network = { git = "https://github.com/alloy-rs/alloy", rev = "cad7935" } alloy-rpc-types = { git = "https://github.com/alloy-rs/alloy", rev = "cad7935" } ``` -We'll also define a simple enum so we can call the program with each of the 3 actions: +You'll also define a simple enum so you can call the program with each of the 3 actions: ```rust #[derive(Debug, Deserialize, Serialize)] @@ -204,7 +211,7 @@ pub enum CounterAction { } ``` -When creating a wallet, we can use one of the funded addresses on the anvil fakechain, like so: +When creating a wallet, you can use one of the funded addresses on the anvil fakechain, like so: ```rust use alloy_consensus::{SignableTransaction, TxEnvelope, TxLegacy}; @@ -255,7 +262,7 @@ First, branching on the enum type `Increment`, let's call the increment() functi } ``` -Note how we do provider.get_transaction_count() to get our current nonce! +Note how you can do provider.get_transaction_count() to get the current nonce of the account! Next, let's do the same for setNumber! @@ -291,7 +298,7 @@ Next, let's do the same for setNumber! } ``` -Nice! Putting it all together, we can build and start the package on a fake node (`kit f` if you don't have one running), `kit bs`. +Nice! Putting it all together, you can build and start the package on a fake node (`kit f` if you don't have one running), `kit bs`. ``` fake.dev > m our@counter:counter:template.os '{"SetNumber": 55}'