Skip to content

Commit 31df41e

Browse files
authored
Merge pull request #24 from ddimaria/features/implement-basic-erc20-functionality
Features/implement basic erc20 functionality
2 parents 4187832 + 21e7081 commit 31df41e

File tree

18 files changed

+354
-120
lines changed

18 files changed

+354
-120
lines changed

README.md

Lines changed: 63 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ This repo is designed to train Rust developers on intermediate and advanced Rust
6060
- [Contracts](#contracts)
6161
- [WIT](#wit)
6262
- [Sample Contract - Erc20](#sample-contract---erc20)
63+
- [Invoking a Contract Function](#invoking-a-contract-function)
6364
- [Web3](#web3)
6465
- [Sample Usage](#sample-usage)
6566
- [Types](#types)
@@ -735,8 +736,17 @@ fn process_transaction<'a>(
735736
contract_address = self.accounts.add_contract_account(&from, data).ok();
736737
Ok(())
737738
}
738-
TransactionKind::ContractExecution(_from, _to, _data) => {
739-
unimplemented!()
739+
TransactionKind::ContractExecution(_from, to, data) => {
740+
let code = self
741+
.accounts
742+
.get_account(&to)?
743+
.code_hash
744+
.ok_or_else(|| ChainError::NotAContractAccount(to.to_string()))?;
745+
let (function, params): (&str, Vec<&str>) = bincode::deserialize(&data)?;
746+
747+
// call the function in the contract
748+
runtime::contract::call_function(&code, function, &params)
749+
.map_err(|e| ChainError::RuntimeError(to.to_string(), e.to_string()))
740750
}
741751
}?;
742752

@@ -777,9 +787,43 @@ pub fn add_contract_account(&mut self, key: &Account, data: Bytes) -> Result<Acc
777787

778788
Code received gets added to the `to` account's `code_hash` attribute.
779789

780-
_TODO: document processing a contract execution_
790+
Contract execution involves calling a function in the contract in a WASM virtual machine (Wasmtime). This sandboxing isolates contract execution from the rest of the blockchain. We first must get the executable code from account storage:
781791

782-
After the transaction is processed, the `from` account's `nonce` is updated. A `transaction receipt` is created and returned from the function.
792+
```rust
793+
let code = self
794+
.accounts
795+
.get_account(&to)?
796+
.code_hash
797+
.ok_or_else(|| ChainError::NotAContractAccount(to.to_string()))?;
798+
```
799+
800+
Now we just extract the function name and the function parameters from the `data` node in the transaction request:
801+
802+
```rust
803+
let (function, params): (&str, Vec<&str>) = bincode::deserialize(&data)?;
804+
```
805+
806+
For example, let's say we want to invoke the `construct` function. The function signature of `construct` contract function is:
807+
808+
```rust
809+
fn construct(name: String, symbol: String) {}
810+
```
811+
812+
We serialize the parameter types and values:
813+
814+
815+
```rust
816+
// ["Param 1 Type", "Param 1 Value", "Param 2 Type", "Param 2 Value"]
817+
let params = ["String", "Rust Coin", "String", "RustCoin"];
818+
```
819+
820+
We can now invoke the `construct` function:
821+
822+
```rust
823+
runtime::contract::call_function(&code, "construct", &params)?;
824+
```
825+
826+
After we've handled one of the 3 transaction types, the `from` account's `nonce` is updated. A `transaction receipt` is created and returned from the function.
783827

784828
## Organization
785829

@@ -849,7 +893,7 @@ use wit_bindgen_guest_rust::*;
849893

850894
wit_bindgen_guest_rust::generate!({path: "../erc20/erc20.wit", world: "erc20"});
851895

852-
struct Erc20 {}
896+
struct Erc20;
853897

854898
export_contract!(Erc20);
855899

@@ -868,6 +912,19 @@ impl erc20::Erc20 for Erc20 {
868912
}
869913
```
870914

915+
#### Invoking a Contract Function
916+
917+
This code can convert the textual representation of a contract function call to a function call within the wasmtime runtime.
918+
Parameters are listed in pairs of parameter type and paramater value.
919+
920+
```rust
921+
let bytes = include_bytes!("./../../target/wasm32-unknown-unknown/release/erc20_wit.wasm");
922+
let function_name = "construct";
923+
let params = &["String", "Rust Coin", "String", "RustCoin"];
924+
925+
call_function(bytes, function_name, params)?;
926+
```
927+
871928
### Web3
872929

873930
The [web3](web3) crate is a naive implementation of a Web3 interface.
@@ -887,7 +944,7 @@ let block_number = web3.get_block_number().await?;
887944
let block = web3.get_block(*block_number).await?;
888945

889946
let contract =
890-
include_bytes!("./../../contracts/artifacts/contracts/ERC20.sol/RustCoinToken.json").to_vec();
947+
include_bytes!("./../../target/wasm32-unknown-unknown/release/erc20_wit.wasm").to_vec();
891948
let tx_hash = web3.deploy(all_accounts[0], &contract).await?;
892949
let receipt = web3.transaction_receipt(tx_hash).await?;
893950
let code = web3.code(receipt.contract_address.unwrap(), None).await?;

chain/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ lazy_static = "1.4.0"
1717
proc_macros = { path = "../proc_macros" }
1818
rayon = "1.5.3"
1919
rocksdb = "0.19.0"
20+
runtime = { path = "../runtime" }
2021
serde_json = { version = "1.0", features = ["raw_value"] }
2122
serde = "1"
2223
thiserror = "1.0"

chain/src/blockchain.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,17 @@ impl BlockChain {
232232
contract_address = self.accounts.add_contract_account(&from, data).ok();
233233
Ok(())
234234
}
235-
TransactionKind::ContractExecution(_from, _to, _data) => {
236-
unimplemented!()
235+
TransactionKind::ContractExecution(_from, to, data) => {
236+
let code = self
237+
.accounts
238+
.get_account(&to)?
239+
.code_hash
240+
.ok_or_else(|| ChainError::NotAContractAccount(to.to_string()))?;
241+
let (function, params): (&str, Vec<&str>) = bincode::deserialize(&data)?;
242+
243+
// call the function in the contract
244+
runtime::contract::call_function(&code, function, &params)
245+
.map_err(|e| ChainError::RuntimeError(to.to_string(), e.to_string()))
237246
}
238247
}?;
239248

chain/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ pub enum ChainError {
5757
#[error("Nonce {0} too low for account {1}")]
5858
NonceTooLow(String, String),
5959

60+
#[error("Account {0} is not a contract account")]
61+
NotAContractAccount(String),
62+
63+
#[error("Error executing contract at address {0}: {1}")]
64+
RuntimeError(String, String),
65+
6066
#[error("Could not serialize: {0}")]
6167
SerializeError(String),
6268

contracts/erc20/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@ edition = "2021"
77
crate-type = ["cdylib"]
88

99
[dependencies]
10-
wit-bindgen-guest-rust = { git = "https://github.com/bytecodealliance/wit-bindgen" }
10+
wit-bindgen = { version = "0.4.0" }

contracts/erc20/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
The [contracts](contracts) directory holds the WASM source code.
44
Using [wit-bindgen](https://github.com/bytecodealliance/wit-bindgen), we can greatly simplify dealing with complex types.
55
The [WIT format](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md) specifies a language for generating WASM code.
6+
67
## Contracts
78

89
### WIT

contracts/erc20/erc20.wit

Lines changed: 0 additions & 7 deletions
This file was deleted.

contracts/erc20/src/lib.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,19 @@
11
#![allow(unused)]
2-
use wit_bindgen_guest_rust::*;
2+
use std::collections::HashMap;
33

4-
wit_bindgen_guest_rust::generate!({path: "../erc20/erc20.wit", world: "erc20"});
4+
wit_bindgen::generate!("erc20");
55

6-
struct Erc20 {}
6+
pub struct Erc20;
7+
8+
pub struct State {
9+
name: String,
10+
symbol: String,
11+
balances: HashMap<String, u64>,
12+
}
713

814
export_contract!(Erc20);
915

10-
impl erc20::Erc20 for Erc20 {
16+
impl Contract for Erc20 {
1117
fn construct(name: String, symbol: String) {
1218
println!("name {}, symbol", symbol);
1319
}

contracts/erc20/wit/erc20.wit

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
default world contract {
2+
export construct: func(name: string, symbol: string)
3+
export mint: func(account: string, amount: u64)
4+
export transfer: func(to: string, amount: u64)
5+
}

runtime/Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ edition = "2021"
77
[dependencies]
88
anyhow = "1.0.68"
99
env_logger = "0.10.0"
10+
paste = "1.0.12"
1011
thiserror = "1.0.38"
1112
tracing = "0.1.34"
1213
tracing-subscriber = { version = "0.3.15", features = ["env-filter"] }
13-
types = { path = "../types" }
14-
wasmtime = { git = "https://github.com/bytecodealliance/wasmtime", features = [
15-
"component-model",
16-
] }
17-
wit-component = "*"
14+
wasmtime = { version = "6.0.1", features = ["component-model"] }
15+
wit-component = "0.7.3"
16+
wit-bindgen = { version = "0.4.0" }
1817

1918

2019
[dev-dependencies]
2120
test-log = { version = "0.2.11", features = ["trace"] }
21+
types = { path = "../types" }

0 commit comments

Comments
 (0)