diff --git a/oracle/rpc/Cargo.toml b/oracle/rpc/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..ad2f0efdb963a5aeb84a5fa77bc9fd35a989e295 --- /dev/null +++ b/oracle/rpc/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "orml-oracle-rpc" +version = "0.0.1" +authors = ["Laminar Developers <hello@laminar.one>"] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.0.0" } +jsonrpc-core = "14.0.3" +jsonrpc-core-client = "14.0.3" +jsonrpc-derive = "14.0.3" +sp-runtime = { git = "https://github.com/paritytech/substrate.git" } +sp-api = { git = "https://github.com/paritytech/substrate.git" } +sp-blockchain = { git = "https://github.com/paritytech/substrate.git" } +orml-oracle-rpc-runtime-api = { path = "runtime-api" } diff --git a/oracle/rpc/runtime-api/Cargo.toml b/oracle/rpc/runtime-api/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..6d449385ffe42eaffcb731b9eb3d04620bf0db6b --- /dev/null +++ b/oracle/rpc/runtime-api/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "orml-oracle-rpc-runtime-api" +version = "0.1.0" +authors = ["Laminar Developers <hello@laminar.one>"] +edition = "2018" + +[dependencies] +codec = { package = "parity-scale-codec", version = "1.0.6", default-features = false, features = ["derive"] } +sp-api = { version = "2.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git" } + +[features] +default = ["std"] +std = [ + "sp-api/std", + "codec/std", +] diff --git a/oracle/rpc/runtime-api/src/lib.rs b/oracle/rpc/runtime-api/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..437fa40eb0b39d2c7d8381720013345a08d8aa0a --- /dev/null +++ b/oracle/rpc/runtime-api/src/lib.rs @@ -0,0 +1,14 @@ +//! Runtime API definition for oracle module. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; + +sp_api::decl_runtime_apis! { + pub trait OracleApi<Key, Value> where + Key: Codec, + Value: Codec, + { + fn get_no_op(key: Key) -> Option<Value>; + } +} diff --git a/oracle/rpc/src/lib.rs b/oracle/rpc/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..124dd6dfba0b82954962d79075e254fa61ca072e --- /dev/null +++ b/oracle/rpc/src/lib.rs @@ -0,0 +1,66 @@ +pub use self::gen_client::Client as OracleClient; +use codec::Codec; +use jsonrpc_core::{Error as RpcError, ErrorCode, Result}; +use jsonrpc_derive::rpc; +pub use orml_oracle_rpc_runtime_api::OracleApi as OracleRuntimeApi; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use std::sync::Arc; + +#[rpc] +pub trait OracleApi<BlockHash, Key, Value> { + #[rpc(name = "oracle_getValue")] + fn get_value(&self, key: Key, at: Option<BlockHash>) -> Result<Option<Value>>; +} + +/// A struct that implements the [`OracleApi`]. +pub struct Oracle<C, B> { + client: Arc<C>, + _marker: std::marker::PhantomData<B>, +} + +impl<C, B> Oracle<C, B> { + /// Create new `Oracle` with the given reference to the client. + pub fn new(client: Arc<C>) -> Self { + Oracle { + client, + _marker: Default::default(), + } + } +} + +pub enum Error { + RuntimeError, +} + +impl From<Error> for i64 { + fn from(e: Error) -> i64 { + match e { + Error::RuntimeError => 1, + } + } +} + +impl<C, Block, Key, Value> OracleApi<<Block as BlockT>::Hash, Key, Value> for Oracle<C, Block> +where + Block: BlockT, + C: Send + Sync + 'static + ProvideRuntimeApi<Block> + HeaderBackend<Block>, + C::Api: OracleRuntimeApi<Block, Key, Value>, + Key: Codec, + Value: Codec, +{ + fn get_value(&self, key: Key, at: Option<<Block as BlockT>::Hash>) -> Result<Option<Value>> { + let api = self.client.runtime_api(); + let at = BlockId::hash(at.unwrap_or_else(|| + // If the block hash is not supplied assume the best block. + self.client.info().best_hash)); + api.get_no_op(&at, key) + .map_err(|e| RpcError { + code: ErrorCode::ServerError(Error::RuntimeError.into()), + message: "Unable to get value.".into(), + data: Some(format!("{:?}", e).into()), + }) + .into() + } +} diff --git a/oracle/src/lib.rs b/oracle/src/lib.rs index ba1f79249cc7ad4840a613c247100a17cdef3928..61823c16fa1d0d0e34ce36d0a7803382da46b1ea 100644 --- a/oracle/src/lib.rs +++ b/oracle/src/lib.rs @@ -82,15 +82,33 @@ impl<T: Trait> Module<T> { .collect() } + /// Returns fresh combined value if has update, or latest combined value. + /// + /// Note this will update values storage if has update. pub fn get(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> { if <HasUpdate<T>>::take(key) { - let values = Self::read_raw_values(key); - let timestamped = T::CombineData::combine_data(key, values, <Values<T>>::get(key))?; + let timestamped = Self::combined(key)?; <Values<T>>::insert(key, timestamped.clone()); return Some(timestamped); } <Values<T>>::get(key) } + + /// Returns fresh combined value if has update, or latest combined value. + /// + /// This is a no-op function which would not change storage. + pub fn get_no_op(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> { + if Self::has_update(key) { + Self::combined(key) + } else { + Self::values(key) + } + } + + fn combined(key: &T::OracleKey) -> Option<TimestampedValueOf<T>> { + let values = Self::read_raw_values(key); + T::CombineData::combine_data(key, values, Self::values(key)) + } } impl<T: Trait> DataProvider<T::OracleKey, T::OracleValue> for Module<T> { diff --git a/oracle/src/timestamped_value.rs b/oracle/src/timestamped_value.rs index 0b29aac7b83ad20822a73b7e4b0637caf2fbc388..26b80d7334aa47b67b24ac7b9b6260dd956b19a1 100644 --- a/oracle/src/timestamped_value.rs +++ b/oracle/src/timestamped_value.rs @@ -1,7 +1,11 @@ use codec::{Decode, Encode}; use sp_runtime::RuntimeDebug; +#[cfg(feature = "std")] +use serde::{Deserialize, Serialize}; + #[derive(Encode, Decode, RuntimeDebug, Eq, PartialEq, Clone, Copy)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct TimestampedValue<Value, Moment> { pub value: Value, pub timestamp: Moment, diff --git a/utilities/Cargo.toml b/utilities/Cargo.toml index 6d065cdd0420cf098fb452a017b2bb654957d9af..05d08a8263b6c5401ab9caf479cdfc5f09f17170 100644 --- a/utilities/Cargo.toml +++ b/utilities/Cargo.toml @@ -16,7 +16,7 @@ frame-system = { git = "https://github.com/paritytech/substrate.git", default-fe frame-support = { git = "https://github.com/paritytech/substrate.git", default-features = false } [dev-dependencies] - +serde_json = "1.0.41" clear_on_drop = { version = "0.2.3", features = ["no_cc"] } # https://github.com/paritytech/substrate/issues/4179 [features] diff --git a/utilities/src/fixed128.rs b/utilities/src/fixed128.rs index f3cd0029263194afef3b02d4738dcba045cdcaae..8b0d314a126ceb17b8bb9a133eeb0ee878236f6c 100644 --- a/utilities/src/fixed128.rs +++ b/utilities/src/fixed128.rs @@ -1,18 +1,21 @@ use codec::{Decode, Encode}; use primitives::U256; -use rstd::convert::{Into, TryFrom, TryInto}; +use rstd::{ + convert::{Into, TryFrom, TryInto}, + result::Result, + vec::Vec, +}; use sp_runtime::{ traits::{Bounded, Saturating, UniqueSaturatedInto}, Perbill, Percent, Permill, Perquintill, }; #[cfg(feature = "std")] -use serde::{Deserialize, Serialize}; +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; /// An unsigned fixed point number. Can hold any value in the range [0, 340_282_366_920_938_463_464] /// with fixed point accuracy of 10 ** 18. #[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct FixedU128(u128); const DIV: u128 = 1_000_000_000_000_000_000; @@ -211,6 +214,61 @@ impl_perthing_into_fixed_u128!(Permill); impl_perthing_into_fixed_u128!(Perbill); impl_perthing_into_fixed_u128!(Perquintill); +#[cfg(feature = "std")] +impl FixedU128 { + fn str_with_precision(&self) -> String { + format!("{}.{}", &self.0 / DIV, &self.0 % DIV) + } + + fn from_str_with_precision(s: &str) -> Result<Self, &'static str> { + let err = "invalid string input"; + let vec_str: Vec<&str> = s.split(".").collect(); + + // parsing to decimal and fractional parts + let (decimal_str, fractional_str) = match vec_str.as_slice() { + &[d] => (d, "0"), + &[d, f] => (d, f), + _ => return Err(err), + }; + + let decimal: u128 = decimal_str.parse().map_err(|_| err)?; + let decimal_with_precision = decimal.checked_mul(DIV).ok_or(err)?; + // width = 18; precision = 18 + let padded_fractional_string = format!("{:0<18.18}", fractional_str); + let fractional_with_precision: u128 = padded_fractional_string.parse().map_err(|_| err)?; + + let parts = decimal_with_precision + .checked_add(fractional_with_precision) + .ok_or(err)?; + Ok(Self::from_parts(parts)) + } +} + +// Manual impl `Serialize` as serde_json does not support u128. +// TODO: remove impl if issue https://github.com/serde-rs/json/issues/548 fixed. +#[cfg(feature = "std")] +impl Serialize for FixedU128 { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + serializer.serialize_str(&self.str_with_precision()) + } +} + +// Manual impl `Serialize` as serde_json does not support u128. +// TODO: remove impl if issue https://github.com/serde-rs/json/issues/548 fixed. +#[cfg(feature = "std")] +impl<'de> Deserialize<'de> for FixedU128 { + fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + FixedU128::from_str_with_precision(&s).map_err(|err_str| de::Error::custom(err_str)) + } +} + #[cfg(test)] mod tests { use super::*; @@ -361,4 +419,45 @@ mod tests { let a = FixedU128::from_natural(0); assert_eq!(a.recip(), None); } + + #[test] + fn from_str_with_precision_should_work() { + assert_eq!( + FixedU128::from_str_with_precision("1").unwrap(), + FixedU128::from_natural(1) + ); + assert_eq!( + FixedU128::from_str_with_precision("1.0").unwrap(), + FixedU128::from_natural(1) + ); + assert_eq!( + FixedU128::from_str_with_precision("0.1").unwrap(), + FixedU128::from_rational(1, 10) + ); + assert_eq!( + FixedU128::from_str_with_precision("2.5").unwrap(), + FixedU128::from_rational(5, 2) + ); + assert_eq!( + FixedU128::from_str_with_precision("0.1000000000000000111").unwrap(), + FixedU128::from_rational(100000000000000011u128, 1000000000000000000u128) + ); + + assert!(FixedU128::from_str_with_precision(".").is_err()); + assert!(FixedU128::from_str_with_precision("").is_err()); + assert!(FixedU128::from_str_with_precision("1.1.1").is_err()); + assert!(FixedU128::from_str_with_precision("a.1").is_err()); + assert!(FixedU128::from_str_with_precision("1.a").is_err()); + // 340282366920938463464 == u128::max_value() / DIV + 1; overflows + assert!(FixedU128::from_str_with_precision("340282366920938463464").is_err()); + } + + #[test] + fn serialize_deserialize_should_work() { + let two_point_five = FixedU128::from_rational(5, 2); + let serialized = serde_json::to_string(&two_point_five).unwrap(); + assert_eq!(serialized, "\"2.500000000000000000\""); + let deserialized: FixedU128 = serde_json::from_str(&serialized).unwrap(); + assert_eq!(deserialized, two_point_five); + } } diff --git a/utilities/src/linked_item.rs b/utilities/src/linked_item.rs index 9e8f3a849b77995e73f7e22222f4aeaa8512c784..690a279deb13fed6aec74a50b1ecc35d22289c64 100644 --- a/utilities/src/linked_item.rs +++ b/utilities/src/linked_item.rs @@ -367,7 +367,8 @@ mod tests { #[test] fn linked_list_can_enumerate() { new_test_ext().execute_with(|| { - assert_eq!(TestLinkedList::enumerate(&0).collect::<Vec<_>>(), []); + let v: Vec<u32> = vec![]; + assert_eq!(TestLinkedList::enumerate(&0).collect::<Vec<_>>(), v); TestLinkedList::append(&0, 1); TestLinkedList::append(&0, 2); @@ -384,7 +385,8 @@ mod tests { #[test] fn linked_list_can_take_all() { new_test_ext().execute_with(|| { - assert_eq!(TestLinkedList::take_all(&0).collect::<Vec<_>>(), []); + let v: Vec<u32> = vec![]; + assert_eq!(TestLinkedList::take_all(&0).collect::<Vec<_>>(), v); TestLinkedList::append(&0, 1); TestLinkedList::append(&0, 2); @@ -395,14 +397,15 @@ mod tests { assert_eq!(TestItem::get(&(0, Some(1))), None); assert_eq!(TestItem::get(&(0, Some(2))), None); assert_eq!(TestItem::get(&(0, Some(3))), None); - assert_eq!(TestLinkedList::enumerate(&0).collect::<Vec<_>>(), []); + assert_eq!(TestLinkedList::enumerate(&0).collect::<Vec<_>>(), v); }); } #[test] fn linked_list_take_all_is_safe() { new_test_ext().execute_with(|| { - assert_eq!(TestLinkedList::take_all(&0).collect::<Vec<_>>(), []); + let v: Vec<u32> = vec![]; + assert_eq!(TestLinkedList::take_all(&0).collect::<Vec<_>>(), v); TestLinkedList::append(&0, 1); TestLinkedList::append(&0, 2); @@ -413,7 +416,7 @@ mod tests { assert_eq!(TestItem::get(&(0, Some(1))), None); assert_eq!(TestItem::get(&(0, Some(2))), None); assert_eq!(TestItem::get(&(0, Some(3))), None); - assert_eq!(TestLinkedList::enumerate(&0).collect::<Vec<_>>(), []); + assert_eq!(TestLinkedList::enumerate(&0).collect::<Vec<_>>(), v); }); } }