diff --git a/.github/workflows/hello_example.yml b/.github/workflows/hello_example.yml new file mode 100644 index 000000000..4b0b279b8 --- /dev/null +++ b/.github/workflows/hello_example.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + + workflow_dispatch: + +jobs: + hello-example: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Super-Linter + uses: github/super-linter@v3 + env: + VALIDATE_ALL_CODEBASE: false + DEFAULT_BRANCH: main + VALIDATE_RUST_2018: true + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - uses: cachix/install-nix-action@v13 + with: + nix_path: nixpkgs=channel:nixos-21.05-small + + - name: Default example test + run: nix-shell --run 'run-example-test hello' diff --git a/Cargo.toml b/Cargo.toml index 14eecc740..1eb57b5f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "sewup-derive", ] exclude = [ + "examples/hello-contract", "examples/default-contract", "examples/erc20-contract", "examples/kv-contract", diff --git a/README.md b/README.md index e2fdbd3da..2fa69ec61 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![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. +**S**econdstate **EW**asm **U**tility **P**rogram, a library helps you sew up your Ethereum project with Rust and just like develop in a common backend. ## Slides | Date | Event | Slides | @@ -14,19 +14,80 @@ | 2021/06/19 | Rust meetup (Beijing) | [v0.0.1-pre](https://slides.com/yanganto/sewup) | ## Usage -Add the `sewup` with the feature and the `sewup-derive` into Cargo.toml, and setup lib section as following, then you are ready to build contract with sewup. +Add `sewup` with the features and the `sewup-derive` into Cargo.toml, and setup other sections +as following, then you are ready to build contract with sewup. + +Beside, we suggest you using `anyhow` to handle your result and error, but not limited to, +if you want to use other error crate please checkout `#[ewasm_main(rusty)]` and learn more. + ```toml +[package] +name = "hello-contract" + [lib] path = "src/lib.rs" crate-type = ["cdylib"] [dependencies] -sewup = { version = "0.0.2", features = ['kv'] } -sewup-derive = { version = "0.0.2" } +sewup = { version = "*", features = ['kv'] } +sewup-derive = { version = "*" } + +anyhow = "1.0" + +[profile.release] +incremental = false +panic = "abort" +lto = true +opt-level = "z" + +[profile.release.package.hello-contract] # package name +incremental = false +opt-level = "z" ``` -Besides, you can learn more from [examples/kv-contract](./examples/kv-contract), [examples/default-contract](./examples/default-contract), [examples/rusty-contract](./examples/rusty-contract) in the examples folder. +Place [.carggo/config](./examples/hello-contract/.cargo/config) file in your project to specify the flags for build. + +Here is minimize example for writing a contract with sewup +```rust +// lib.rs +use anyhow::Result; + +use sewup::primitives::Contract; +use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_main, ewasm_test}; + +#[ewasm_fn] +fn hello() -> Result { + Ok("hello world".to_string()) +} + +#[ewasm_main(auto)] +fn main() -> Result { + let contract = Contract::new()?; + let greeting = match contract.get_function_selector()? { + ewasm_fn_sig!(hello) => hello()?, + _ => panic!("unknown handle"), + }; + Ok(greeting) +} + +#[ewasm_test] +mod tests { + use super::*; + use sewup_derive::ewasm_auto_assert_eq; + + #[ewasm_test] + fn test_get_greeting() { + ewasm_auto_assert_eq!(hello(), "hello world".to_string()); + } +} +``` + +Run `cargo build --release --target=wasm32-unknown-unknown`, then the contract will build in `target/wasm32-unknown-unknown/release/*.wasm` +Besides, you also can easily run test with `cargo test`, the ewasm contract automatically test with [WasmEdge](https://github.com/WasmEdge/WasmEdge). +Furthermore, you can learn more from other examples in the [example](./examples/) folder. ## Development -The workspace have several project, the contract project should build with target `wasm32-unknown-unknown` and the flag `-Clink-arg=--export-table`. -After placing the `.wasm` output into [/resources/test](./resources/test), you can run `cargo test -p sewup --features=kv` to check on the test for kv features. +The workspace have several project, the contract project should build with target +`wasm32-unknown-unknown` and the flag `-C link-arg=--export-table`. + +You can run `cargo test` in each example to check on the test your modification. It is easy to participate with help want issues and the good first issues. diff --git a/examples/default-contract/Cargo.toml b/examples/default-contract/Cargo.toml index 7be90d9e4..e53816018 100644 --- a/examples/default-contract/Cargo.toml +++ b/examples/default-contract/Cargo.toml @@ -12,7 +12,6 @@ crate-type = ["cdylib"] [dependencies] sewup ={ version = "0.0.3", path = "../../sewup" } sewup-derive = { version = "0.0.3", 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" diff --git a/examples/default-contract/src/errors.rs b/examples/default-contract/src/errors.rs index 1a6e5a19a..0c56b34f5 100644 --- a/examples/default-contract/src/errors.rs +++ b/examples/default-contract/src/errors.rs @@ -4,6 +4,6 @@ use thiserror::Error; pub enum Error { #[error("not trust input")] NotTrustedInput, - #[error("unknow handler")] + #[error("unknown handler")] UnknownHandle, } diff --git a/examples/default-contract/src/lib.rs b/examples/default-contract/src/lib.rs index fb12b9bb4..1c4f01d28 100644 --- a/examples/default-contract/src/lib.rs +++ b/examples/default-contract/src/lib.rs @@ -2,7 +2,7 @@ use anyhow::Result; use serde_derive::{Deserialize, Serialize}; use sewup::primitives::Contract; -use sewup_derive::{ewasm_fn, ewasm_main, ewasm_test, fn_sig, input_from}; +use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_input_from, ewasm_main, ewasm_test}; mod errors; use errors::Error; @@ -25,7 +25,7 @@ fn check_input_object(s: SimpleStruct) -> Result<()> { fn main() -> Result<()> { let contract = Contract::new()?; match contract.get_function_selector()? { - fn_sig!(check_input_object) => input_from!(contract, check_input_object)?, + ewasm_fn_sig!(check_input_object) => ewasm_input_from!(contract, check_input_object)?, _ => return Err(Error::UnknownHandle.into()), }; @@ -35,6 +35,7 @@ fn main() -> Result<()> { #[ewasm_test] mod tests { use super::*; + use sewup_derive::{ewasm_assert_eq, ewasm_assert_ok, ewasm_err_output}; #[ewasm_test] fn test_execute_basic_operations() { diff --git a/examples/erc20-contract/Cargo.toml b/examples/erc20-contract/Cargo.toml index 5892136c0..ab3a189a9 100644 --- a/examples/erc20-contract/Cargo.toml +++ b/examples/erc20-contract/Cargo.toml @@ -13,8 +13,6 @@ crate-type = ["cdylib"] sewup ={ version = "0.0.3", path = "../../sewup", features = [ "token" ] } sewup-derive = { version = "0.0.3", path = "../../sewup-derive" } anyhow = "1.0.40" - -[target.'cfg(target_arch = "wasm32")'.dependencies] ewasm_api = { version = "0.11.0", default-features = false, features = ["std", "qimalloc"], package = "ss_ewasm_api" } [dev-dependencies] diff --git a/examples/erc20-contract/src/token.rs b/examples/erc20-contract/src/token.rs index c0262fc0b..8d13fcb96 100644 --- a/examples/erc20-contract/src/token.rs +++ b/examples/erc20-contract/src/token.rs @@ -9,7 +9,7 @@ use sewup::token::{ }, helpers::{copy_into_address, copy_into_array, copy_into_storage_value}, }; -use sewup_derive::{ewasm_assert_eq, ewasm_fn, ewasm_main, ewasm_test, fn_sig}; +use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_main, ewasm_test}; #[cfg(target_arch = "wasm32")] use ewasm_api::types::*; @@ -102,16 +102,16 @@ fn mint(contract: &Contract) { fn main() -> Result<()> { let contract = Contract::new()?; match contract.get_function_selector()? { - fn_sig!(do_balance) => do_balance(&contract), - fn_sig!(do_transfer) => do_transfer(&contract), + ewasm_fn_sig!(do_balance) => do_balance(&contract), + ewasm_fn_sig!(do_transfer) => do_transfer(&contract), NAME_SIG => name(), SYMBOL_SIG => symbol("ETD"), DECIMALS_SIG => decimals(), TOTAL_SUPPLY_SIG => total_supply(), - fn_sig!(approve) => approve(&contract), - fn_sig!(allowance) => allowance(&contract), - fn_sig!(transfer_from) => transfer_from(&contract), - fn_sig!(mint) => mint(&contract), + ewasm_fn_sig!(approve) => approve(&contract), + ewasm_fn_sig!(allowance) => allowance(&contract), + ewasm_fn_sig!(transfer_from) => transfer_from(&contract), + ewasm_fn_sig!(mint) => mint(&contract), _ => (), }; Ok(()) @@ -120,7 +120,8 @@ fn main() -> Result<()> { #[ewasm_test] mod tests { use super::*; - use hex_literal::*; + use hex_literal::hex; + use sewup_derive::ewasm_assert_eq; #[ewasm_test] fn test_execute_basic_operations() { diff --git a/examples/hello-contract/.cargo/config b/examples/hello-contract/.cargo/config new file mode 100644 index 000000000..a3ace1d3d --- /dev/null +++ b/examples/hello-contract/.cargo/config @@ -0,0 +1,2 @@ +[target.'cfg(target_arch="wasm32")'] +rustflags = ["-C", "link-arg=--export-table"] diff --git a/examples/hello-contract/Cargo.toml b/examples/hello-contract/Cargo.toml new file mode 100644 index 000000000..0a29697dc --- /dev/null +++ b/examples/hello-contract/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "hello-contract" +version = "0.1.0" +authors = ["Antonio Yang "] +edition = "2018" +description = "The example contract using sewup default feature" + +[lib] +path = "src/lib.rs" +crate-type = ["cdylib"] + +[dependencies] +sewup ={ version = "0.0.3", path = "../../sewup" } +sewup-derive = { version = "0.0.3", path = "../../sewup-derive" } +anyhow = "1.0.40" + +[profile.release] +incremental = false +panic = "abort" +lto = true +opt-level = "z" + +[profile.release.package.hello-contract] +incremental = false +opt-level = "z" diff --git a/examples/hello-contract/src/lib.rs b/examples/hello-contract/src/lib.rs new file mode 100644 index 000000000..510aab1bf --- /dev/null +++ b/examples/hello-contract/src/lib.rs @@ -0,0 +1,35 @@ +use anyhow::Result; + +use sewup::primitives::Contract; +use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_main, ewasm_test}; + +#[ewasm_fn] +fn hello() -> Result { + Ok("hello world".to_string()) +} + +#[ewasm_main(auto)] +fn main() -> Result { + let contract = Contract::new()?; + let greeting = match contract.get_function_selector()? { + ewasm_fn_sig!(hello) => hello()?, + _ => panic!("unknown handle"), + }; + Ok(greeting) +} + +#[ewasm_test] +mod tests { + use super::*; + use sewup_derive::{ewasm_assert_eq, ewasm_auto_assert_eq, ewasm_output_from}; + + #[ewasm_test] + fn test_get_greeting() { + ewasm_assert_eq!( + hello(), + vec![11, 0, 0, 0, 0, 0, 0, 0, 104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100] + ); + ewasm_assert_eq!(hello(), ewasm_output_from!("hello world".to_string())); + ewasm_auto_assert_eq!(hello(), "hello world".to_string()); + } +} diff --git a/examples/kv-contract/Cargo.toml b/examples/kv-contract/Cargo.toml index 209c27b02..5ddbeefe2 100644 --- a/examples/kv-contract/Cargo.toml +++ b/examples/kv-contract/Cargo.toml @@ -12,7 +12,6 @@ crate-type = ["cdylib"] [dependencies] sewup ={ version = "0.0.3", path = "../../sewup", features = [ "kv" ] } sewup-derive = { version = "0.0.3", 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" diff --git a/examples/kv-contract/src/errors.rs b/examples/kv-contract/src/errors.rs index a65eb7704..ce26ea851 100644 --- a/examples/kv-contract/src/errors.rs +++ b/examples/kv-contract/src/errors.rs @@ -3,11 +3,11 @@ use thiserror::Error; #[derive(Error, Debug, PartialEq)] pub enum KVError { - #[error("the db version `{0}` is unexpect.")] + #[error("the DB version `{0}` is unexpected.")] UnexpectVersion(u8), #[error("features are compatible, current features are: `{0:?}`.")] IncompatibleFeatures(Vec), - #[error("current db size is `{0}`.")] + #[error("current DB size is `{0}`.")] UnexpectedDBSize(u32), #[error("current bucket are: `{0:?}.`")] IncorrectBuckets(Vec), @@ -15,8 +15,8 @@ pub enum KVError { BucketError(String), #[error("current value is `{0}.`")] ValueError(String), - #[error("ValueNotFound")] + #[error("value not found")] ValueNotFound, - #[error("unknow handler")] + #[error("unknown handler")] UnknownHandle, } diff --git a/examples/kv-contract/src/lib.rs b/examples/kv-contract/src/lib.rs index 0c777af26..207778b70 100644 --- a/examples/kv-contract/src/lib.rs +++ b/examples/kv-contract/src/lib.rs @@ -4,7 +4,7 @@ use serde_derive::{Deserialize, Serialize}; use sewup::kv::{Feature, Store}; use sewup::primitives::Contract; use sewup::types::{Raw, Row}; -use sewup_derive::{ewasm_fn, ewasm_main, ewasm_test, fn_sig, Value}; +use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_main, ewasm_test, Value}; mod errors; use errors::KVError; @@ -185,23 +185,23 @@ fn main() -> Result<()> { let contract = Contract::new()?; match contract.get_function_selector()? { - fn_sig!(empty_commit) => empty_commit()?, - fn_sig!(check_version_and_features) => { + ewasm_fn_sig!(empty_commit) => empty_commit()?, + ewasm_fn_sig!(check_version_and_features) => { check_version_and_features(1, vec![Feature::Default])? } - fn_sig!(check_empty_storage_size) => check_empty_storage_size(EMPTY_DB_SIZE)?, - fn_sig!(add_buckets) => add_buckets()?, - fn_sig!(check_buckets) => { + ewasm_fn_sig!(check_empty_storage_size) => check_empty_storage_size(EMPTY_DB_SIZE)?, + ewasm_fn_sig!(add_buckets) => add_buckets()?, + ewasm_fn_sig!(check_buckets) => { check_buckets(vec!["bucket1".to_string(), "bucket2".to_string()])? } - fn_sig!(drop_bucket_than_check) => { + ewasm_fn_sig!(drop_bucket_than_check) => { drop_bucket_than_check("bucket1", vec!["bucket2".to_string()])? } // Following handler is for other test - fn_sig!(new_bucket_with_specific_struct) => new_bucket_with_specific_struct()?, - fn_sig!(check_objects_in_bucket) => check_objects_in_bucket()?, - fn_sig!(delete_object_in_bucket) => delete_object_in_bucket()?, + ewasm_fn_sig!(new_bucket_with_specific_struct) => new_bucket_with_specific_struct()?, + ewasm_fn_sig!(check_objects_in_bucket) => check_objects_in_bucket()?, + ewasm_fn_sig!(delete_object_in_bucket) => delete_object_in_bucket()?, _ => return Err(KVError::UnknownHandle.into()), }; @@ -211,6 +211,7 @@ fn main() -> Result<()> { #[ewasm_test] mod tests { use super::*; + use sewup_derive::{ewasm_assert_eq, ewasm_assert_ok, ewasm_err_output}; #[ewasm_test] fn test_execute_storage_operations() { diff --git a/examples/rusty-contract/Cargo.toml b/examples/rusty-contract/Cargo.toml index 755d5ded9..3d8408b64 100644 --- a/examples/rusty-contract/Cargo.toml +++ b/examples/rusty-contract/Cargo.toml @@ -12,7 +12,6 @@ crate-type = ["cdylib"] [dependencies] sewup ={ version = "0.0.3", path = "../../sewup" } sewup-derive = { version = "0.0.3", 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" diff --git a/examples/rusty-contract/src/lib.rs b/examples/rusty-contract/src/lib.rs index 1aaaf435f..a6a2de5a6 100644 --- a/examples/rusty-contract/src/lib.rs +++ b/examples/rusty-contract/src/lib.rs @@ -1,7 +1,7 @@ use serde_derive::{Deserialize, Serialize}; use sewup::primitives::Contract; -use sewup_derive::{ewasm_fn, ewasm_main, ewasm_test, fn_sig, input_from}; +use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_input_from, ewasm_main, ewasm_test}; #[derive(Default, Serialize, Deserialize)] struct SimpleStruct { @@ -24,8 +24,8 @@ fn main() -> Result<(), &'static str> { .get_function_selector() .map_err(|_| "FailGetFnSelector")? { - fn_sig!(check_input_object) => { - input_from!(contract, check_input_object, |_| "DeserdeError")? + ewasm_fn_sig!(check_input_object) => { + ewasm_input_from!(contract, check_input_object, |_| "DeserdeError")? } _ => return Err("UnknownHandle"), }; @@ -37,7 +37,7 @@ fn main() -> Result<(), &'static str> { mod tests { use super::*; use sewup::primitives::Contract; - use sewup_derive::{ewasm_assert_eq, ewasm_assert_rusty_ok, ewasm_rusty_err_output}; + use sewup_derive::{ewasm_assert_eq, ewasm_rusty_assert_ok, ewasm_rusty_err_output}; #[ewasm_test] fn test_execute_rusty_contract() { @@ -62,7 +62,7 @@ mod tests { simple_struct.trust = true; // use `ewasm_assert_rusty_ok`, because the `#[ewasm_main(rusty)]` specify the rusty return - ewasm_assert_rusty_ok!(check_input_object(simple_struct)); + ewasm_rusty_assert_ok!(check_input_object(simple_struct)); // Assert an ok result with raw output, // the previous `ewasm_assert_rusty_ok` is the suggested way diff --git a/sewup-derive/src/lib.rs b/sewup-derive/src/lib.rs index b42ce7a82..7d7697791 100644 --- a/sewup-derive/src/lib.rs +++ b/sewup-derive/src/lib.rs @@ -12,8 +12,9 @@ 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. +/// helps you setup the main function of a contract +/// +/// There are three different kind contract output. /// /// `#[ewasm_main]` /// The default contract output, the error will be return as a string message @@ -25,10 +26,22 @@ fn get_function_signature(function_prototype: &str) -> [u8; 4] { /// 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. +/// `#[ewasm_main(auto)]` +/// Auto 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. +/// +/// ```compile_fail +/// #[ewasm_main] +/// fn main() -> Result<()> { +/// let contract = Contract::new()?; +/// match contract.get_function_selector()? { +/// ewasm_fn_sig!(check_input_object) => ewasm_input_from!(contract, check_input_object)?, +/// _ => return Err(Error::UnknownHandle.into()), +/// }; +/// Ok(()) +/// } +/// ``` #[proc_macro_attribute] pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream { let re = Regex::new(r"fn (?P[^(]+?)\(").unwrap(); @@ -41,12 +54,12 @@ pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream { 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!( + "auto" => format!( r#" #[cfg(target_arch = "wasm32")] use sewup::bincode; #[cfg(target_arch = "wasm32")] - use ewasm_api::finish_data; + use sewup::ewasm_api::finish_data; #[cfg(target_arch = "wasm32")] #[no_mangle] pub fn main() {{ @@ -60,7 +73,6 @@ pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream { Err(e) => {{ let error_msg = e.to_string(); finish_data(&error_msg.as_bytes()); - }} }} }} @@ -78,7 +90,7 @@ pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream { #[cfg(target_arch = "wasm32")] use sewup::bincode; #[cfg(target_arch = "wasm32")] - use ewasm_api::finish_data; + use sewup::ewasm_api::finish_data; #[cfg(target_arch = "wasm32")] #[no_mangle] pub fn main() {{ @@ -101,7 +113,7 @@ pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream { r#" use sewup::bincode; #[cfg(target_arch = "wasm32")] - use ewasm_api::finish_data; + use sewup::ewasm_api::finish_data; #[cfg(target_arch = "wasm32")] #[no_mangle] pub fn main() {{ @@ -120,9 +132,22 @@ pub fn ewasm_main(attr: TokenStream, item: TokenStream) -> TokenStream { }; } -/// 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]` +/// helps you to build your handlers in the contract +/// +/// This macro also generate the function signature, you can use +/// `ewasm_fn_sig!` macro to get your function signature; +/// +/// ```compile_fail +/// #[ewasm_main] +/// fn main() -> Result<()> { +/// let contract = Contract::new()?; +/// match contract.get_function_selector()? { +/// ewasm_fn_sig!(check_input_object) => ewasm_input_from!(contract, check_input_object)?, +/// _ => return Err(Error::UnknownHandle.into()), +/// }; +/// Ok(()) +/// } +/// ``` #[proc_macro_attribute] pub fn ewasm_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { let re = Regex::new(r"^fn (?P[^(]+?)\((?P[^)]*?)\)").unwrap(); @@ -155,9 +180,37 @@ 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` +/// helps you to build your handler in other module +/// +/// This macro will automatically generated as `{FUNCTION_NAME}_SIG` +/// +/// ```compile_fail +/// // module.rs +/// +/// use sewup::ewasm_api; +/// +/// #[ewasm_lib_fn] +/// pub fn symbol(s: &str) { +/// let symbol = s.to_string().into_bytes(); +/// ewasm_api::finish_data(&symbol); +/// } +/// ``` +/// +/// ```compile_fail +/// // lib.rs +/// +/// use module::{symbol, SYMBOL_SIG}; +/// +/// #[ewasm_main] +/// fn main() -> Result<()> { +/// let contract = Contract::new()?; +/// match contract.get_function_selector()? { +/// SYMBOL_SIG => symbol("ETD"), +/// _ => return Err(Error::UnknownHandle.into()), +/// }; +/// Ok(()) +/// } +/// ``` #[proc_macro_attribute] pub fn ewasm_lib_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { let re = Regex::new(r"^pub fn (?P[^(]+?)\((?P[^)]*?)\)").unwrap(); @@ -198,16 +251,57 @@ pub fn ewasm_lib_fn(_attr: TokenStream, item: TokenStream) -> TokenStream { } } -/// `fn_sig` helps you get you function signature +/// 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)` +/// `ewasm_fn_sig!(contract_handler)` +/// +/// ```compile_fail +/// #[ewasm_fn] +/// fn decorated_handler(a: i32, b: String) -> Result<()> { +/// Ok(()) +/// } +/// +/// #[ewasm_main] +/// fn main() -> Result<()> { +/// let contract = Contract::new()?; +/// match contract.get_function_selector()? { +/// ewasm_fn_sig!(decorated_handler) => ewasm_input_from!(contract, decorated_handler)?, +/// _ => return Err(Error::UnknownHandle.into()), +/// }; +/// Ok(()) +/// } +/// ``` /// /// 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 ))` +/// ex: `ewasm_fn_sig!(undecorated_handler( a: i32, b: String ))` +/// +/// ```compile_fail +/// // some_crate.rs +/// pub fn decorated_handler(a: i32, b: String) -> Result<()> { +/// Ok(()) +/// } +/// ``` +/// +/// ```compile_fail +/// use some_crate::decorated_handler; +/// +/// #[ewasm_main] +/// fn main() -> Result<()> { +/// let contract = Contract::new()?; +/// match contract.get_function_selector()? { +/// ewasm_fn_sig!(undecorated_handler(a: i32, b: String)) +/// => ewasm_input_from!(contract, undecorated_handler)?, +/// _ => return Err(Error::UnknownHandle.into()), +/// }; +/// Ok(()) +/// } +/// ``` +/// #[proc_macro] -pub fn fn_sig(item: TokenStream) -> TokenStream { +pub fn ewasm_fn_sig(item: TokenStream) -> TokenStream { let re = Regex::new(r"^(?P[^(]+?)\((?P[^)]*?)\)").unwrap(); if let Some(cap) = re.captures(&item.to_string()) { let fn_name = cap.name("name").unwrap().as_str(); @@ -231,14 +325,38 @@ 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)` +/// helps you to get the input data from contract caller +/// +/// This macro automatically deserialize input into handler +/// `ewasm_input_from!(contract, the_name_of_the_handler)` +/// ```compile_fail +/// #[ewasm_main] +/// fn main() -> Result<()> { +/// let contract = Contract::new()?; +/// match contract.get_function_selector()? { +/// ewasm_fn_sig!(check_input_object) => ewasm_input_from!(contract, check_input_object)?, +/// _ => return Err(Error::UnknownHandle.into()), +/// }; +/// Ok(()) +/// } +/// ``` +/// /// 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"))` +/// `ewasm_input_from!`, for example: +/// `ewasm_input_from!(contract, check_input_object, |_| Err("DeserdeError"))` +/// ```compile_fail +/// #[ewasm_main(rusty)] +/// fn main() -> Result<(), &'static str> { +/// let contract = Contract::new().map_err(|_| "NewContractError")?; +/// match contract.get_function_selector().map_err(|_| "FailGetFnSelector")? { +/// ewasm_fn_sig!(check_input_object) => ewasm_input_from!(contract, check_input_object, |_| "DeserdeError")? +/// _ => return Err("UnknownHandle"), +/// }; +/// Ok(()) +/// } +/// ``` #[proc_macro] -pub fn input_from(item: TokenStream) -> TokenStream { +pub fn ewasm_input_from(item: TokenStream) -> TokenStream { let re = Regex::new(r"^(?P\w+),\s+(?P\w+),?(?P.*)").unwrap(); if let Some(cap) = re.captures(&item.to_string()) { let contract = cap.name("contract").unwrap().as_str(); @@ -267,16 +385,39 @@ pub fn input_from(item: TokenStream) -> TokenStream { 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 { + +/// help you generate the exactly contract output form rust instance +#[proc_macro] +pub fn ewasm_output_from(item: TokenStream) -> TokenStream { + format!( + r#" + bincode::serialize(&{}).expect("fail to serialize in `ewasm_output_from`") + "#, + item.to_string(), + ) + .parse() + .unwrap() +} + +/// `Key` derive help you implement Key trait for the kv feature +/// +/// ``` +/// use sewup_derive::Key; +/// #[derive(Key)] +/// struct SimpleStruct { +/// trust: bool, +/// description: String, +/// } +/// ``` +#[proc_macro_derive(Key)] +pub fn derive_key(item: TokenStream) -> TokenStream { let re = Regex::new(r"struct (?P\w+)").unwrap(); if let Some(cap) = re.captures(&item.to_string()) { let struct_name = cap.name("name").unwrap().as_str(); format!( r#" #[cfg(target_arch = "wasm32")] - impl sewup::kv::traits::Value for {} {{}} + impl sewup::kv::traits::Key for {} {{}} "#, struct_name, ) @@ -287,16 +428,25 @@ 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 { +/// `Value` derive help you implement Value trait for kv feature +/// +/// ``` +/// use sewup_derive::Value; +/// #[derive(Value)] +/// struct SimpleStruct { +/// trust: bool, +/// description: String, +/// } +/// ``` +#[proc_macro_derive(Value)] +pub fn derive_value(item: TokenStream) -> TokenStream { let re = Regex::new(r"struct (?P\w+)").unwrap(); if let Some(cap) = re.captures(&item.to_string()) { let struct_name = cap.name("name").unwrap().as_str(); format!( r#" #[cfg(target_arch = "wasm32")] - impl sewup::kv::traits::Key for {} {{}} + impl sewup::kv::traits::Value for {} {{}} "#, struct_name, ) @@ -307,112 +457,122 @@ pub fn derive_key(item: TokenStream) -> TokenStream { } } +/// helps you setup the test mododule, and test cases in contract. +/// +/// ```compile_fail +/// #[ewasm_test] +/// mod tests { +/// use super::*; +/// +/// #[ewasm_test] +/// fn test_execute_basic_operations() { +/// ewasm_assert_ok!(contract_fn()); +/// } +/// } +/// ``` #[proc_macro_attribute] pub fn ewasm_test(_attr: TokenStream, item: TokenStream) -> TokenStream { - let mod_re = Regex::new(r"mod (?P[^\{]*)\{").unwrap(); - let fn_re = Regex::new(r"fn (?P[^\(]*)\(").unwrap(); - - if let Some(cap) = mod_re.captures(&item.to_string()) { - let mod_name = cap.name("mod_name").unwrap().as_str().to_owned(); - let prefix = format!("mod {}{{", mod_name); - return format!( - r#" + let mod_re = Regex::new(r"^mod (?P[^\{\s]*)(?P[^\{]*\{)").unwrap(); + let fn_re = Regex::new(r"^fn (?P[^\(\s]*)(?P[^\{]*\{)").unwrap(); + let context = item.to_string(); + if mod_re.captures(&context).is_some() { + return mod_re.replace(&context, r#" #[cfg(test)] - mod {} {{ + mod $mod_name { use sewup::bincode; - use sewup::runtimes::{{handler::ContractHandler, test::TestRuntime}}; - use sewup_derive::*; + use sewup::runtimes::{handler::ContractHandler, test::TestRuntime}; use std::cell::RefCell; use std::path::Path; use std::path::PathBuf; use std::process::Command; use std::sync::Arc; - fn _build_wasm() -> String {{ + fn _build_wasm() -> String { let output = Command::new("sh") .arg("-c") .arg("cargo build --release --target=wasm32-unknown-unknown") .output() .expect("failed to build wasm binary"); - if !dbg!(output).status.success() {{ + if !dbg!(output).status.success() { panic!("return code not success: fail to build wasm binary") - }} + } let pkg_name = env!("CARGO_PKG_NAME"); let base_dir = env!("CARGO_MANIFEST_DIR"); let wasm_binary = format!( - "{{}}/target/wasm32-unknown-unknown/release/{{}}.wasm", + "{}/target/wasm32-unknown-unknown/release/{}.wasm", base_dir, pkg_name.replace("-", "_") ); - if !Path::new(&wasm_binary).exists() {{ + if !Path::new(&wasm_binary).exists() { panic!("wasm binary missing") - }} + } wasm_binary - }} + } fn _build_runtime_and_runner() -> ( Arc>, impl Fn(Arc>, &str, [u8; 4], Option<&[u8]>, Vec) -> (), - ) {{ + ) { ( Arc::new(RefCell::new(TestRuntime::default())), |runtime: Arc>, fn_name: &str, sig: [u8; 4], input_data: Option<&[u8]>, - expect_output: Vec| {{ - let mut h = ContractHandler {{ + expect_output: Vec| { + let mut h = ContractHandler { call_data: Some(_build_wasm()), ..Default::default() - }}; + }; h.rt = Some(runtime.clone()); - match h.execute(sig, input_data, 1_000_000_000_000) {{ + match h.execute(sig, input_data, 1_000_000_000_000) { Ok(r) => assert_eq!((fn_name, r.output_data), (fn_name, expect_output)), - Err(e) => {{ - panic!("vm error: {{:?}}", e); - }} - }} - }}, + Err(e) => { + panic!("vm error: {:?}", e); + } + } + }, ) - }} + } #[test] - fn _compile_test() {{ + fn _compile_test() { _build_wasm(); - }} - - {} - "#, - mod_name, - item.to_string().trim_start_matches(&prefix) - ) - .parse() - .unwrap(); - } else if let Some(cap) = fn_re.captures(&item.to_string()) { - let fn_name = cap.name("fn_name").unwrap().as_str().to_owned(); - let prefix = format!("fn {}()\n{{", fn_name); - return format!( - r#" + }"#).to_string().parse().unwrap(); + } else if fn_re.captures(&context).is_some() { + return fn_re + .replace( + &context, + r#" #[test] - fn {} () {{ + fn $fn_name () { let (_runtime, _run_wasm_fn) = _build_runtime_and_runner(); let mut _bin: Vec = Vec::new(); - - {} "#, - fn_name, - item.to_string().trim_start_matches(&prefix) - ) - .parse() - .unwrap(); + ) + .to_string() + .parse() + .unwrap(); } else { panic!("parse mod or function for testing error") } } - +/// helps you assert output from the handle of a contract with `Vec`. +/// +/// ```compile_fail +/// #[ewasm_test] +/// mod tests { +/// use super::*; +/// +/// #[ewasm_test] +/// fn test_execute_basic_operations() { +/// ewasm_assert_eq!(handler_fn(), vec![74, 111, 118, 121]); +/// } +/// } +/// ``` #[proc_macro] pub fn ewasm_assert_eq(item: TokenStream) -> TokenStream { let re = Regex::new(r"^(?P[^(]+?)\((?P[^)]*?)\),(?P.*)").unwrap(); @@ -426,7 +586,7 @@ pub fn ewasm_assert_eq(item: TokenStream) -> TokenStream { _run_wasm_fn( _runtime.clone(), "{}", - fn_sig!({}), + ewasm_fn_sig!({}), None, {} ); @@ -442,7 +602,7 @@ pub fn ewasm_assert_eq(item: TokenStream) -> TokenStream { _run_wasm_fn( _runtime.clone(), "{}", - fn_sig!({}), + ewasm_fn_sig!({}), Some(&_bin), {} ); @@ -457,7 +617,67 @@ pub fn ewasm_assert_eq(item: TokenStream) -> TokenStream { } } -/// This macro helps you asser your handler without error and returns +/// helps you assert return instance from your handler with auto unwrap ewasm_main, namely `#[ewasm_main(auto)]` +/// +/// This usage of the macro likes `ewasm_assert_eq`, but the contract main function should be +/// decorated with `#[ewasm_main(auto)]`, and the equivalence arm will be serialized into `Vec` +#[proc_macro] +pub fn ewasm_auto_assert_eq(item: TokenStream) -> TokenStream { + let re = Regex::new(r"^(?P[^(]+?)\((?P[^)]*?)\),(?P.*)").unwrap(); + if let Some(cap) = re.captures(&item.to_string().replace("\n", "")) { + let fn_name = cap.name("fn_name").unwrap().as_str(); + let params = cap.name("params").unwrap().as_str().replace(" ", ""); + let equivalence = cap.name("equivalence").unwrap().as_str(); + if params.is_empty() { + format!( + r#" + _run_wasm_fn( + _runtime.clone(), + "{}", + ewasm_fn_sig!({}), + None, + sewup_derive::ewasm_output_from!({}) + ); + "#, + fn_name, fn_name, equivalence + ) + .parse() + .unwrap() + } else { + format!( + r#" + _bin = bincode::serialize(&{}).unwrap(); + _run_wasm_fn( + _runtime.clone(), + "{}", + ewasm_fn_sig!({}), + Some(&_bin), + sewup_derive::ewasm_output_from!({}) + ); + "#, + params, fn_name, fn_name, equivalence + ) + .parse() + .unwrap() + } + } else { + panic!("fail to parsing function in fn_select"); + } +} + +/// helps you assert your handler without error and returns +/// +/// ```compile_fail +/// #[ewasm_test] +/// mod tests { +/// use super::*; +/// +/// #[ewasm_test] +/// fn test_execute_basic_operations() { +/// ewasm_assert_ok!(contract_fn()); +/// } +/// } +/// ``` #[proc_macro] pub fn ewasm_assert_ok(item: TokenStream) -> TokenStream { let re = Regex::new(r"^(?P[^(]+?)\((?P[^)]*?)\)").unwrap(); @@ -470,9 +690,9 @@ pub fn ewasm_assert_ok(item: TokenStream) -> TokenStream { _run_wasm_fn( _runtime.clone(), "{}", - fn_sig!({}), + ewasm_fn_sig!({}), None, - Vec::new() + Vec::with_capacity(0) ); "#, fn_name, fn_name @@ -486,9 +706,9 @@ pub fn ewasm_assert_ok(item: TokenStream) -> TokenStream { _run_wasm_fn( _runtime.clone(), "{}", - fn_sig!({}), + ewasm_fn_sig!({}), Some(&_bin), - Vec::new() + Vec::with_capacity(0) ); "#, params, fn_name, fn_name @@ -501,9 +721,12 @@ pub fn ewasm_assert_ok(item: TokenStream) -> TokenStream { } } -/// This macro helps you assert return Ok(()) your handler with rusty ewasm_main, namely `#[ewasm_main(rusty)]` +/// helps you assert return Ok(()) your handler with rusty ewasm_main, namely `#[ewasm_main(rusty)]` +/// +/// This usage of the macro likes `ewasm_assert_ok`, this only difference is that the contract main +/// function should be decorated with `#[ewasm_main(rusty)]`. #[proc_macro] -pub fn ewasm_assert_rusty_ok(item: TokenStream) -> TokenStream { +pub fn ewasm_rusty_assert_ok(item: TokenStream) -> TokenStream { let re = Regex::new(r"^(?P[^(]+?)\((?P[^)]*?)\)").unwrap(); if let Some(cap) = re.captures(&item.to_string().replace("\n", "")) { let fn_name = cap.name("fn_name").unwrap().as_str(); @@ -514,7 +737,7 @@ pub fn ewasm_assert_rusty_ok(item: TokenStream) -> TokenStream { _run_wasm_fn( _runtime.clone(), "{}", - fn_sig!({}), + ewasm_fn_sig!({}), None, vec![0, 0, 0, 0] ); @@ -530,7 +753,7 @@ pub fn ewasm_assert_rusty_ok(item: TokenStream) -> TokenStream { _run_wasm_fn( _runtime.clone(), "{}", - fn_sig!({}), + ewasm_fn_sig!({}), Some(&_bin), vec![0, 0, 0, 0] ); @@ -545,10 +768,14 @@ pub fn ewasm_assert_rusty_ok(item: TokenStream) -> TokenStream { } } -/// This macro helps you assert return Err your handler with rusty ewasm_main, namely `#[ewasm_main(rusty)]` -/// you should pass the complete Result type, as the following example +/// helps you assert return Err your handler with rusty ewasm_main, namely `#[ewasm_main(rusty)]` +/// +/// This usage of the macro likes `ewasm_err_output`, the contract main function should be +/// decorated with `#[ewasm_main(rusty)]`. +/// +/// You should pass the complete Result type, as the following example /// `ewasm_rusty_err_output!(Err("NotTrustedInput") as Result<(), &'static str>)` -/// such that you can easy to use any kind of rust error as you like +/// such that you can easy to use any kind of rust error as you like. #[proc_macro] pub fn ewasm_rusty_err_output(item: TokenStream) -> TokenStream { format!( @@ -559,10 +786,26 @@ pub fn ewasm_rusty_err_output(item: TokenStream) -> TokenStream { .unwrap() } -/// The macro helps you to get the binary result of the thiserror, +/// helps you to get the binary result of the thiserror, +/// /// such that you can assert your handler with error. /// for example: -/// `ewasm_assert_eq!(some_handler(), ewasm_err_output!(Error::SomeError))` +/// ```compile_fail +/// #[ewasm_test] +/// mod tests { +/// use super::*; +/// +/// #[ewasm_test] +/// fn test_execute_basic_operations() { +/// let mut simple_struct = SimpleStruct::default(); +/// +/// ewasm_assert_eq!( +/// check_input_object(simple_struct), +/// ewasm_err_output!(Error::NotTrustedInput) +/// ); +/// } +///} +/// ``` #[proc_macro] pub fn ewasm_err_output(item: TokenStream) -> TokenStream { format!("{}.to_string().as_bytes().to_vec()", &item.to_string()) diff --git a/sewup/src/lib.rs b/sewup/src/lib.rs index 314cc94ed..c8be4bfff 100644 --- a/sewup/src/lib.rs +++ b/sewup/src/lib.rs @@ -30,3 +30,8 @@ pub mod runtimes; pub mod types; pub use bincode; + +/// Re export the ewasm_api +/// these api is low level apis, it is better to keep in a library not in the contract file +#[cfg(target_arch = "wasm32")] +pub use ewasm_api;