feat: introduce warp_contracts macro
gh-309
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
|||||||
# Dependency directories
|
# Dependency directories
|
||||||
|
|
||||||
node_modules/
|
node_modules/
|
||||||
|
crates/warp-contracts/target
|
||||||
|
|
||||||
# Optional eslint cache
|
# Optional eslint cache
|
||||||
.eslintcache
|
.eslintcache
|
||||||
|
|||||||
214
crates/warp-contracts/Cargo.lock
generated
Normal file
214
crates/warp-contracts/Cargo.lock
generated
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
|
||||||
|
dependencies = [
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.17.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.50"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.152"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-wasm-bindgen"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.152"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "1.0.109"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "warp-contracts"
|
||||||
|
version = "0.1.1"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"warp-contracts-core",
|
||||||
|
"warp-contracts-macro",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "warp-contracts-core"
|
||||||
|
version = "0.1.1"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde-wasm-bindgen",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "warp-contracts-macro"
|
||||||
|
version = "0.1.1"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"warp-contracts-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-backend"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-futures"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
"wasm-bindgen-backend",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.84"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "web-sys"
|
||||||
|
version = "0.3.61"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
34
crates/warp-contracts/Cargo.toml
Normal file
34
crates/warp-contracts/Cargo.toml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
[package]
|
||||||
|
name = "warp-contracts"
|
||||||
|
version = "0.1.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "Warp WASM contract utils for rust contracts"
|
||||||
|
license = "MIT"
|
||||||
|
documentation = "https://academy.warp.cc/docs/sdk/advanced/wasm"
|
||||||
|
homepage = "https://warp.cc"
|
||||||
|
repository = "https://github.com/warp-contracts/warp"
|
||||||
|
keywords = ["warp", "smart-contract", "SmartWeave", "web3"]
|
||||||
|
categories = ["api-bindings", "development-tools::ffi", "finance", "wasm"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = { workspace = true }
|
||||||
|
wasm-bindgen-futures = { workspace = true }
|
||||||
|
js-sys = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde-wasm-bindgen = { workspace = true }
|
||||||
|
warp-contracts-macro = { version = "0.1.1", path = "warp-contracts-macro" }
|
||||||
|
warp-contracts-core = { version = "0.1.1", path = "warp-contracts-core" }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
debug = ["warp-contracts-core/debug"]
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["warp-contracts-macro", "warp-contracts-core"]
|
||||||
|
|
||||||
|
[workspace.dependencies]
|
||||||
|
wasm-bindgen = "=0.2.84"
|
||||||
|
wasm-bindgen-futures = { version = "=0.4.34" }
|
||||||
|
js-sys = "=0.3.61"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde-wasm-bindgen = "=0.5.0"
|
||||||
|
web-sys = { version = "=0.3.61" }
|
||||||
23
crates/warp-contracts/README.md
Normal file
23
crates/warp-contracts/README.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Warp contracts
|
||||||
|
|
||||||
|
`warp-contracts` is an inherent part of [Warp SDK](https://github.com/warp-contracts/warp). This library allows for smooth integration with Warp implementation of SmartWeave protocol.
|
||||||
|
|
||||||
|
| Feature | Yes/No |
|
||||||
|
| ---------------------- | ----------- |
|
||||||
|
| Sandboxing | ✅ |
|
||||||
|
| Foreign contract read | ✅ |
|
||||||
|
| Foreign contract view | ✅ |
|
||||||
|
| Foreign contract write | ✅ |
|
||||||
|
| Arweave.utils | Soon |
|
||||||
|
| Evolve | ✅ |
|
||||||
|
| Logging from contract | ✅ |
|
||||||
|
| Transaction data (1) | ✅ |
|
||||||
|
| Block data (2) | ✅ |
|
||||||
|
| Contract data (3) | ✅ |
|
||||||
|
| Gas metering | ✅ |
|
||||||
|
|
||||||
|
Legend:
|
||||||
|
- `Soon` - the technology is already there, we just need to find some time to add the missing features :-)
|
||||||
|
- `(1)` - access current transaction data (id, owner, etc.)
|
||||||
|
- `(2)` - access current block data (indep_hash, height, timestamp)
|
||||||
|
- `(3)` - access current contract data (id, owner)
|
||||||
4
crates/warp-contracts/src/README.md
Normal file
4
crates/warp-contracts/src/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# contract_utils module
|
||||||
|
|
||||||
|
This is a module with boilerplate code for each SmartWeave RUST contract.
|
||||||
|
**Please don't modify it unless you 100% know what you are doing!**
|
||||||
65
crates/warp-contracts/src/foreign_call.rs
Normal file
65
crates/warp-contracts/src/foreign_call.rs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
use super::js_imports::SmartWeave;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_wasm_bindgen::from_value;
|
||||||
|
use core::fmt::Debug;
|
||||||
|
use warp_contracts_core::{
|
||||||
|
handler_result::{ViewResult, WriteResult},
|
||||||
|
methods::to_json_value,
|
||||||
|
warp_result::{transmission::from_json, WarpResult},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub async fn read_foreign_contract_state<T: DeserializeOwned>(
|
||||||
|
contract_address: &str,
|
||||||
|
) -> Result<T, String> {
|
||||||
|
match SmartWeave::read_contract_state(contract_address).await {
|
||||||
|
Ok(s) => match from_value::<T>(s) {
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
Err(e) => Err(format!("{e:?}")),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!("{e:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn view_foreign_contract_state<
|
||||||
|
V: DeserializeOwned + Debug,
|
||||||
|
I: Serialize,
|
||||||
|
E: DeserializeOwned + Debug,
|
||||||
|
>(
|
||||||
|
contract_address: &str,
|
||||||
|
input: I,
|
||||||
|
) -> ViewResult<V, E> {
|
||||||
|
let input = match to_json_value(&input) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return ViewResult::RuntimeError(format!("{e:?}")),
|
||||||
|
};
|
||||||
|
match SmartWeave::view_contract_state(contract_address, input).await {
|
||||||
|
Ok(s) => match from_json::<V, E>(s) {
|
||||||
|
WarpResult::WriteSuccess() => {
|
||||||
|
ViewResult::RuntimeError("got WriteResponse for view call".to_owned())
|
||||||
|
}
|
||||||
|
v => v.into(),
|
||||||
|
},
|
||||||
|
Err(e) => ViewResult::RuntimeError(format!("{e:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_foreign_contract<I: Serialize, E: DeserializeOwned + Debug>(
|
||||||
|
contract_address: &str,
|
||||||
|
input: I,
|
||||||
|
) -> WriteResult<(), E> {
|
||||||
|
let input = match to_json_value(&input) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return WriteResult::RuntimeError(format!("{e:?}")),
|
||||||
|
};
|
||||||
|
let write_result = SmartWeave::write(contract_address, input).await;
|
||||||
|
match write_result {
|
||||||
|
Ok(s) => match from_json::<(), E>(s) {
|
||||||
|
WarpResult::ViewSuccess(_) => {
|
||||||
|
WriteResult::RuntimeError("got ViewResponse for write call".to_owned())
|
||||||
|
}
|
||||||
|
v => v.into(),
|
||||||
|
},
|
||||||
|
Err(e) => WriteResult::RuntimeError(format!("{e:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
96
crates/warp-contracts/src/js_imports.rs
Normal file
96
crates/warp-contracts/src/js_imports.rs
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub type Block;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Block, js_name = indep_hash)]
|
||||||
|
pub fn indep_hash() -> String;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Block, js_name = height)]
|
||||||
|
pub fn height() -> i32;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Block, js_name = timestamp)]
|
||||||
|
pub fn timestamp() -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub type Contract;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Contract, js_name = contractId)]
|
||||||
|
pub fn id() -> String;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Contract, js_name = contractOwner)]
|
||||||
|
pub fn owner() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub type Transaction;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Transaction, js_name = id)]
|
||||||
|
pub fn id() -> String;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Transaction, js_name = owner)]
|
||||||
|
pub fn owner() -> String;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Transaction, js_name = target)]
|
||||||
|
pub fn target() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub type KV;
|
||||||
|
|
||||||
|
#[wasm_bindgen(catch, static_method_of = KV, js_name = kvGet)]
|
||||||
|
pub async fn get(key: &str) -> Result<JsValue, JsValue>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(catch, static_method_of = KV, js_name = kvPut)]
|
||||||
|
pub async fn put(key: &str, value: JsValue) -> Result<(), JsValue>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub type SmartWeave;
|
||||||
|
|
||||||
|
#[wasm_bindgen(catch, static_method_of = SmartWeave, js_name = readContractState)]
|
||||||
|
pub async fn read_contract_state(contract_id: &str) -> Result<JsValue, JsValue>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(catch, static_method_of = SmartWeave, js_name = viewContractState)]
|
||||||
|
pub async fn view_contract_state(contract_id: &str, input: JsValue)
|
||||||
|
-> Result<JsValue, JsValue>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(catch, static_method_of = SmartWeave, js_name = write)]
|
||||||
|
pub async fn write(contract_id: &str, input: JsValue) -> Result<JsValue, JsValue>;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = SmartWeave, js_name = refreshState)]
|
||||||
|
pub async fn refresh_state();
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = SmartWeave, js_name = caller)]
|
||||||
|
pub fn caller() -> String;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub type Vrf;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Vrf, js_name = value)]
|
||||||
|
pub fn value() -> String;
|
||||||
|
|
||||||
|
#[wasm_bindgen(static_method_of = Vrf, js_name = randomInt)]
|
||||||
|
pub fn randomInt(max_value: i32) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
extern "C" {
|
||||||
|
#[wasm_bindgen(js_namespace = console)]
|
||||||
|
pub fn log(s: &str);
|
||||||
|
}
|
||||||
31
crates/warp-contracts/src/kv_operations.rs
Normal file
31
crates/warp-contracts/src/kv_operations.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use super::js_imports::KV;
|
||||||
|
use serde::de::DeserializeOwned;
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_wasm_bindgen::from_value;
|
||||||
|
use warp_contracts_core::{handler_result::ViewResult, methods::to_json_value};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum KvError {
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn kv_get<T: DeserializeOwned + Default>(key: &str) -> ViewResult<T, KvError> {
|
||||||
|
match KV::get(key).await {
|
||||||
|
Ok(a) if !a.is_null() => match from_value(a) {
|
||||||
|
Ok(v) => ViewResult::Success(v),
|
||||||
|
Err(e) => ViewResult::RuntimeError(format!("{e:?}")),
|
||||||
|
},
|
||||||
|
Ok(_) => ViewResult::ContractError(KvError::NotFound),
|
||||||
|
Err(e) => ViewResult::RuntimeError(format!("{e:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn kv_put<T: Serialize>(key: &str, value: T) -> Result<(), String> {
|
||||||
|
match to_json_value(&value) {
|
||||||
|
Ok(v) => match KV::put(key, v).await {
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) => Err(format!("{e:?}")),
|
||||||
|
},
|
||||||
|
Err(e) => Err(format!("{:?}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
8
crates/warp-contracts/src/lib.rs
Normal file
8
crates/warp-contracts/src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
pub mod foreign_call;
|
||||||
|
pub mod js_imports;
|
||||||
|
pub mod kv_operations;
|
||||||
|
pub use warp_contracts_core::handler_result;
|
||||||
|
pub use warp_contracts_core::methods;
|
||||||
|
pub use warp_contracts_core::optional_cell;
|
||||||
|
pub use warp_contracts_core::warp_result;
|
||||||
|
pub use warp_contracts_macro::warp_contract;
|
||||||
21
crates/warp-contracts/warp-contracts-core/Cargo.toml
Normal file
21
crates/warp-contracts/warp-contracts-core/Cargo.toml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
[package]
|
||||||
|
name = "warp-contracts-core"
|
||||||
|
version = "0.1.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "warp-contracts helper crate"
|
||||||
|
license = "MIT"
|
||||||
|
documentation = "https://academy.warp.cc/docs/sdk/advanced/wasm"
|
||||||
|
homepage = "https://warp.cc"
|
||||||
|
repository = "https://github.com/warp-contracts/warp"
|
||||||
|
keywords = ["warp", "smart-contract", "SmartWeave", "web3"]
|
||||||
|
categories = ["api-bindings", "development-tools::ffi", "finance", "wasm"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
wasm-bindgen = { workspace = true }
|
||||||
|
wasm-bindgen-futures = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde-wasm-bindgen = { workspace = true }
|
||||||
|
web-sys = { workspace = true, optional = true }
|
||||||
|
|
||||||
|
[features]
|
||||||
|
debug = ["web-sys/console"]
|
||||||
4
crates/warp-contracts/warp-contracts-core/README.md
Normal file
4
crates/warp-contracts/warp-contracts-core/README.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# warp-contract-core
|
||||||
|
|
||||||
|
Helper library for warp-contracts and warp-contracts-macro. See the `warp-contracts` documentation for more details.
|
||||||
|
|
||||||
20
crates/warp-contracts/warp-contracts-core/src/detail.rs
Normal file
20
crates/warp-contracts/warp-contracts-core/src/detail.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// What we try to achieve here is setting common lifetime for State and Future objects
|
||||||
|
// but using higher-ranked trait bounds (https://doc.rust-lang.org/reference/trait-bounds.html#higher-ranked-trait-bounds),
|
||||||
|
// i.e. impossible to specify on the call side (see use of this trait).
|
||||||
|
// The effect is somewhat opposite to 'static lifetime, the lifetime shorter than anything passed by the user.
|
||||||
|
// Inspired by https://stackoverflow.com/a/63558160/3021277
|
||||||
|
pub trait BorrowingFn<'a, S, A, V> {
|
||||||
|
type Fut: core::future::Future<Output = V> + 'a;
|
||||||
|
fn call(self, state: &'a S, action: A) -> Self::Fut;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, Fu: 'a, F, S: 'a, A, V> BorrowingFn<'a, S, A, V> for F
|
||||||
|
where
|
||||||
|
F: FnOnce(&'a S, A) -> Fu,
|
||||||
|
Fu: core::future::Future<Output = V> + 'a,
|
||||||
|
{
|
||||||
|
type Fut = Fu;
|
||||||
|
fn call(self, state: &'a S, action: A) -> Fu {
|
||||||
|
self(state, action)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
#[derive(Debug)]
|
||||||
|
pub enum ViewResult<View, Error> {
|
||||||
|
Success(View),
|
||||||
|
ContractError(Error),
|
||||||
|
RuntimeError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum WriteResult<State, Error> {
|
||||||
|
Success(State),
|
||||||
|
ContractError(Error),
|
||||||
|
RuntimeError(String),
|
||||||
|
}
|
||||||
5
crates/warp-contracts/warp-contracts-core/src/lib.rs
Normal file
5
crates/warp-contracts/warp-contracts-core/src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod detail;
|
||||||
|
pub mod handler_result;
|
||||||
|
pub mod methods;
|
||||||
|
pub mod optional_cell;
|
||||||
|
pub mod warp_result;
|
||||||
185
crates/warp-contracts/warp-contracts-core/src/methods.rs
Normal file
185
crates/warp-contracts/warp-contracts-core/src/methods.rs
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
use crate::{
|
||||||
|
detail::BorrowingFn,
|
||||||
|
handler_result::{ViewResult, WriteResult},
|
||||||
|
optional_cell::{CloneContents, OptionalCell},
|
||||||
|
warp_result::transmission::Transmission,
|
||||||
|
warp_result::WarpResult,
|
||||||
|
};
|
||||||
|
use core::fmt::Debug;
|
||||||
|
use core::future::Future;
|
||||||
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
|
use serde_wasm_bindgen::from_value;
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
|
pub async fn write_async<State, Action, Error, Fun, Fut>(
|
||||||
|
state: &OptionalCell<State>,
|
||||||
|
interaction: JsValue,
|
||||||
|
write_contract_method: Fun,
|
||||||
|
) -> JsValue
|
||||||
|
where
|
||||||
|
State: Clone + Debug + Serialize,
|
||||||
|
Action: DeserializeOwned + Debug,
|
||||||
|
Error: Serialize,
|
||||||
|
Fun: FnOnce(State, Action) -> Fut,
|
||||||
|
Fut: Future<Output = WriteResult<State, Error>>,
|
||||||
|
{
|
||||||
|
let result = match parse_input(state, interaction) {
|
||||||
|
Err(value) => return value,
|
||||||
|
Ok(action) => write_contract_method(state.clone_contents(), action).await,
|
||||||
|
};
|
||||||
|
|
||||||
|
map_write_result(result, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_sync<State, Action, Error, Fun>(
|
||||||
|
s: &OptionalCell<State>,
|
||||||
|
interaction: JsValue,
|
||||||
|
write_contract_method: Fun,
|
||||||
|
) -> JsValue
|
||||||
|
where
|
||||||
|
State: Clone + Debug + Serialize,
|
||||||
|
Action: DeserializeOwned + Debug,
|
||||||
|
Error: Serialize,
|
||||||
|
Fun: FnOnce(State, Action) -> WriteResult<State, Error>,
|
||||||
|
{
|
||||||
|
let result = match parse_input(s, interaction) {
|
||||||
|
Err(value) => return value,
|
||||||
|
Ok(action) => write_contract_method(s.clone_contents(), action),
|
||||||
|
};
|
||||||
|
|
||||||
|
map_write_result(result, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need a dedicated trait BorrowingFn to attach the same lifetime
|
||||||
|
// that is not possible to specify by the caller of the view_async to both &State and Future
|
||||||
|
pub async fn view_async<State, Action, View, Error, Fun>(
|
||||||
|
s: &OptionalCell<State>,
|
||||||
|
interaction: JsValue,
|
||||||
|
view_contract_method: Fun,
|
||||||
|
) -> JsValue
|
||||||
|
where
|
||||||
|
State: Clone,
|
||||||
|
Action: DeserializeOwned + core::fmt::Debug,
|
||||||
|
View: Serialize + Debug,
|
||||||
|
Error: Serialize + Debug,
|
||||||
|
Fun: for<'a> BorrowingFn<'a, State, Action, ViewResult<View, Error>>,
|
||||||
|
{
|
||||||
|
let result = match parse_input(s, interaction) {
|
||||||
|
Err(value) => return value,
|
||||||
|
Ok(action) => {
|
||||||
|
view_contract_method
|
||||||
|
.call(s.cell.borrow().as_ref().unwrap(), action)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
to_json_value::<Transmission<View, Error>>(&WarpResult::from(result).into()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view_sync<State, Action, View, Error, Fun>(
|
||||||
|
s: &OptionalCell<State>,
|
||||||
|
interaction: JsValue,
|
||||||
|
view_contract_method: Fun,
|
||||||
|
) -> JsValue
|
||||||
|
where
|
||||||
|
State: Clone,
|
||||||
|
Action: DeserializeOwned + Debug,
|
||||||
|
View: Serialize,
|
||||||
|
Error: Serialize,
|
||||||
|
Fun: FnOnce(&State, Action) -> ViewResult<View, Error>,
|
||||||
|
{
|
||||||
|
let result = match parse_input(s, interaction) {
|
||||||
|
Err(value) => return value,
|
||||||
|
Ok(action) => view_contract_method(s.cell.borrow().as_ref().unwrap(), action),
|
||||||
|
};
|
||||||
|
|
||||||
|
to_json_value::<Transmission<View, Error>>(&WarpResult::from(result).into()).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn map_write_result<State, Error>(
|
||||||
|
result: WriteResult<State, Error>,
|
||||||
|
state: &OptionalCell<State>,
|
||||||
|
) -> JsValue
|
||||||
|
where
|
||||||
|
State: Clone + Debug + Serialize,
|
||||||
|
Error: Serialize,
|
||||||
|
{
|
||||||
|
if let WriteResult::Success(new_state) = result {
|
||||||
|
state.cell.replace(Some(new_state));
|
||||||
|
to_json_value::<Transmission<(), Error>>(&WarpResult::WriteSuccess().into()).unwrap()
|
||||||
|
} else {
|
||||||
|
to_json_value::<Transmission<(), Error>>(&WarpResult::from(result).into()).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_input<State, Action>(
|
||||||
|
state: &OptionalCell<State>,
|
||||||
|
interaction: JsValue,
|
||||||
|
) -> Result<Action, JsValue>
|
||||||
|
where
|
||||||
|
Action: DeserializeOwned + core::fmt::Debug,
|
||||||
|
State: Clone,
|
||||||
|
{
|
||||||
|
let action = from_value(interaction);
|
||||||
|
if action.is_err() {
|
||||||
|
return Err(runtime_error(format!(
|
||||||
|
"Error while parsing input {}",
|
||||||
|
action.unwrap_err()
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
if state.is_empty() {
|
||||||
|
return Err(runtime_error(format!(
|
||||||
|
"initState MUST be called before interaction can take place"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(action.unwrap())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_state<S: DeserializeOwned>(
|
||||||
|
state: &OptionalCell<S>,
|
||||||
|
init_state: &JsValue,
|
||||||
|
) -> Option<String> {
|
||||||
|
match from_value(init_state.clone()) {
|
||||||
|
Ok(parsed_state) => {
|
||||||
|
state.cell.replace(Some(parsed_state));
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let ret = format!("failed to parse init state {:?}", e);
|
||||||
|
#[cfg(feature = "debug")]
|
||||||
|
{
|
||||||
|
web_sys::console::debug_1(&JsValue::from_str(&ret));
|
||||||
|
}
|
||||||
|
Option::from(ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn current_state<State: Serialize + Debug>(state: &OptionalCell<State>) -> JsValue {
|
||||||
|
// not sure if that's deterministic - which is very important for the execution network.
|
||||||
|
// TODO: perf - according to docs:
|
||||||
|
// "This is unlikely to be super speedy so it's not recommended for large payload"
|
||||||
|
// - we should minimize calls to serde_wasm_bindgen::to_json_value
|
||||||
|
if state.is_empty() {
|
||||||
|
runtime_error(
|
||||||
|
"contract state not initialized. please run initState method first".to_owned(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
match to_json_value(state.cell.borrow().as_ref().unwrap()) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => runtime_error(format!("failed to serialize return value {:?}", e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_json_value<T: serde::ser::Serialize + ?Sized>(
|
||||||
|
value: &T,
|
||||||
|
) -> core::result::Result<JsValue, serde_wasm_bindgen::Error> {
|
||||||
|
let serializer = serde_wasm_bindgen::Serializer::json_compatible();
|
||||||
|
value.serialize(&serializer)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn runtime_error(error_message: String) -> JsValue {
|
||||||
|
to_json_value::<Transmission<(), ()>>(&WarpResult::RuntimeError(error_message).into()).unwrap()
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
use core::cell::RefCell;
|
||||||
|
|
||||||
|
/// Tiny wrapper over RefCell that:
|
||||||
|
/// - allow for uninitialized values (by storing Option)
|
||||||
|
/// - allow static usage (by implementing Sync and providing const constructor)
|
||||||
|
///
|
||||||
|
/// Because WASM is single threaded, we don't need to worry much about inter-thread communication
|
||||||
|
pub struct OptionalCell<T> {
|
||||||
|
pub cell: RefCell<Option<T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> OptionalCell<T> {
|
||||||
|
pub const fn empty() -> OptionalCell<T> {
|
||||||
|
OptionalCell {
|
||||||
|
cell: RefCell::new(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.cell.borrow().is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add clone_content to OptionalCell only if T is known to implement Clone.
|
||||||
|
// In particular to to use is_empty on OptionalCell trait Clone is not required on T.
|
||||||
|
pub trait CloneContents<T: Clone> {
|
||||||
|
fn clone_contents(&self) -> T;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Clone> CloneContents<T> for OptionalCell<T> {
|
||||||
|
fn clone_contents(&self) -> T {
|
||||||
|
self.cell.borrow().as_ref().unwrap().clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe impl<T> Sync for OptionalCell<T> {}
|
||||||
130
crates/warp-contracts/warp-contracts-core/src/warp_result.rs
Normal file
130
crates/warp-contracts/warp-contracts-core/src/warp_result.rs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
use crate::handler_result::{ViewResult, WriteResult};
|
||||||
|
|
||||||
|
pub enum WarpResult<View, Error> {
|
||||||
|
WriteSuccess(),
|
||||||
|
ViewSuccess(View),
|
||||||
|
ContractError(Error),
|
||||||
|
RuntimeError(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, E> From<ViewResult<V, E>> for WarpResult<V, E> {
|
||||||
|
fn from(value: ViewResult<V, E>) -> Self {
|
||||||
|
match value {
|
||||||
|
ViewResult::Success(v) => WarpResult::ViewSuccess(v),
|
||||||
|
ViewResult::ContractError(e) => WarpResult::ContractError(e),
|
||||||
|
ViewResult::RuntimeError(e) => WarpResult::RuntimeError(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S, V, E> From<WriteResult<S, E>> for WarpResult<V, E> {
|
||||||
|
fn from(value: WriteResult<S, E>) -> Self {
|
||||||
|
match value {
|
||||||
|
WriteResult::Success(_) => WarpResult::WriteSuccess(),
|
||||||
|
WriteResult::ContractError(e) => WarpResult::ContractError(e),
|
||||||
|
WriteResult::RuntimeError(e) => WarpResult::RuntimeError(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, E> From<WarpResult<V, E>> for ViewResult<V, E> {
|
||||||
|
fn from(value: WarpResult<V, E>) -> Self {
|
||||||
|
match value {
|
||||||
|
WarpResult::WriteSuccess() => unreachable!(),
|
||||||
|
WarpResult::ViewSuccess(v) => ViewResult::Success(v),
|
||||||
|
WarpResult::ContractError(e) => ViewResult::ContractError(e),
|
||||||
|
WarpResult::RuntimeError(e) => ViewResult::RuntimeError(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, E> From<WarpResult<V, E>> for WriteResult<(), E> {
|
||||||
|
fn from(value: WarpResult<V, E>) -> Self {
|
||||||
|
match value {
|
||||||
|
WarpResult::WriteSuccess() => WriteResult::Success(()),
|
||||||
|
WarpResult::ViewSuccess(_) => unreachable!(),
|
||||||
|
WarpResult::ContractError(e) => WriteResult::ContractError(e),
|
||||||
|
WarpResult::RuntimeError(e) => WriteResult::RuntimeError(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Module defining format as expected by SDK, covers (de)serialization.
|
||||||
|
// We have this separated from WarpResult above (visible to contract code) to
|
||||||
|
// keep WarpResult interface clean
|
||||||
|
pub mod transmission {
|
||||||
|
use super::WarpResult;
|
||||||
|
use core::fmt::Debug;
|
||||||
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct ErrorResult {
|
||||||
|
#[serde(rename = "errorMessage")]
|
||||||
|
error_message: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct Transmission<View, Error> {
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
result_type: String,
|
||||||
|
result: Option<View>,
|
||||||
|
error: Option<Error>,
|
||||||
|
#[serde(rename = "errorMessage")]
|
||||||
|
error_message: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V: Debug, E: Debug> From<Transmission<V, E>> for WarpResult<V, E> {
|
||||||
|
fn from(value: Transmission<V, E>) -> Self {
|
||||||
|
match value.result_type.as_str() {
|
||||||
|
"ok" if value.result.is_none() => WarpResult::WriteSuccess(),
|
||||||
|
"ok" => WarpResult::ViewSuccess(value.result.unwrap()),
|
||||||
|
"error" if value.error.is_some() => WarpResult::ContractError(value.error.unwrap()),
|
||||||
|
"exception" if value.error_message.is_some() => {
|
||||||
|
WarpResult::RuntimeError(value.error_message.unwrap())
|
||||||
|
}
|
||||||
|
_ => WarpResult::RuntimeError(format!("failed to parse response {:?}", value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<V, E> From<WarpResult<V, E>> for Transmission<V, E> {
|
||||||
|
fn from(value: WarpResult<V, E>) -> Self {
|
||||||
|
let mut res = Transmission {
|
||||||
|
result_type: "".to_owned(),
|
||||||
|
result: None,
|
||||||
|
error: None,
|
||||||
|
error_message: None,
|
||||||
|
};
|
||||||
|
match value {
|
||||||
|
WarpResult::WriteSuccess() => {
|
||||||
|
res.result_type = "ok".to_owned();
|
||||||
|
}
|
||||||
|
WarpResult::ViewSuccess(v) => {
|
||||||
|
res.result_type = "ok".to_owned();
|
||||||
|
res.result = Some(v);
|
||||||
|
}
|
||||||
|
WarpResult::ContractError(e) => {
|
||||||
|
res.result_type = "error".to_owned();
|
||||||
|
res.error = Some(e);
|
||||||
|
}
|
||||||
|
WarpResult::RuntimeError(e) => {
|
||||||
|
res.result_type = "exception".to_owned();
|
||||||
|
res.error_message = Some(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_json<V: Debug, E: Debug>(warp_result: JsValue) -> WarpResult<V, E>
|
||||||
|
where
|
||||||
|
V: DeserializeOwned,
|
||||||
|
E: DeserializeOwned,
|
||||||
|
{
|
||||||
|
match serde_wasm_bindgen::from_value::<Transmission<V, E>>(warp_result) {
|
||||||
|
Ok(t) => t.into(),
|
||||||
|
Err(e) => WarpResult::RuntimeError(format!("{e:?}")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
crates/warp-contracts/warp-contracts-macro/Cargo.toml
Normal file
19
crates/warp-contracts/warp-contracts-macro/Cargo.toml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[package]
|
||||||
|
name = "warp-contracts-macro"
|
||||||
|
version = "0.1.1"
|
||||||
|
edition = "2021"
|
||||||
|
description = "warp_contract macro definition"
|
||||||
|
license = "MIT"
|
||||||
|
documentation = "https://academy.warp.cc/docs/sdk/advanced/wasm"
|
||||||
|
homepage = "https://warp.cc"
|
||||||
|
repository = "https://github.com/warp-contracts/warp"
|
||||||
|
keywords = ["warp", "smart-contract", "SmartWeave", "web3"]
|
||||||
|
categories = ["api-bindings", "development-tools::ffi", "finance", "wasm"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0"
|
||||||
|
syn = { version = "1.0", features = ["extra-traits"] }
|
||||||
|
warp-contracts-core = { version = "0.1.1", path = "../warp-contracts-core" }
|
||||||
3
crates/warp-contracts/warp-contracts-macro/README.md
Normal file
3
crates/warp-contracts/warp-contracts-macro/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# warp-contracts-macro
|
||||||
|
|
||||||
|
Implementation of the #[warp_contract] attribute. See the `warp-contracts` documentation for more information.
|
||||||
8
crates/warp-contracts/warp-contracts-macro/src/lib.rs
Normal file
8
crates/warp-contracts/warp-contracts-macro/src/lib.rs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
|
mod warp_contract_macro;
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn warp_contract(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
warp_contract_macro::warp_contract(attr, input)
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
use proc_macro::TokenStream;
|
||||||
|
use quote::quote;
|
||||||
|
use syn::{parse_macro_input, FnArg, ItemFn};
|
||||||
|
|
||||||
|
pub(crate) fn warp_contract(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
let is_write = match attr.to_string().as_str() {
|
||||||
|
"write" => true,
|
||||||
|
"view" => false,
|
||||||
|
_ => panic!(
|
||||||
|
"warp_contract macro requires exactly one attribute: \
|
||||||
|
'write' for method changing state or 'view' for view state method \
|
||||||
|
e.g. #[warp_contract(write)], #[warp_contract(view)]"
|
||||||
|
),
|
||||||
|
};
|
||||||
|
let ast = parse_macro_input!(input as ItemFn);
|
||||||
|
let ast_clone = ast.clone();
|
||||||
|
let fun_name = ast.sig.ident;
|
||||||
|
let is_async = ast.sig.asyncness.is_some();
|
||||||
|
let (await_spec, write_core_method, view_core_method) = if is_async {
|
||||||
|
(
|
||||||
|
quote! { .await },
|
||||||
|
quote! { ::warp_contracts::methods::write_async },
|
||||||
|
quote! { ::warp_contracts::methods::view_async },
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(
|
||||||
|
quote! {},
|
||||||
|
quote! { ::warp_contracts::methods::write_sync },
|
||||||
|
quote! { ::warp_contracts::methods::view_sync },
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let inputs = ast.sig.inputs;
|
||||||
|
if inputs.len() != 2 {
|
||||||
|
panic!("two arguments expected for warp contract handle method, the first one representing state object and the second representing interaction description");
|
||||||
|
}
|
||||||
|
let state_type = match inputs.first().unwrap() {
|
||||||
|
FnArg::Receiver(_) => panic!("self not allowed in warp contract handle function"),
|
||||||
|
FnArg::Typed(t) => &t.ty,
|
||||||
|
};
|
||||||
|
if is_write {
|
||||||
|
quote! {
|
||||||
|
/*
|
||||||
|
Note: in order do optimize communication between host and the WASM module,
|
||||||
|
we're storing the state inside the WASM module (for the time of state evaluation).
|
||||||
|
This allows to reduce the overhead of passing the state back and forth
|
||||||
|
between the host and module with each contract interaction.
|
||||||
|
In case of bigger states this overhead can be huge.
|
||||||
|
Same approach has been implemented for the AssemblyScript version.
|
||||||
|
|
||||||
|
So the flow (from the SDK perspective) is:
|
||||||
|
1. SDK calls exported WASM module function "initState" (with lastly cached state or initial state,
|
||||||
|
if cache is empty) - which initializes the state in the WASM module.
|
||||||
|
2. SDK calls "handle" function for each of the interaction.
|
||||||
|
If given interaction was modifying the state - it is updated inside the WASM module
|
||||||
|
- but not returned to host.
|
||||||
|
3. Whenever SDK needs to know the current state (eg. in order to perform
|
||||||
|
caching or to simply get its value after evaluating all of the interactions)
|
||||||
|
- it calls WASM's module "currentState" function.
|
||||||
|
|
||||||
|
The handle function by default does not return the new state -
|
||||||
|
it only updates it in the WASM module.
|
||||||
|
The handle function returns a value only in case of error
|
||||||
|
or calling a "view" function.
|
||||||
|
|
||||||
|
In the future this might also allow to enhance the inner-contracts communication
|
||||||
|
- e.g. if the execution network will store the state of the contracts - as the WASM contract module memory
|
||||||
|
- it would allow to read other contract's state "directly" from WASM module memory.
|
||||||
|
*/
|
||||||
|
static __WARP_CONTRACT_STATE: ::warp_contracts::optional_cell::OptionalCell<#state_type> =
|
||||||
|
::warp_contracts::optional_cell::OptionalCell::empty();
|
||||||
|
|
||||||
|
const _: () = {
|
||||||
|
use ::core::{cell::RefCell, option::Option};
|
||||||
|
use ::serde::{Deserialize, Serialize};
|
||||||
|
use ::serde_wasm_bindgen::from_value;
|
||||||
|
use ::warp_contracts::{
|
||||||
|
optional_cell::OptionalCell,
|
||||||
|
warp_result::{transmission::Transmission, WarpResult},
|
||||||
|
js_imports::log,
|
||||||
|
methods::*
|
||||||
|
};
|
||||||
|
use ::wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = warpContractWrite)]
|
||||||
|
pub async fn __warp_contracts_generated_write(interaction: JsValue) -> JsValue {
|
||||||
|
#write_core_method(&__WARP_CONTRACT_STATE, interaction, #fun_name)#await_spec
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = initState)]
|
||||||
|
pub fn __warp_contracts_generated_init_state(state: &JsValue) -> Option<String> {
|
||||||
|
init_state(&__WARP_CONTRACT_STATE, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = currentState)]
|
||||||
|
pub fn __warp_contracts_generated_current_state() -> JsValue {
|
||||||
|
current_state(&__WARP_CONTRACT_STATE)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = version)]
|
||||||
|
pub fn __warp_contracts_generated_version() -> i32 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Workaround for now to simplify type reading without as/loader or wasm-bindgen
|
||||||
|
// 1 = assemblyscript
|
||||||
|
// 2 = rust
|
||||||
|
// 3 = go
|
||||||
|
// 4 = swift
|
||||||
|
// 5 = c
|
||||||
|
#[wasm_bindgen(js_name = lang)]
|
||||||
|
pub fn __warp_contracts_generated_lang() -> i32 {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
()
|
||||||
|
};
|
||||||
|
#ast_clone
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
quote! {
|
||||||
|
const _: () = {
|
||||||
|
use ::wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_name = warpContractView)]
|
||||||
|
pub async fn __warp_contracts_generated_view(interaction: JsValue) -> JsValue {
|
||||||
|
#view_core_method(&__WARP_CONTRACT_STATE, interaction, #fun_name)#await_spec
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#ast_clone
|
||||||
|
}.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -720,7 +720,7 @@ export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorVal
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
__wbg_log_3: function () {
|
__wbg_log: function () {
|
||||||
return logError(function (arg0, arg1) {
|
return logError(function (arg0, arg1) {
|
||||||
rawImports.console.log(getStringFromWasm0(arg0, arg1));
|
rawImports.console.log(getStringFromWasm0(arg0, arg1));
|
||||||
}, arguments)
|
}, arguments)
|
||||||
@@ -737,14 +737,12 @@ export const rustWasmImports = (swGlobal, wbindgenImports, wasmInstance, dtorVal
|
|||||||
return ret;
|
return ret;
|
||||||
},
|
},
|
||||||
|
|
||||||
__wbg_log_7: function () {
|
__wbg_debug: function () {
|
||||||
return logError(function (arg0) {
|
return logError(function (arg0) {
|
||||||
console.log(getObject(arg0));
|
console.log(getObject(arg0));
|
||||||
}, arguments)
|
}, arguments)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
__wbg_String: function () {
|
__wbg_String: function () {
|
||||||
return logError(function (arg0, arg1) {
|
return logError(function (arg0, arg1) {
|
||||||
const ret = String(getObject(arg1));
|
const ret = String(getObject(arg1));
|
||||||
|
|||||||
Reference in New Issue
Block a user