Skip to content

Commit

Permalink
Implement different type of contract return (#65)
Browse files Browse the repository at this point in the history
* Implement different type of contract return
  • Loading branch information
yanganto committed Jun 22, 2021
1 parent ef42d81 commit 644f41b
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Build contract
run: nix-shell --run "update-contract"

- name: default feature test
- name: Default feature test
run: nix-shell --run 'run-test default'

- name: Token feature test
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/doc.yml
Expand Up @@ -16,7 +16,7 @@ jobs:
nix_path: nixpkgs=channel:nixos-21.05-small

- name: Build Documentation
run: nix-shell --run "cargo doc -p sewup --features=kv"
run: nix-shell --run "cargo doc -p sewup -p sewup-derive --features=kv"

- name: Deploy Documentation
env:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,4 +5,5 @@ shell.nix
resources/test/erc20_contract.wasm
resources/test/kv_contract.wasm
resources/test/default_contract.wasm
resources/test/rusty_contract.wasm
.direnv
1 change: 1 addition & 0 deletions Cargo.toml
Expand Up @@ -7,4 +7,5 @@ members = [
"examples/erc20-contract",
"examples/kv-contract",
"examples/default-contract",
"examples/rusty-contract",
]
3 changes: 2 additions & 1 deletion README.md
@@ -1,7 +1,8 @@
# SewUp

![GitHub Workflow Status](https://img.shields.io/github/workflow/status/second-state/SewUp/CI)
[![Generic badge](https://img.shields.io/badge/Doc-main-green.svg)](https://second-state.github.io/SewUp/sewup/)
[![Generic badge](https://img.shields.io/badge/SewUpDoc-main-green.svg)](https://second-state.github.io/SewUp/sewup/)
[![Generic badge](https://img.shields.io/badge/SewUpDeriveDoc-main-green.svg)](https://second-state.github.io/SewUp/sewup_derive/)

**S**econdstate **EW**asm **U**tility **P**rogram, a library to help you sew up your Ethereum project with Rust and just like develop in a common backend.

Expand Down
2 changes: 1 addition & 1 deletion examples/default-contract/Cargo.toml
Expand Up @@ -24,6 +24,6 @@ panic = "abort"
lto = true
opt-level = "z"

[profile.release.package.kv-contract]
[profile.release.package.default-contract]
incremental = false
opt-level = "z"
3 changes: 3 additions & 0 deletions examples/rusty-contract/.cargo/config
@@ -0,0 +1,3 @@
[build]
target = "wasm32-unknown-unknown"
rustflags = ["-Clink-arg=--export-table"]
29 changes: 29 additions & 0 deletions examples/rusty-contract/Cargo.toml
@@ -0,0 +1,29 @@
[package]
name = "rusty-contract"
version = "0.1.0"
authors = ["Antonio Yang <yanganto@gmail.com>"]
edition = "2018"
description = "The rusty return example contract using sewup default feature"

[lib]
path = "src/lib.rs"
crate-type = ["cdylib"]

[dependencies]
sewup ={ version = "0.0.2", path = "../../sewup" }
sewup-derive = { version = "0.0.2", path = "../../sewup-derive" }
ewasm_api = { version = "0.11.0", default-features = false, features = ["std", "qimalloc"], package = "ss_ewasm_api" }
anyhow = "1.0.40"
thiserror = "1.0.24"
serde = "1.0"
serde_derive = "1.0"

[profile.release]
incremental = false
panic = "abort"
lto = true
opt-level = "z"

[profile.release.package.rusty-contract]
incremental = false
opt-level = "z"
35 changes: 35 additions & 0 deletions examples/rusty-contract/src/lib.rs
@@ -0,0 +1,35 @@
use serde_derive::{Deserialize, Serialize};

use sewup::primitives::Contract;
use sewup_derive::{ewasm_fn, ewasm_main, fn_sig, input_from};

#[derive(Default, Serialize, Deserialize)]
struct SimpleStruct {
trust: bool,
description: String,
}

#[ewasm_fn]
fn check_input_object(s: SimpleStruct) -> Result<(), &'static str> {
if !s.trust {
return Err("NotTrustedInput");
}
Ok(())
}

#[ewasm_main(rusty)]
fn main() -> Result<(), &'static str> {
let contract = Contract::new().map_err(|_| "NewContractError")?;
match contract
.get_function_selector()
.map_err(|_| "FailGetFnSelector")?
{
fn_sig!(check_input_object) => {
input_from!(contract, check_input_object, |_| "DeserdeError")
.map_err(|_| "InputError")?
}
_ => return Err("UnknownHandle"),
};

Ok(())
}
160 changes: 133 additions & 27 deletions sewup-derive/src/lib.rs
@@ -1,4 +1,5 @@
extern crate proc_macro;

use proc_macro::TokenStream;
use regex::Regex;
use tiny_keccak::{Hasher, Keccak};
Expand All @@ -11,34 +12,109 @@ fn get_function_signature(function_prototype: &str) -> [u8; 4] {
sig
}

/// `ewasm_main` is a macro for the main function of the contract
/// There are three different contract output.
///
/// `#[ewasm_main]`
/// The default contract output, the error will be return as a string message
/// This is for a scenario that you just want to modify the data on
/// chain only, and the error will to string than return.
///
/// `#[ewasm_main(rusty)]`
/// The rust styl output, the result object from ewasm_main function will be
/// returned, this is for a scenario that you are using a rust client to catch
/// and want to catch the result from the contract.
///
/// `#[ewasm_main(unwrap)]`
/// The unwrap the output of the result object from ewasm_main function.
/// This is for a scenario that you are using a rust non-rust client,
/// and you are only care the happy case of excuting the contract.
#[proc_macro_attribute]
pub fn ewasm_main(_attr: TokenStream, item: TokenStream) -> TokenStream {
pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream {
let re = Regex::new(r"fn (?P<name>[^(]+?)\(").unwrap();
let fn_name = if let Some(cap) = re.captures(&item.to_string()) {
cap.name("name").unwrap().as_str().to_owned()
} else {
panic!("parse function error")
};
format!(
r#"
use sewup::bincode;
#[no_mangle]
pub fn main() {{

return match attr.to_string().to_lowercase().as_str() {
// Return the inner structure from unwrap result
// This is for a scenario that you take care the result but not using Rust client
"unwrap" => format!(
r#"
use sewup::bincode;
use ewasm_api::finish_data;
{}
if let Err(e) = {}() {{
let error_msg = e.to_string();
finish_data(&error_msg.as_bytes());
#[no_mangle]
pub fn main() {{
{}
match {}() {{
Ok(r) => {{
let bin = bincode::serialize(&r).expect("The resuslt of `ewasm_main` should be serializable");
finish_data(&bin);
}},
Err(e) => {{
let error_msg = e.to_string();
finish_data(&error_msg.as_bytes());
}}
}}
}}
"#,
item.to_string(),
fn_name
)
.parse()
.unwrap(),

// Return all result structure
// This is for a scenario that you are using a rust client to operation the contract
"rusty" => format!(
r#"
use sewup::bincode;
use ewasm_api::finish_data;
#[no_mangle]
pub fn main() {{
{}
let r = {}();
let bin = bincode::serialize(&r).expect("The resuslt of `ewasm_main` should be serializable");
finish_data(&bin);
}}
}}
"#,
item.to_string(),
fn_name
)
.parse()
.unwrap()
"#,
item.to_string(),
fn_name
)
.parse()
.unwrap(),

// Default only return error message,
// This is for a scenario that you just want to modify the data on
// chain only
_ => format!(
r#"
use sewup::bincode;
use ewasm_api::finish_data;
#[no_mangle]
pub fn main() {{
{}
if let Err(e) = {}() {{
let error_msg = e.to_string();
finish_data(&error_msg.as_bytes());
}}
}}
"#,
item.to_string(),
fn_name
)
.parse()
.unwrap()
};
}

/// The macro helps you to build your handler in the contract, and also
/// generate the function signature, you can use `fn_sig!` macro to get your
/// function signature of the function wrappered with `#[ewasm_fn]`
#[proc_macro_attribute]
pub fn ewasm_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
let re = Regex::new(r"^fn (?P<name>[^(]+?)\((?P<params>[^)]*?)\)").unwrap();
Expand Down Expand Up @@ -70,6 +146,9 @@ pub fn ewasm_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
}

/// The macro helps you to build your handler as a lib, which can used in the
/// contract, the function signature well automatically generated as
/// `{FUNCTION_NAME}_SIG`
#[proc_macro_attribute]
pub fn ewasm_lib_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
let re = Regex::new(r"^pub fn (?P<name>[^(]+?)\((?P<params>[^)]*?)\)").unwrap();
Expand Down Expand Up @@ -103,6 +182,14 @@ pub fn ewasm_lib_fn(_attr: TokenStream, item: TokenStream) -> TokenStream {
}
}

/// `fn_sig` helps you get you function signature
/// 1. provide function name to get function signature from the same namespace,
/// which function should be decorated with `#[ewasm_fn]`, for example,
/// `fn_sig!(the_name_of_contract_handler)`
///
/// 2. provide a function name with input parameters then the macro will
/// calculate the correct functional signature for you.
/// ex: `fn_sig!(the_name_of_contract_handler( a: i32, b: String ))`
#[proc_macro]
pub fn fn_sig(item: TokenStream) -> TokenStream {
let re = Regex::new(r"^(?P<name>[^(]+?)\((?P<params>[^)]*?)\)").unwrap();
Expand All @@ -128,25 +215,43 @@ pub fn fn_sig(item: TokenStream) -> TokenStream {
}
}

/// `input_from` will help you to get the input data from contract caller, and
/// automatically deserialize input into handler
/// `input_from!(contract, the_name_of_the_handler)`
/// Besides, you can map the error to your customized error when something wrong happened in
/// `input_from!`, for example:
/// `input_from!(contract, check_input_object, |_| Err("DeserdeError"))`
#[proc_macro]
pub fn input_from(item: TokenStream) -> TokenStream {
let re = Regex::new(r"^(?P<contract>\w+),\s+(?P<name>\w+)").unwrap();
let re = Regex::new(r"^(?P<contract>\w+),\s+(?P<name>\w+),?(?P<error_handler>.*)").unwrap();
if let Some(cap) = re.captures(&item.to_string()) {
let contract = cap.name("contract").unwrap().as_str();
let fn_name = cap.name("name").unwrap().as_str();
format!(
r#"
{}(bincode::deserialize(&{}.input_data[4..])?)
"#,
fn_name, contract
)
.parse()
.unwrap()
let error_handler = cap.name("error_handler").unwrap().as_str();
if error_handler.is_empty() {
format!(
r#"
{}(bincode::deserialize(&{}.input_data[4..])?)
"#,
fn_name, contract
)
.parse()
.unwrap()
} else {
format!(
r#"
{}(bincode::deserialize(&{}.input_data[4..]).map_err({})?)
"#,
fn_name, contract, error_handler
)
.parse()
.unwrap()
}
} else {
panic!("fail to parsing function in fn_select");
}
}

/// `Value` derive help you implement Value trait for kv feature
#[proc_macro_derive(Value)]
pub fn derive_value(item: TokenStream) -> TokenStream {
let re = Regex::new(r"struct (?P<name>\w+)").unwrap();
Expand All @@ -165,6 +270,7 @@ pub fn derive_value(item: TokenStream) -> TokenStream {
}
}

/// `Key` derive help you implement Key trait for the kv feature
#[proc_macro_derive(Key)]
pub fn derive_key(item: TokenStream) -> TokenStream {
let re = Regex::new(r"struct (?P<name>\w+)").unwrap();
Expand Down
48 changes: 48 additions & 0 deletions sewup/src/tests.rs
Expand Up @@ -61,3 +61,51 @@ fn test_execute_basic_operations() {
vec![],
);
}

#[test]
fn test_execute_rusty_contract() {
let runtime = Arc::new(RefCell::new(TestRuntime::default()));
let run_function =
|fn_name: &str, sig: [u8; 4], input_data: Option<&[u8]>, expect_output: Vec<u8>| {
let config_file = NamedTempFile::new().unwrap();

let mut h = ContractHandler {
sender_address: Address::from_low_u64_be(1),
call_data: Some(format!(
"{}/../resources/test/rusty_contract.wasm",
env!("CARGO_MANIFEST_DIR")
)),
config_file_path: Some(config_file.path().into()),
..Default::default()
};

h.rt = Some(runtime.clone());

match h.execute(sig, input_data, 1_000_000) {
Ok(r) => assert_eq!((fn_name, r.output_data), (fn_name, expect_output)),
Err(e) => {
panic!("vm error: {:?}", e);
}
}
};

let mut simple_struct = SimpleStruct::default();
let mut bin = bincode::serialize(&simple_struct).unwrap();
run_function(
"Check input object",
fn_sig!(check_input_object(s: SimpleStruct)),
Some(&bin),
vec![
1, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 73, 110, 112, 117, 116, 69, 114, 114, 111, 114,
],
);

simple_struct.trust = true;
bin = bincode::serialize(&simple_struct).unwrap();
run_function(
"Check input object",
fn_sig!(check_input_object(s: SimpleStruct)),
Some(&bin),
vec![0, 0, 0, 0],
);
}

0 comments on commit 644f41b

Please sign in to comment.