Skip to content
Snippets Groups Projects
Unverified Commit a5c369cc authored by Shaopeng Wang's avatar Shaopeng Wang Committed by GitHub
Browse files

Oracle RPC (#83)

* Oracle rpc api.

* Manual Serialize impl for FixedU128; Manual Display impl for TimestampedValue.

* Remove unused Display impls.

* Fixed some wired failing existing unit tests.

* Impl Deserialize for FixedU128.

* Rename oracle rpc method name.

* fmt

* Renaming.
parent 4a26ce44
No related branches found
No related tags found
No related merge requests found
[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" }
[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",
]
//! 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>;
}
}
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()
}
}
......@@ -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> {
......
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,
......
......@@ -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]
......
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);
}
}
......@@ -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);
});
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment