diff --git a/teeracle/Cargo.toml b/teeracle/Cargo.toml index a1ab15d6..7585a217 100644 --- a/teeracle/Cargo.toml +++ b/teeracle/Cargo.toml @@ -103,6 +103,9 @@ git = "https://github.com/paritytech/substrate.git" branch = "master" version = "4.0.0-dev" +[dev-dependencies.hex-literal] +version = "0.3.2" + [dependencies.teeracle-primitives] default-features = false package = "teeracle-primitives" diff --git a/teeracle/src/benchmarking.rs b/teeracle/src/benchmarking.rs index be133172..6335b813 100644 --- a/teeracle/src/benchmarking.rs +++ b/teeracle/src/benchmarking.rs @@ -53,12 +53,30 @@ benchmarks! { TEST4_SETUP.cert.to_vec(), URL.to_vec() ).unwrap(); - + let mrenclave = Teerex::::enclave(1).mr_enclave; + Exchange::::add_to_whitelist(RawOrigin::Root.into(), mrenclave); }: _(RawOrigin::Signed(signer), currency, Some(rate)) verify { assert_eq!(Exchange::::exchange_rate("usd".as_bytes().to_owned()), U32F32::from_num(43.65)); } + + add_to_whitelist { + let mrenclave = TEST4_MRENCLAVE; + + }: _(RawOrigin::Root, mrenclave) + verify { + assert_eq!(Exchange::::whitelist().len(), 1, "mrenclave not added to whitelist") + } + + remove_from_whitelist { + let mrenclave = TEST4_MRENCLAVE; + Exchange::::add_to_whitelist(RawOrigin::Root.into(), mrenclave); + + }: _(RawOrigin::Root, mrenclave) + verify { + assert_eq!(Exchange::::whitelist().len(), 0, "mrenclave not removed from whitelist") + } } #[cfg(test)] diff --git a/teeracle/src/lib.rs b/teeracle/src/lib.rs index e9d8d71d..c049ab03 100644 --- a/teeracle/src/lib.rs +++ b/teeracle/src/lib.rs @@ -37,7 +37,7 @@ pub use substrate_fixed::types::U32F32; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, WeakBoundedVec}; use frame_system::pallet_prelude::*; use sp_std::prelude::*; use teeracle_primitives::*; @@ -51,24 +51,40 @@ pub mod pallet { /// The overarching event type. type Event: From> + IsType<::Event>; type WeightInfo: WeightInfo; + /// Max number of whitelisted oracle's releases allowed + #[pallet::constant] + type MaxWhitelistedReleases: Get; } + /// Exchange rates chain's cryptocurrency/currency #[pallet::storage] #[pallet::getter(fn exchange_rate)] - pub(super) type ExchangeRates = + pub(super) type ExchangeRates = StorageMap<_, Blake2_128Concat, CurrencyString, ExchangeRate, ValueQuery>; + /// whitelist of trusted oracle's releases + #[pallet::storage] + #[pallet::getter(fn whitelist)] + pub(super) type Whitelist = + StorageValue<_, WeakBoundedVec<[u8; 32], T::MaxWhitelistedReleases>, ValueQuery>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// The exchange rate of currency was set/updated. \[currency], [new value\] ExchangeRateUpdated(CurrencyString, Option), ExchangeRateDeleted(CurrencyString), + AddedToWhitelist([u8; 32]), + RemovedFromWhitelist([u8; 32]), } #[pallet::error] pub enum Error { InvalidCurrency, + /// Too many MrEnclave in the whitelist. + ReleaseWhitelistOverflow, + ReleaseNotWhitelisted, + ReleaseAlreadyWhitelisted, } #[pallet::hooks] @@ -76,6 +92,24 @@ pub mod pallet { #[pallet::call] impl Pallet { + #[pallet::weight(::WeightInfo::add_to_whitelist())] + pub fn add_to_whitelist(origin: OriginFor, mrenclave: [u8; 32]) -> DispatchResult { + ensure_root(origin)?; + ensure!(!Self::is_whitelisted(mrenclave), >::ReleaseAlreadyWhitelisted); + >::try_append(mrenclave) + .map_err(|_| Error::::ReleaseWhitelistOverflow)?; + Self::deposit_event(Event::AddedToWhitelist(mrenclave)); + Ok(()) + } + #[pallet::weight(::WeightInfo::remove_from_whitelist())] + pub fn remove_from_whitelist(origin: OriginFor, mrenclave: [u8; 32]) -> DispatchResult { + ensure_root(origin)?; + ensure!(Self::is_whitelisted(mrenclave), >::ReleaseNotWhitelisted); + Whitelist::::mutate(|mrenclaves| mrenclaves.retain(|m| *m != mrenclave)); + Self::deposit_event(Event::RemovedFromWhitelist(mrenclave)); + Ok(()) + } + #[pallet::weight(::WeightInfo::update_exchange_rate())] pub fn update_exchange_rate( origin: OriginFor, @@ -84,6 +118,11 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; >::is_registered_enclave(&sender)?; + let sender_index = >::enclave_index(sender); + ensure!( + Self::is_whitelisted(>::enclave(sender_index).mr_enclave), + >::ReleaseNotWhitelisted + ); if new_value.is_none() || new_value == Some(U32F32::from_num(0)) { log::info!("Delete exchange rate : {:?}", new_value); ExchangeRates::::mutate_exists(currency.clone(), |rate| *rate = None); @@ -97,6 +136,11 @@ pub mod pallet { } } } +impl Pallet { + fn is_whitelisted(mrenclave: [u8; 32]) -> bool { + Self::whitelist().contains(&mrenclave) + } +} mod benchmarking; #[cfg(test)] diff --git a/teeracle/src/mock.rs b/teeracle/src/mock.rs index 1630eaf3..70823550 100644 --- a/teeracle/src/mock.rs +++ b/teeracle/src/mock.rs @@ -120,6 +120,7 @@ impl timestamp::Config for Test { parameter_types! { pub const MomentsPerDay: u64 = 86_400_000; // [ms/d] pub const MaxSilenceTime: u64 = 172_800_000; // 48h + pub const MaxWhitelistedReleases: u32 = 10; } impl pallet_teerex::Config for Test { @@ -133,6 +134,7 @@ impl pallet_teerex::Config for Test { impl Config for Test { type Event = Event; type WeightInfo = (); + type MaxWhitelistedReleases = MaxWhitelistedReleases; } // This function basically just builds a genesis storage key/value store according to diff --git a/teeracle/src/tests.rs b/teeracle/src/tests.rs index 39d80679..49f998d2 100644 --- a/teeracle/src/tests.rs +++ b/teeracle/src/tests.rs @@ -16,16 +16,21 @@ */ use crate::{mock::*, ExchangeRates}; use frame_support::{assert_err, assert_ok}; +use hex_literal::hex; use pallet_teerex::Error; +use sp_runtime::DispatchError::BadOrigin; use substrate_fixed::types::U32F32; -use test_utils::ias::consts::{TEST4_CERT, TEST4_SIGNER_PUB, TEST4_TIMESTAMP, URL}; +use test_utils::ias::consts::{ + TEST4_CERT, TEST4_MRENCLAVE, TEST4_SIGNER_PUB, TEST4_TIMESTAMP, TEST5_MRENCLAVE, + TEST5_SIGNER_PUB, TEST8_MRENCLAVE, URL, +}; // give get_signer a concrete type fn get_signer(pubkey: &[u8; 32]) -> AccountId { test_utils::get_signer(pubkey) } -fn verifiy_update_exchange_rate_for_dollars(rate: U32F32) { +fn register_enclave_and_add_oracle_to_whitelist_ok() { Timestamp::set_timestamp(TEST4_TIMESTAMP); let signer = get_signer(TEST4_SIGNER_PUB); assert_ok!(Teerex::register_enclave( @@ -33,39 +38,51 @@ fn verifiy_update_exchange_rate_for_dollars(rate: U32F32) { TEST4_CERT.to_vec(), URL.to_vec() )); + let mrenclave = Teerex::enclave(1).mr_enclave; + assert_ok!(Exchange::add_to_whitelist(Origin::root(), mrenclave)); +} + +fn update_exchange_rate_for_dollars_ok(rate: Option) { + let signer = get_signer(TEST4_SIGNER_PUB); assert_ok!(Exchange::update_exchange_rate( Origin::signed(signer), "usd".as_bytes().to_owned(), - Some(rate) + rate )); - let expected_event = - Event::Exchange(crate::Event::ExchangeRateUpdated("usd".as_bytes().to_owned(), Some(rate))); - assert!(System::events().iter().any(|a| a.event == expected_event)); } #[test] -fn verifiy_update_exchange_rate_works() { +fn update_exchange_rate_works() { new_test_ext().execute_with(|| { + register_enclave_and_add_oracle_to_whitelist_ok(); + let rate = U32F32::from_num(43.65); - verifiy_update_exchange_rate_for_dollars(rate); + update_exchange_rate_for_dollars_ok(Some(rate)); + let expected_event = Event::Exchange(crate::Event::ExchangeRateUpdated( + "usd".as_bytes().to_owned(), + Some(rate), + )); + assert!(System::events().iter().any(|a| a.event == expected_event)); assert_eq!(Exchange::exchange_rate("usd".as_bytes().to_owned()), rate); + let rate2 = U32F32::from_num(4294967295.65); - verifiy_update_exchange_rate_for_dollars(rate2); + update_exchange_rate_for_dollars_ok(Some(rate2)); assert_eq!(Exchange::exchange_rate("usd".as_bytes().to_owned()), rate2); }) } #[test] -fn verifiy_get_existing_exchange_rate_works() { +fn get_existing_exchange_rate_works() { new_test_ext().execute_with(|| { let rate = U32F32::from_num(43.65); - verifiy_update_exchange_rate_for_dollars(rate); + register_enclave_and_add_oracle_to_whitelist_ok(); + update_exchange_rate_for_dollars_ok(Some(rate)); assert_eq!(Exchange::exchange_rate("usd".as_bytes().to_owned()), rate); }) } #[test] -fn verifiy_get_inexisting_exchange_rate_is_zero() { +fn get_inexisting_exchange_rate_is_zero() { new_test_ext().execute_with(|| { assert_eq!(ExchangeRates::::contains_key("eur".as_bytes().to_owned()), false); assert_eq!(Exchange::exchange_rate("eur".as_bytes().to_owned()), U32F32::from_num(0)); @@ -73,16 +90,14 @@ fn verifiy_get_inexisting_exchange_rate_is_zero() { } #[test] -fn verifiy_update_exchange_rate_to_none_delete_exchange_rate() { +fn update_exchange_rate_to_none_delete_exchange_rate() { new_test_ext().execute_with(|| { + register_enclave_and_add_oracle_to_whitelist_ok(); let rate = U32F32::from_num(43.65); - verifiy_update_exchange_rate_for_dollars(rate); - let signer = get_signer(TEST4_SIGNER_PUB); - assert_ok!(Exchange::update_exchange_rate( - Origin::signed(signer), - "usd".as_bytes().to_owned(), - None - )); + update_exchange_rate_for_dollars_ok(Some(rate)); + + update_exchange_rate_for_dollars_ok(None); + let expected_event = Event::Exchange(crate::Event::ExchangeRateDeleted("usd".as_bytes().to_owned())); assert!(System::events().iter().any(|a| a.event == expected_event)); @@ -91,25 +106,23 @@ fn verifiy_update_exchange_rate_to_none_delete_exchange_rate() { } #[test] -fn verifiy_update_exchange_rate_to_zero_delete_exchange_rate() { +fn update_exchange_rate_to_zero_delete_exchange_rate() { new_test_ext().execute_with(|| { - let rate = U32F32::from_num(43.65); - let key = "usd".as_bytes().to_owned(); - verifiy_update_exchange_rate_for_dollars(rate); - let signer = get_signer(TEST4_SIGNER_PUB); - assert_ok!(Exchange::update_exchange_rate( - Origin::signed(signer), - key.clone(), - Some(U32F32::from_num(0)) - )); - let expected_event = Event::Exchange(crate::Event::ExchangeRateDeleted(key.clone())); + register_enclave_and_add_oracle_to_whitelist_ok(); + let rate = Some(U32F32::from_num(43.65)); + update_exchange_rate_for_dollars_ok(rate); + + update_exchange_rate_for_dollars_ok(Some(U32F32::from_num(0))); + + let expected_event = + Event::Exchange(crate::Event::ExchangeRateDeleted("usd".as_bytes().to_owned())); assert!(System::events().iter().any(|a| a.event == expected_event)); - assert_eq!(ExchangeRates::::contains_key(key), false); + assert_eq!(ExchangeRates::::contains_key("usd".as_bytes().to_owned()), false); }) } #[test] -fn verifiy_update_exchange_rate_from_not_registered_enclave_fails() { +fn update_exchange_rate_from_not_registered_enclave_fails() { new_test_ext().execute_with(|| { let signer = get_signer(TEST4_SIGNER_PUB); let rate = U32F32::from_num(43.65); @@ -123,3 +136,150 @@ fn verifiy_update_exchange_rate_from_not_registered_enclave_fails() { ); }) } + +#[test] +fn update_exchange_rate_from_not_whitelisted_oracle_fails() { + new_test_ext().execute_with(|| { + Timestamp::set_timestamp(TEST4_TIMESTAMP); + let signer = get_signer(TEST4_SIGNER_PUB); + assert_ok!(Teerex::register_enclave( + Origin::signed(signer.clone()), + TEST4_CERT.to_vec(), + URL.to_vec() + )); + + let rate = U32F32::from_num(43.65); + assert_err!( + Exchange::update_exchange_rate( + Origin::signed(signer), + "usd".as_bytes().to_owned(), + Some(rate) + ), + crate::Error::::ReleaseNotWhitelisted + ); + }) +} + +#[test] +fn add_to_whitelist_works() { + new_test_ext().execute_with(|| { + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE)); + let expected_event = Event::Exchange(crate::Event::AddedToWhitelist(TEST4_MRENCLAVE)); + assert!(System::events().iter().any(|a| a.event == expected_event)); + assert_eq!(Exchange::whitelist().len(), 1); + }) +} + +#[test] +fn add_two_times_to_whitelist_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE)); + assert_err!( + Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE), + crate::Error::::ReleaseAlreadyWhitelisted + ); + assert_eq!(Exchange::whitelist().len(), 1); + }) +} + +#[test] +fn add_too_many_oracles_to_whitelist_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE)); + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST5_MRENCLAVE)); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d2") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d3") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d4") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d5") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d6") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d7") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d8") + )); + assert_ok!(Exchange::add_to_whitelist( + Origin::root(), + hex!("f4dedfc9e5fcc48443332bc9b23161c34a3c3f5a692eaffdb228db27b704d9d9") + )); + assert_err!( + Exchange::add_to_whitelist(Origin::root(), TEST8_MRENCLAVE), + crate::Error::::ReleaseWhitelistOverflow + ); + assert_eq!(Exchange::whitelist().len(), 10); + }) +} + +#[test] +fn non_root_add_to_whitelist_fails() { + new_test_ext().execute_with(|| { + let signer = get_signer(TEST5_SIGNER_PUB); + assert_err!(Exchange::add_to_whitelist(Origin::signed(signer), TEST4_MRENCLAVE), BadOrigin); + assert_eq!(Exchange::whitelist().len(), 0); + }) +} + +#[test] +fn remove_from_whitelist_works() { + new_test_ext().execute_with(|| { + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE)); + assert_ok!(Exchange::remove_from_whitelist(Origin::root(), TEST4_MRENCLAVE)); + let expected_event = Event::Exchange(crate::Event::RemovedFromWhitelist(TEST4_MRENCLAVE)); + assert!(System::events().iter().any(|a| a.event == expected_event)); + assert_eq!(Exchange::whitelist().len(), 0); + }) +} + +#[test] +fn remove_from_whitelist_not_whitelisted_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE)); + assert_err!( + Exchange::remove_from_whitelist(Origin::root(), TEST5_MRENCLAVE), + crate::Error::::ReleaseNotWhitelisted + ); + assert_eq!(Exchange::whitelist().len(), 1); + }) +} + +#[test] +fn remove_from_empty_whitelist_doesnt_crash() { + new_test_ext().execute_with(|| { + assert_eq!(Exchange::whitelist().len(), 0); + assert_err!( + Exchange::remove_from_whitelist(Origin::root(), TEST5_MRENCLAVE), + crate::Error::::ReleaseNotWhitelisted + ); + assert_eq!(Exchange::whitelist().len(), 0); + }) +} + +#[test] +fn non_root_remove_from_whitelist_fails() { + new_test_ext().execute_with(|| { + let signer = get_signer(TEST5_SIGNER_PUB); + assert_ok!(Exchange::add_to_whitelist(Origin::root(), TEST4_MRENCLAVE)); + assert_err!( + Exchange::remove_from_whitelist(Origin::signed(signer), TEST4_MRENCLAVE), + BadOrigin + ); + assert_eq!(Exchange::whitelist().len(), 1); + }) +} diff --git a/teeracle/src/weights.rs b/teeracle/src/weights.rs index 20c92f17..02e78ba7 100644 --- a/teeracle/src/weights.rs +++ b/teeracle/src/weights.rs @@ -19,17 +19,31 @@ use sp_std::marker::PhantomData; /// Weight functions needed for pallet_exchange. pub trait WeightInfo { + fn add_to_whitelist() -> Weight; + fn remove_from_whitelist() -> Weight; fn update_exchange_rate() -> Weight; } pub struct IntegriteeWeight(PhantomData); impl WeightInfo for IntegriteeWeight { + fn add_to_whitelist() -> Weight { + 46_200_000 as Weight + } + fn remove_from_whitelist() -> Weight { + 46_200_000 as Weight + } fn update_exchange_rate() -> Weight { 46_200_000 as Weight } } // For tests impl WeightInfo for () { + fn add_to_whitelist() -> Weight { + 46_200_000 as Weight + } + fn remove_from_whitelist() -> Weight { + 46_200_000 as Weight + } fn update_exchange_rate() -> Weight { 46_200_000 as Weight }