Make sure frame examples compile for wasm (#5332)

* Make sure frame examples compile for wasm

This makes sure that `frame-example` and `frame-example-offchain-worker`
compile for wasm.

This also fixes compilation for these crates. The offchain worker
example doesn't use serde-json anymore as that is too heavy and breaks
`no_std` compilation.

* Apply suggestions from code review

Co-Authored-By: Nikolay Volf <nikvolf@gmail.com>

Co-authored-by: Nikolay Volf <nikvolf@gmail.com>
This commit is contained in:
Bastian Köcher
2020-03-20 16:57:39 +01:00
committed by GitHub
parent 57af4facbd
commit 46458f4082
6 changed files with 93 additions and 32 deletions
+18
View File
@@ -214,6 +214,24 @@ test-frame-staking:
- WASM_BUILD_NO_COLOR=1 time cargo test --release --verbose --no-default-features --features std
- sccache -s
test-frame-examples-compile-to-wasm:
stage: test
<<: *docker-env
variables:
# Enable debug assertions since we are running optimized builds for testing
# but still want to have debug assertions.
RUSTFLAGS: -Cdebug-assertions=y
RUST_BACKTRACE: 1
except:
variables:
- $DEPLOY_TAG
script:
- cd frame/example-offchain-worker/
- cargo +nightly build --target=wasm32-unknown-unknown --no-default-features
- cd ../example
- cargo +nightly build --target=wasm32-unknown-unknown --no-default-features
- sccache -s
test-wasmtime:
stage: test
<<: *docker-env
+14 -5
View File
@@ -3063,6 +3063,15 @@ dependencies = [
"linked-hash-map",
]
[[package]]
name = "lite-json"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faa835713bb12ba5204013497da16caf2dd2eee25ca829d0efaa054fb38c4ddd"
dependencies = [
"paste",
]
[[package]]
name = "lock_api"
version = "0.3.3"
@@ -4147,9 +4156,9 @@ version = "2.0.0-alpha.4"
dependencies = [
"frame-support",
"frame-system",
"lite-json",
"parity-scale-codec",
"serde",
"serde_json",
"sp-core",
"sp-io",
"sp-runtime",
@@ -6718,18 +6727,18 @@ checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0"
[[package]]
name = "serde"
version = "1.0.104"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.104"
version = "1.0.105"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8"
dependencies = [
"proc-macro2",
"quote",
@@ -17,7 +17,7 @@ sp-core = { version = "2.0.0-alpha.4", default-features = false, path = "../../p
sp-io = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/io" }
sp-runtime = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/std" }
serde_json = { version = "1.0.46", default-features = false, features = ["alloc"] }
lite-json = { version = "0.1", default-features = false }
[features]
default = ["std"]
@@ -26,6 +26,7 @@ std = [
"frame-support/std",
"frame-system/std",
"serde",
"lite-json/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
@@ -47,13 +47,14 @@ use frame_support::{
weights::SimpleDispatchInfo,
};
use frame_system::{self as system, ensure_signed, ensure_none, offchain};
use serde_json as json;
use sp_core::crypto::KeyTypeId;
use sp_runtime::{
offchain::{http, Duration, storage::StorageValueRef},
traits::Zero,
transaction_validity::{InvalidTransaction, ValidTransaction, TransactionValidity},
};
use sp_std::{vec, vec::Vec};
use lite_json::json::JsonValue;
#[cfg(test)]
mod tests;
@@ -320,7 +321,7 @@ impl<T: Trait> Module<T> {
}
/// A helper function to fetch the price and send signed transaction.
fn fetch_price_and_send_signed() -> Result<(), String> {
fn fetch_price_and_send_signed() -> Result<(), &'static str> {
use system::offchain::SubmitSignedTransaction;
// Firstly we check if there are any accounts in the local keystore that are capable of
// signing the transaction.
@@ -334,7 +335,7 @@ impl<T: Trait> Module<T> {
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|e| format!("{:?}", e))?;
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// Received price is wrapped into a call to `submit_price` public function of this pallet.
// This means that the transaction, when executed, will simply call that function passing
@@ -357,20 +358,18 @@ impl<T: Trait> Module<T> {
}
/// A helper function to fetch the price and send unsigned transaction.
fn fetch_price_and_send_unsigned(block_number: T::BlockNumber) -> Result<(), String> {
fn fetch_price_and_send_unsigned(block_number: T::BlockNumber) -> Result<(), &'static str> {
use system::offchain::SubmitUnsignedTransaction;
// Make sure we don't fetch the price if unsigned transaction is going to be rejected
// anyway.
let next_unsigned_at = <NextUnsignedAt<T>>::get();
if next_unsigned_at > block_number {
return Err(
format!("Too early to send unsigned transaction. Next at: {:?}", next_unsigned_at)
)?
return Err("Too early to send unsigned transaction")
}
// Make an external HTTP request to fetch the current price.
// Note this call will block until response is received.
let price = Self::fetch_price().map_err(|e| format!("{:?}", e))?;
let price = Self::fetch_price().map_err(|_| "Failed to fetch price")?;
// Received price is wrapped into a call to `submit_price_unsigned` public function of this
// pallet. This means that the transaction, when executed, will simply call that function
@@ -428,21 +427,17 @@ impl<T: Trait> Module<T> {
// Note that the return object allows you to read the body in chunks as well
// with a way to control the deadline.
let body = response.body().collect::<Vec<u8>>();
// Next we parse the response using `serde_json`. Even though it's possible to use
// `serde_derive` and deserialize to a struct it's not recommended due to blob size
// overhead introduced by such code. Deserializing to `json::Value` is much more
// lightweight and should be preferred, especially if we only care about a small number
// of properties from the response.
let val: Result<json::Value, _> = json::from_slice(&body);
// Let's parse the price as float value. Note that you should avoid using floats in the
// runtime, it's fine to do that in the offchain worker, but we do convert it to an integer
// before submitting on-chain.
let price = val.ok().and_then(|v| v.get("USD").and_then(|v| v.as_f64()));
let price = match price {
Some(pricef) => Ok((pricef * 100.) as u32),
// Create a str slice from the body.
let body_str = sp_std::str::from_utf8(&body).map_err(|_| {
debug::warn!("No UTF8 body");
http::Error::Unknown
})?;
let price = match Self::parse_price(body_str) {
Some(price) => Ok(price),
None => {
let s = core::str::from_utf8(&body);
debug::warn!("Unable to extract price from the response: {:?}", s);
debug::warn!("Unable to extract price from the response: {:?}", body_str);
Err(http::Error::Unknown)
}
}?;
@@ -452,6 +447,28 @@ impl<T: Trait> Module<T> {
Ok(price)
}
/// Parse the price from the given JSON string using `lite-json`.
///
/// Returns `None` when parsing failed or `Some(price in cents)` when parsing is successful.
fn parse_price(price_str: &str) -> Option<u32> {
let val = lite_json::parse_json(price_str);
let price = val.ok().and_then(|v| match v {
JsonValue::Object(obj) => {
let mut chars = "USD".chars();
obj.into_iter()
.find(|(k, _)| k.iter().all(|k| Some(*k) == chars.next()))
.and_then(|v| match v.1 {
JsonValue::Number(number) => Some(number),
_ => None,
})
},
_ => None
})?;
let exp = price.fraction_length.checked_sub(2).unwrap_or(0);
Some(price.integer as u32 * 100 + (price.fraction / 10_u64.pow(exp)) as u32)
}
/// Add new price to the list.
fn add_price(who: T::AccountId, price: u32) {
debug::info!("Adding to the average: {}", price);
@@ -132,7 +132,7 @@ fn should_make_http_call_and_parse_result() {
// when
let price = Example::fetch_price().unwrap();
// then
assert_eq!(price, 15522);
assert_eq!(price, 15523);
});
}
@@ -164,7 +164,7 @@ fn should_submit_signed_transaction_on_chain() {
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature.unwrap().0, 0);
assert_eq!(tx.call, Call::submit_price(15522));
assert_eq!(tx.call, Call::submit_price(15523));
});
}
@@ -186,7 +186,7 @@ fn should_submit_unsigned_transaction_on_chain() {
assert!(pool_state.read().transactions.is_empty());
let tx = Extrinsic::decode(&mut &*tx).unwrap();
assert_eq!(tx.signature, None);
assert_eq!(tx.call, Call::submit_price_unsigned(1, 15522));
assert_eq!(tx.call, Call::submit_price_unsigned(1, 15523));
});
}
@@ -208,3 +208,19 @@ fn price_oracle_response(state: &mut testing::OffchainState) {
..Default::default()
});
}
#[test]
fn parse_price_works() {
let test_data = vec![
("{\"USD\":6536.92}", Some(653692)),
("{\"USD\":65.92}", Some(6592)),
("{\"USD\":6536.924565}", Some(653692)),
("{\"USD\":6536}", Some(653600)),
("{\"USD2\":6536}", None),
("{\"USD\":\"6432\"}", None),
];
for (json, expected) in test_data {
assert_eq!(expected, Example::parse_price(json));
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ sp-std = { version = "2.0.0-alpha.4", default-features = false, path = "../../pr
sp-io = { version = "2.0.0-alpha.4", default-features = false, path = "../../primitives/io" }
[dev-dependencies]
sp-core = { version = "2.0.0-alpha.4", path = "../../primitives/core" }
sp-core = { version = "2.0.0-alpha.4", path = "../../primitives/core", default-features = false }
[features]
default = ["std"]