diff --git a/Cargo.lock b/Cargo.lock index fc8d501264..332083474d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2792,6 +2792,39 @@ dependencies = [ "substrate-api-client", ] +[[package]] +name = "ita-raffle-stf" +version = "0.9.0" +dependencies = [ + "frame-support", + "frame-system", + "ita-sgx-runtime", + "itp-hashing", + "itp-node-api", + "itp-node-api-metadata", + "itp-sgx-externalities", + "itp-sgx-runtime-primitives", + "itp-stf-interface", + "itp-stf-primitives", + "itp-storage", + "itp-types", + "itp-utils", + "log 0.4.20", + "pallet-balances", + "pallet-parentchain", + "pallet-raffles", + "pallet-sudo", + "parity-scale-codec", + "rlp", + "sgx_tstd", + "sha3", + "sp-core", + "sp-io 7.0.0", + "sp-keyring", + "sp-runtime", + "sp-std", +] + [[package]] name = "ita-sgx-runtime" version = "0.9.0" @@ -2803,11 +2836,13 @@ dependencies = [ "pallet-balances", "pallet-evm", "pallet-parentchain", + "pallet-raffles", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", "scale-info", + "sgx_rand", "sp-api", "sp-core", "sp-runtime", @@ -2821,6 +2856,7 @@ version = "0.9.0" dependencies = [ "frame-support", "frame-system", + "ita-raffle-stf", "ita-sgx-runtime", "itp-hashing", "itp-node-api", @@ -5177,6 +5213,26 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-raffles" +version = "0.11.0" +dependencies = [ + "env_logger 0.9.3", + "frame-support", + "frame-system", + "itp-binary-merkle-tree", + "log 0.4.20", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "serde 1.0.193", + "sp-core", + "sp-io 7.0.0 (git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.42)", + "sp-keyring", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-sidechain" version = "0.11.0" diff --git a/Cargo.toml b/Cargo.toml index a850abc167..017245be22 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,8 @@ resolver = "2" members = [ "app-libs/oracle", "app-libs/parentchain-interface", + "app-libs/raffle/stf", + "app-libs/raffle/pallet-raffle", "app-libs/sgx-runtime", "app-libs/stf", "cli", diff --git a/app-libs/raffle/pallet-raffle/Cargo.toml b/app-libs/raffle/pallet-raffle/Cargo.toml new file mode 100644 index 0000000000..e9a2282024 --- /dev/null +++ b/app-libs/raffle/pallet-raffle/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "pallet-raffles" +description = "The remote attestation registry and verification pallet for integritee blockchains and parachains" +version = "0.11.0" +authors = ["Integritee AG "] +homepage = "https://integritee.network/" +repository = "https://github.com/integritee-network/pallets/" +license = "Apache-2.0" +edition = "2021" + +[dependencies] +log = { version = "0.4", default-features = false } +parity-scale-codec = { version = "3.0.0", default-features = false, features = ["derive"] } +scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"] } + +itp-binary-merkle-tree = { default-features = false, path = "../../../core-primitives/binary-merkle-tree" } + +# substrate dependencies +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-core = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-io = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } + +[dev-dependencies] +env_logger = "0.9.0" +pallet-balances = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "log/std", + "itp-binary-merkle-tree/std", + "parity-scale-codec/std", + "scale-info/std", + "serde/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] + +try-runtime = ["frame-support/try-runtime"] diff --git a/app-libs/raffle/pallet-raffle/src/lib.rs b/app-libs/raffle/pallet-raffle/src/lib.rs new file mode 100644 index 0000000000..fb5472a0e1 --- /dev/null +++ b/app-libs/raffle/pallet-raffle/src/lib.rs @@ -0,0 +1,232 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{dispatch::DispatchResult, ensure}; +use itp_binary_merkle_tree::{merkle_proof, merkle_root, MerkleProofWithCodec}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_core::{MaxEncodedLen, H256}; +use sp_runtime::traits::Keccak256; +use sp_std::{fmt::Debug, vec::Vec}; + +pub use pallet::*; + +pub mod merkle_tree { + pub use itp_binary_merkle_tree::{ + merkle_proof, merkle_root, verify_proof, MerkleProofWithCodec, + }; + pub use sp_runtime::traits::Keccak256; +} + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +pub type RaffleIndex = u32; +pub type WinnerCount = u32; + +#[derive(Debug, Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub struct RaffleMetadata { + index: RaffleIndex, + raffle: Raffle, +} + +#[derive(Debug, Clone, Encode, Decode, Eq, PartialEq, TypeInfo, MaxEncodedLen)] +pub struct Raffle { + owner: AccountId, + winner_count: WinnerCount, + registration_open: bool, +} + +#[frame_support::pallet] +pub mod pallet { + use crate::{weights::WeightInfo, Raffle, RaffleIndex, Shuffle, WinnerCount}; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_core::H256; + use sp_std::vec::Vec; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::without_storage_info] + pub struct Pallet(PhantomData); + + /// Configuration trait. + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type WeightInfo: WeightInfo; + + /// Implements random shuffling of values. + /// + /// If you use this on-chain you need to make sure to have a deterministic seed based + /// on-chain values. If you use this in SGX, we want to make sure that the randomness + /// is as secure as possible, hence we use the SGX's randomness, which uses a hardware + /// secured randomness source: https://en.wikipedia.org/wiki/RDRAND. + type Shuffle: Shuffle; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A new raffle has been registered + RaffleAdded { index: RaffleIndex, raffle: Raffle }, + + /// Someone has registered for a raffle + RaffleRegistration { who: T::AccountId, index: RaffleIndex }, + + /// Winners were drawn of a raffle + WinnersDrawn { index: RaffleIndex, winners: Vec, registrations_root: H256 }, + } + + #[pallet::error] + pub enum Error { + /// The raffle does not exist + NonexistentRaffle, + /// The registrations for that raffles are closed + RegistrationsClosed, + /// Only the raffle owner can draw the winners + OnlyRaffleOwnerCanDrawWinners, + } + + /// Ongoing raffles. + #[pallet::storage] + #[pallet::getter(fn ongoing_raffles)] + pub type OnGoingRaffles = + StorageMap<_, Blake2_128Concat, RaffleIndex, Raffle, OptionQuery>; + + /// Ongoing raffles. + #[pallet::storage] + #[pallet::getter(fn winners)] + pub type Winners = + StorageMap<_, Blake2_128Concat, RaffleIndex, Vec, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn registrations)] + pub type Registrations = StorageDoubleMap< + _, + Blake2_128Concat, + RaffleIndex, + Blake2_128Concat, + T::AccountId, + (), + OptionQuery, + >; + + #[pallet::storage] + #[pallet::getter(fn raffle_count)] + pub type RaffleCount = StorageValue<_, RaffleIndex, ValueQuery>; + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::add_raffle())] + pub fn add_raffle(origin: OriginFor, winner_count: WinnerCount) -> DispatchResult { + let sender = ensure_signed(origin)?; + let index = Self::raffle_count(); + + let raffle = Raffle { owner: sender, winner_count, registration_open: true }; + + OnGoingRaffles::::insert(index, &raffle); + RaffleCount::::put(index + 1); + + Self::deposit_event(Event::RaffleAdded { index, raffle }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::register_for_raffle())] + pub fn register_for_raffle(origin: OriginFor, index: RaffleIndex) -> DispatchResult { + let sender = ensure_signed(origin)?; + + ensure!(OnGoingRaffles::::contains_key(index), Error::::NonexistentRaffle); + ensure!( + OnGoingRaffles::::get(index) + .expect("Asserted above that the key exists; qed") + .registration_open, + Error::::RegistrationsClosed + ); + + Registrations::::insert(index, &sender, ()); + + Self::deposit_event(Event::RaffleRegistration { who: sender, index }); + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::draw_winners())] + pub fn draw_winners(origin: OriginFor, index: RaffleIndex) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::try_draw_winners(sender, index) + } + } +} + +impl Pallet { + pub fn all_ongoing_raffles() -> Vec> { + OnGoingRaffles::::iter() + .map(|kv| RaffleMetadata { index: kv.0, raffle: kv.1 }) + .collect() + } + + pub fn raffle_registrations(index: RaffleIndex) -> Vec { + Registrations::::iter_prefix(index).map(|kv| kv.0).collect() + } + + fn try_draw_winners(owner: T::AccountId, index: RaffleIndex) -> DispatchResult { + let raffle = OnGoingRaffles::::get(index).ok_or(Error::::NonexistentRaffle)?; + ensure!(raffle.registration_open, Error::::RegistrationsClosed); + ensure!(raffle.owner == owner, Error::::OnlyRaffleOwnerCanDrawWinners); + + let mut registrations = Self::raffle_registrations(index); + let registrations_root = Self::merkle_root(®istrations); + ::Shuffle::shuffle(&mut registrations); + + let count = core::cmp::min(registrations.len(), raffle.winner_count as usize); + let winners = registrations[..count].to_vec(); + + OnGoingRaffles::::mutate(index, |r| r.as_mut().map(|r| r.registration_open = false)); + Winners::::insert(index, &winners); + + Self::deposit_event(Event::WinnersDrawn { index, winners, registrations_root }); + Ok(()) + } + + pub fn merkle_proof_for_registration( + index: RaffleIndex, + account: &T::AccountId, + ) -> Option>> { + let registrations = Self::raffle_registrations(index); + Self::merkle_proof(account, ®istrations) + } + + pub(crate) fn merkle_root(accounts: &[T::AccountId]) -> H256 { + merkle_root::(accounts.iter().map(Encode::encode)) + } + + pub(crate) fn merkle_proof( + account: &T::AccountId, + registrations: &[T::AccountId], + ) -> Option>> { + let leaf_index = Self::merkle_leaf_index_for_registration(account, registrations)?; + let p = + merkle_proof::(registrations.iter().map(Encode::encode), leaf_index); + Some(p.into()) + } + + pub(crate) fn merkle_leaf_index_for_registration( + account: &T::AccountId, + registrations: &[T::AccountId], + ) -> Option { + registrations.iter().position(|a| a == account) + } +} + +pub trait Shuffle { + fn shuffle(values: &mut [T]); +} diff --git a/app-libs/raffle/pallet-raffle/src/mock.rs b/app-libs/raffle/pallet-raffle/src/mock.rs new file mode 100644 index 0000000000..6758862f05 --- /dev/null +++ b/app-libs/raffle/pallet-raffle/src/mock.rs @@ -0,0 +1,141 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate as pallet_raffles; +use crate::Shuffle; +use frame_support::parameter_types; +use frame_system as system; +use sp_core::H256; +use sp_keyring::AccountKeyring; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, +}; + +pub type Signature = sp_runtime::MultiSignature; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; +pub type Address = sp_runtime::MultiAddress; + +pub type BlockNumber = u32; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = + generic::UncheckedExtrinsic; + +pub type SignedExtra = ( + frame_system::CheckSpecVersion, + frame_system::CheckTxVersion, + frame_system::CheckGenesis, + frame_system::CheckEra, + frame_system::CheckNonce, + frame_system::CheckWeight, +); + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Raffles: pallet_raffles + } +); + +parameter_types! { + pub const BlockHashCount: u32 = 250; +} + +impl pallet_raffles::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type Shuffle = MockShuffler; +} + +pub struct MockShuffler; + +impl Shuffle for MockShuffler { + /// Switch the first two values if there are at least two values. + fn shuffle(values: &mut [T]) { + if values.len() > 1 { + values.swap(0, 1); + } + } +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +pub type Balance = u64; + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxReserves = (); + type ReserveIdentifier = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +// This function basically just builds a genesis storage key/value store according to +// our desired mockup. RA from enclave compiled in debug mode is allowed +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = system::GenesisConfig::default().build_storage::().unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(AccountKeyring::Alice.to_account_id(), 1 << 60)], + } + .assimilate_storage(&mut t) + .unwrap(); + let mut ext: sp_io::TestExternalities = t.into(); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/app-libs/raffle/pallet-raffle/src/tests.rs b/app-libs/raffle/pallet-raffle/src/tests.rs new file mode 100644 index 0000000000..bec149d956 --- /dev/null +++ b/app-libs/raffle/pallet-raffle/src/tests.rs @@ -0,0 +1,338 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ +use crate::{mock::*, Error, Event as RaffleEvent, Raffle}; +use frame_support::{assert_err, assert_ok}; +use sp_keyring::AccountKeyring; + +mod add_raffle { + use super::*; + + #[test] + fn add_raffle_works() { + new_test_ext().execute_with(|| { + let winner_count = 1; + assert_ok!(Raffles::add_raffle( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + winner_count + )); + + let expected_raffle = Raffle { + owner: AccountKeyring::Alice.to_account_id(), + winner_count, + registration_open: true, + }; + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleAdded { + index: 0, + raffle: expected_raffle.clone(), + })); + + assert_eq!(Raffles::ongoing_raffles(0).unwrap(), expected_raffle) + }) + } +} + +mod register_for_raffle { + use super::*; + + #[test] + fn register_for_raffle_works() { + new_test_ext().execute_with(|| { + let winner_count = 1; + assert_ok!(Raffles::add_raffle( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + winner_count + )); + + let raffle_index = 0; + + // register bob for raffle and ensure he is signed up + assert_ok!(Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Bob.into()), + raffle_index + )); + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleRegistration { + who: AccountKeyring::Bob.to_account_id(), + index: raffle_index, + })); + assert_eq!(Raffles::raffle_registrations(0), vec![AccountKeyring::Bob.to_account_id()]); + + // register charlie for raffle and ensure charlie and bob is signed up + assert_ok!(Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Charlie.into()), + raffle_index + )); + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleRegistration { + who: AccountKeyring::Charlie.to_account_id(), + index: raffle_index, + })); + assert_eq!( + Raffles::raffle_registrations(0), + vec![AccountKeyring::Bob.to_account_id(), AccountKeyring::Charlie.to_account_id(),] + ); + }) + } + + #[test] + fn register_for_raffle_errs_for_nonexistent_raffle() { + new_test_ext().execute_with(|| { + let raffle_index = 0; + assert_err!( + Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Bob.into()), + raffle_index + ), + Error::::NonexistentRaffle + ); + }) + } +} + +mod draw_winners { + use super::*; + + #[test] + fn draw_winners_works() { + new_test_ext().execute_with(|| { + let winner_count = 2; + assert_ok!(Raffles::add_raffle( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + winner_count + )); + + let raffle_index = 0; + + // register bob for raffle and ensure he is signed up + assert_ok!(Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Bob.into()), + raffle_index + )); + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleRegistration { + who: AccountKeyring::Bob.to_account_id(), + index: raffle_index, + })); + assert_eq!(Raffles::raffle_registrations(0), vec![AccountKeyring::Bob.to_account_id()]); + + // register charlie for raffle and ensure charlie and bob is signed up + assert_ok!(Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Charlie.into()), + raffle_index + )); + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleRegistration { + who: AccountKeyring::Charlie.to_account_id(), + index: raffle_index, + })); + assert_eq!( + Raffles::raffle_registrations(0), + vec![AccountKeyring::Bob.to_account_id(), AccountKeyring::Charlie.to_account_id(),] + ); + + // register eve for raffle and ensure eve, charlie and bob is signed up + assert_ok!(Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Eve.into()), + raffle_index + )); + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleRegistration { + who: AccountKeyring::Eve.to_account_id(), + index: raffle_index, + })); + assert_eq!( + Raffles::raffle_registrations(0), + vec![ + AccountKeyring::Bob.to_account_id(), + AccountKeyring::Charlie.to_account_id(), + AccountKeyring::Eve.to_account_id() + ] + ); + + // draw winners and check if they are the expected ones given our shuffle source + assert_ok!(Raffles::draw_winners( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + raffle_index + )); + + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::WinnersDrawn { + index: raffle_index, + winners: vec![ + AccountKeyring::Charlie.to_account_id(), + AccountKeyring::Bob.to_account_id(), + ], + registrations_root: Raffles::merkle_root(&vec![ + AccountKeyring::Bob.to_account_id(), + AccountKeyring::Charlie.to_account_id(), + AccountKeyring::Eve.to_account_id(), + ]), + })); + + assert_eq!(Raffles::ongoing_raffles(0).unwrap().registration_open, false) + }) + } + + #[test] + fn register_for_raffle_works_if_less_registrations_than_winners() { + new_test_ext().execute_with(|| { + let winner_count = 2; + assert_ok!(Raffles::add_raffle( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + winner_count + )); + + let raffle_index = 0; + + // register bob for raffle and ensure he is signed up + assert_ok!(Raffles::register_for_raffle( + RuntimeOrigin::signed(AccountKeyring::Bob.into()), + raffle_index + )); + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::RaffleRegistration { + who: AccountKeyring::Bob.to_account_id(), + index: raffle_index, + })); + assert_eq!(Raffles::raffle_registrations(0), vec![AccountKeyring::Bob.to_account_id()]); + + // draw winners and check if they are the expected ones given our shuffle source + assert_ok!(Raffles::draw_winners( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + raffle_index + )); + + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::WinnersDrawn { + index: raffle_index, + winners: vec![AccountKeyring::Bob.to_account_id()], + registrations_root: Raffles::merkle_root( + &vec![AccountKeyring::Bob.to_account_id()], + ), + })); + + assert_eq!(Raffles::ongoing_raffles(0).unwrap().registration_open, false) + }) + } + + #[test] + fn draw_winners_errs_for_nonexistent_raffle() { + new_test_ext().execute_with(|| { + let raffle_index = 0; + + assert_err!( + Raffles::draw_winners( + RuntimeOrigin::signed(AccountKeyring::Bob.into()), + raffle_index + ), + Error::::NonexistentRaffle + ); + }) + } + + #[test] + fn ensure_only_raffle_owner_can_draw_winners() { + new_test_ext().execute_with(|| { + let winner_count = 2; + assert_ok!(Raffles::add_raffle( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + winner_count + )); + + let raffle_index = 0; + + assert_err!( + Raffles::draw_winners( + RuntimeOrigin::signed(AccountKeyring::Bob.into()), + raffle_index + ), + Error::::OnlyRaffleOwnerCanDrawWinners + ); + }) + } + + #[test] + fn registering_fails_after_raffle_registrations_are_closed() { + new_test_ext().execute_with(|| { + let winner_count = 2; + assert_ok!(Raffles::add_raffle( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + winner_count + )); + + let raffle_index = 0; + + // draw winners and check if they are the expected ones given our shuffle source + assert_ok!(Raffles::draw_winners( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + raffle_index + )); + + System::assert_last_event(RuntimeEvent::Raffles(RaffleEvent::WinnersDrawn { + index: raffle_index, + winners: vec![], + registrations_root: Raffles::merkle_root(&[]), + })); + + assert_eq!(Raffles::ongoing_raffles(0).unwrap().registration_open, false); + + assert_err!( + Raffles::draw_winners( + RuntimeOrigin::signed(AccountKeyring::Alice.into()), + raffle_index + ), + Error::::RegistrationsClosed + ); + }) + } +} + +#[test] +fn merkle_proof_works() { + use sp_runtime::traits::Keccak256; + + let registrations = vec![ + AccountKeyring::Bob.to_account_id(), + AccountKeyring::Charlie.to_account_id(), + AccountKeyring::Eve.to_account_id(), + ]; + + let root = Raffles::merkle_root(®istrations); + + let proof = Raffles::merkle_proof(&AccountKeyring::Bob.to_account_id(), ®istrations) + .expect("Bob is part of the set; qed"); + + assert!(itp_binary_merkle_tree::verify_proof::( + &root, + proof.proof.clone(), + proof + .number_of_leaves + .try_into() + .expect("Target Architecture needs usize > 32bits "), + proof.leaf_index.try_into().expect("Target Architecture needs usize > 32bits "), + &proof.leaf, + )) +} + +#[test] +fn mock_shuffle_works() { + use crate::{mock::MockShuffler, Shuffle}; + + let mut values = [1]; + MockShuffler::shuffle(&mut values); + assert_eq!(values, [1]); + + let mut values = [1, 2]; + MockShuffler::shuffle(&mut values); + assert_eq!(values, [2, 1]); + + let mut values = [1, 2, 3]; + MockShuffler::shuffle(&mut values); + assert_eq!(values, [2, 1, 3]); +} diff --git a/app-libs/raffle/pallet-raffle/src/weights.rs b/app-libs/raffle/pallet-raffle/src/weights.rs new file mode 100644 index 0000000000..5c41dadfbd --- /dev/null +++ b/app-libs/raffle/pallet-raffle/src/weights.rs @@ -0,0 +1,21 @@ +pub use frame_support::weights::{constants::RocksDbWeight, Weight}; + +/// Weight functions needed for pallet_parentchain. +pub trait WeightInfo { + fn add_raffle() -> Weight; + fn register_for_raffle() -> Weight; + fn draw_winners() -> Weight; +} + +/// Weights for pallet_parentchain using the Integritee parachain node and recommended hardware. +impl WeightInfo for () { + fn add_raffle() -> Weight { + Weight::from_parts(10_000, 0u64) + } + fn register_for_raffle() -> Weight { + Weight::from_parts(10_000, 0u64) + } + fn draw_winners() -> Weight { + Weight::from_parts(10_000, 0u64) + } +} diff --git a/app-libs/raffle/stf/Cargo.toml b/app-libs/raffle/stf/Cargo.toml new file mode 100644 index 0000000000..f3bdc8ffdc --- /dev/null +++ b/app-libs/raffle/stf/Cargo.toml @@ -0,0 +1,82 @@ +[package] +name = "ita-raffle-stf" +version = "0.9.0" +authors = ["Integritee AG "] +edition = "2021" + +[dependencies] +# crates.io +codec = { version = "3.0.0", default-features = false, features = ["derive"], package = "parity-scale-codec" } +log = { version = "0.4", default-features = false } +rlp = { version = "0.5", default-features = false } +sha3 = { version = "0.10", default-features = false } + +pallet-raffles = { path = "../pallet-raffle", default-features = false } + +# sgx deps +sgx_tstd = { branch = "master", features = ["untrusted_fs", "net", "backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } + +# local crates +ita-sgx-runtime = { default-features = false, path = "../../sgx-runtime" } +itp-hashing = { default-features = false, path = "../../../core-primitives/hashing" } +itp-node-api = { default-features = false, path = "../../../core-primitives/node-api" } +itp-node-api-metadata = { default-features = false, path = "../../../core-primitives/node-api/metadata" } +itp-sgx-externalities = { default-features = false, path = "../../../core-primitives/substrate-sgx/externalities" } +itp-sgx-runtime-primitives = { default-features = false, path = "../../../core-primitives/sgx-runtime-primitives" } +itp-stf-interface = { default-features = false, path = "../../../core-primitives/stf-interface" } +itp-stf-primitives = { default-features = false, path = "../../../core-primitives/stf-primitives" } +itp-storage = { default-features = false, path = "../../../core-primitives/storage" } +itp-types = { default-features = false, path = "../../../core-primitives/types" } +itp-utils = { default-features = false, path = "../../../core-primitives/utils" } +sp-io = { default-features = false, features = ["disable_oom", "disable_panic_handler", "disable_allocator"], path = "../../../core-primitives/substrate-sgx/sp-io" } + +# Substrate dependencies +frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +frame-system = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +pallet-balances = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +pallet-parentchain = { default-features = false, git = "https://github.com/integritee-network/pallets.git", branch = "sdk-v0.12.11-polkadot-v0.9.42" } +pallet-sudo = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-runtime = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } +sp-std = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } + + +[dev-dependencies] +sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } + +[features] +default = ["std"] +sgx = [ + "ita-sgx-runtime/sgx", + "itp-node-api/sgx", + "itp-sgx-externalities/sgx", + "sgx_tstd", + "sp-io/sgx", +] +std = [ + # crates.io + "codec/std", + "log/std", + "rlp/std", + # local + "ita-sgx-runtime/std", + "itp-hashing/std", + "itp-sgx-externalities/std", + "itp-stf-interface/std", + "itp-storage/std", + "itp-types/std", + "itp-node-api/std", + "itp-node-api-metadata/std", + "pallet-raffles/std", + # substrate + "sp-core/std", + "pallet-balances/std", + "pallet-sudo/std", + "frame-system/std", + "frame-support/std", + "sp-runtime/std", + # scs/integritee + "pallet-parentchain/std", + "sp-io/std", +] +test = [] diff --git a/app-libs/raffle/stf/src/getter.rs b/app-libs/raffle/stf/src/getter.rs new file mode 100644 index 0000000000..4f382ec578 --- /dev/null +++ b/app-libs/raffle/stf/src/getter.rs @@ -0,0 +1,73 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::RaffleIndex; +use codec::{Decode, Encode}; +use ita_sgx_runtime::Runtime; +use itp_stf_interface::ExecuteGetter; +use itp_stf_primitives::types::AccountId; +use sp_std::vec::Vec; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum RafflePublicGetter { + all_ongoing_raffles, +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum RaffleTrustedGetter { + merkle_proof { origin: AccountId, raffle_index: RaffleIndex }, +} + +impl RaffleTrustedGetter { + pub fn sender_account(&self) -> &AccountId { + match self { + Self::merkle_proof { origin, .. } => origin, + } + } +} + +impl ExecuteGetter for RaffleTrustedGetter { + fn execute(self) -> Option> { + match self { + Self::merkle_proof { origin, raffle_index } => + pallet_raffles::Pallet::::merkle_proof_for_registration( + raffle_index, + &origin, + ) + .map(|proof| proof.encode()), + } + } + + fn get_storage_hashes_to_update(self) -> Vec> { + Vec::new() + } +} + +impl ExecuteGetter for RafflePublicGetter { + fn execute(self) -> Option> { + match self { + Self::all_ongoing_raffles => + Some(pallet_raffles::Pallet::::all_ongoing_raffles().encode()), + } + } + + fn get_storage_hashes_to_update(self) -> Vec> { + Vec::new() + } +} diff --git a/app-libs/raffle/stf/src/lib.rs b/app-libs/raffle/stf/src/lib.rs new file mode 100644 index 0000000000..3712520798 --- /dev/null +++ b/app-libs/raffle/stf/src/lib.rs @@ -0,0 +1,13 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(not(feature = "std"))] +extern crate alloc; + +mod getter; +mod trusted_call; + +pub use getter::{RafflePublicGetter, RaffleTrustedGetter}; +pub use pallet_raffles::{ + self, merkle_tree, RaffleCount, RaffleIndex, RaffleMetadata, WinnerCount, +}; +pub use trusted_call::RaffleTrustedCall; diff --git a/app-libs/raffle/stf/src/trusted_call.rs b/app-libs/raffle/stf/src/trusted_call.rs new file mode 100644 index 0000000000..4490daab1a --- /dev/null +++ b/app-libs/raffle/stf/src/trusted_call.rs @@ -0,0 +1,193 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use codec::{Decode, Encode}; +use frame_support::traits::UnfilteredDispatchable; +use ita_sgx_runtime::Runtime; +pub use ita_sgx_runtime::{Balance, Index}; +use itp_node_api::metadata::{provider::AccessNodeMetadata, NodeMetadataTrait}; +use itp_node_api_metadata::pallet_enclave_bridge::EnclaveBridgeCallIndexes; +use itp_stf_interface::ExecuteCall; +use itp_stf_primitives::{error::StfError, types::AccountId}; +use itp_types::{parentchain::ParentchainCall, OpaqueCall}; +use itp_utils::stringify::account_id_to_string; +use log::*; +use sp_std::{sync::Arc, vec::Vec}; + +#[cfg(not(feature = "std"))] +use alloc::{format, string::ToString}; + +pub use pallet_raffles::{RaffleCount, RaffleIndex, WinnerCount}; + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +#[allow(non_camel_case_types)] +pub enum RaffleTrustedCall { + addRaffle { origin: AccountId, winner_count: WinnerCount }, + registerForRaffle { origin: AccountId, raffle_index: RaffleIndex }, + drawWinners { origin: AccountId, raffle_index: RaffleIndex }, +} + +impl RaffleTrustedCall { + pub fn sender_account(&self) -> &AccountId { + match self { + Self::addRaffle { origin, .. } => origin, + Self::drawWinners { origin, .. } => origin, + Self::registerForRaffle { origin, .. } => origin, + } + } +} + +impl ExecuteCall for RaffleTrustedCall +where + NodeMetadataRepository: AccessNodeMetadata, + NodeMetadataRepository::MetadataType: NodeMetadataTrait, +{ + type Error = StfError; + + fn execute( + self, + calls: &mut Vec, + node_metadata_repo: Arc, + ) -> Result<(), Self::Error> { + match self { + Self::addRaffle { origin, winner_count } => { + debug!("createRaffle called by {}", account_id_to_string(&origin),); + let origin = ita_sgx_runtime::RuntimeOrigin::signed(origin); + + pallet_raffles::Call::::add_raffle { winner_count } + .dispatch_bypass_filter(origin) + .map_err(|e| { + Self::Error::Dispatch(format!("Create Raffle error: {:?}", e.error)) + })?; + + // call was successfull so we should find our raffle now on the last index. + let index = pallet_raffles::Pallet::::raffle_count(); + let raffle = pallet_raffles::Pallet::::ongoing_raffles(index - 1) + .ok_or_else(|| { + // This should never happen if the pallet works correctly. + Self::Error::Dispatch( + "AddRaffle: Could not find expected raffle, critical pallet bug." + .to_string(), + ) + })?; + + calls.push(ParentchainCall::Integritee(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.publish_hash_call_indexes()) + .map_err(|_| Self::Error::InvalidMetadata)? + .map_err(|_| Self::Error::InvalidMetadata)?, + itp_types::H256::default(), // don't bother with the call hash for now. + Vec::::new(), + format!("Raffle Added: index: {}, {:?}", index, raffle), + )))); + + Ok::<(), Self::Error>(()) + }, + Self::registerForRaffle { origin, raffle_index } => { + debug!("registerForRaffle called by {}", account_id_to_string(&origin),); + let origin = ita_sgx_runtime::RuntimeOrigin::signed(origin); + + pallet_raffles::Call::::register_for_raffle { index: raffle_index } + .dispatch_bypass_filter(origin) + .map_err(|e| { + Self::Error::Dispatch(format!("Create Raffle error: {:?}", e.error)) + })?; + + calls.push(ParentchainCall::Integritee(OpaqueCall::from_tuple(&( + node_metadata_repo + .get_from_metadata(|m| m.publish_hash_call_indexes()) + .map_err(|_| Self::Error::InvalidMetadata)? + .map_err(|_| Self::Error::InvalidMetadata)?, + itp_types::H256::default(), // don't bother with the call hash for now. + Vec::::new(), + format!("Someone registered for raffle with index: {}", raffle_index), + )))); + + Ok::<(), Self::Error>(()) + }, + Self::drawWinners { origin, raffle_index } => { + debug!("drawWinners called by {}", account_id_to_string(&origin),); + let origin = ita_sgx_runtime::RuntimeOrigin::signed(origin); + + pallet_raffles::Call::::draw_winners { index: raffle_index } + .dispatch_bypass_filter(origin) + .map_err(|e| { + Self::Error::Dispatch(format!("Draw winners error: {:?}", e.error)) + })?; + + Runtime::read_events() + .last() + .map(|event_record| -> Result<(), Self::Error> { + match &event_record.event { + ita_sgx_runtime::RuntimeEvent::Raffles( + pallet_raffles::Event::WinnersDrawn { + index, + winners, + registrations_root, + }, + ) => { + let publish_hash_indexes = node_metadata_repo + .get_from_metadata(|m| m.publish_hash_call_indexes()) + .map_err(|_| Self::Error::InvalidMetadata)? + .map_err(|_| Self::Error::InvalidMetadata)?; + + calls.push(ParentchainCall::Integritee(OpaqueCall::from_tuple(&( + publish_hash_indexes, + itp_types::H256::default(), // don't bother with the call hash for now. + Vec::::new(), + format!("Raffle Winners Drawn: index: {}", index), + )))); + + calls.push(ParentchainCall::Integritee(OpaqueCall::from_tuple(&( + publish_hash_indexes, + registrations_root, + Vec::::new(), + "Registrations Root".to_string(), + )))); + + for w in winners.iter().map(account_id_to_string) { + calls.push(ParentchainCall::Integritee( + OpaqueCall::from_tuple(&( + publish_hash_indexes, + itp_types::H256::default(), // don't bother with the call hash for now. + Vec::::new(), + format!("Raffle Winner 1: {:?}", w), + )), + )); + } + }, + _ => + return Err(Self::Error::Dispatch( + "AddRaffle: Could not find expected winners drawn event" + .to_string(), + )), + } + Ok::<(), Self::Error>(()) + }) + .transpose()? + .ok_or_else(|| { + Self::Error::Dispatch("Could not find expected event.".to_string()) + }) + }, + } + } + + fn get_storage_hashes_to_update(self) -> Vec> { + debug!("No storage updates needed..."); + Vec::new() + } +} diff --git a/app-libs/sgx-runtime/Cargo.toml b/app-libs/sgx-runtime/Cargo.toml index d42d2df59e..de62ac2c8e 100644 --- a/app-libs/sgx-runtime/Cargo.toml +++ b/app-libs/sgx-runtime/Cargo.toml @@ -13,6 +13,7 @@ scale-info = { version = "2.10.0", default-features = false, features = ["derive # local dependencies itp-sgx-runtime-primitives = { path = "../../core-primitives/sgx-runtime-primitives", default-features = false } +pallet-raffles = { default-features = false, path = "../raffle/pallet-raffle" } # Substrate dependencies frame-executive = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } @@ -32,11 +33,16 @@ sp-version = { default-features = false, git = "https://github.com/paritytech/su pallet-evm = { default-features = false, optional = true, git = "https://github.com/integritee-network/frontier.git", branch = "bar/polkadot-v0.9.42" } pallet-parentchain = { default-features = false, git = "https://github.com/integritee-network/pallets.git", branch = "sdk-v0.12.11-polkadot-v0.9.42" } +# Raffle Demo +sgx_rand = { optional = true, version = "1.1.6", git = "https://github.com/apache/incubator-teaclave-sgx-sdk", branch = "master" } + [features] default = ["std"] # Compile the sgx-runtime with evm support. evm = ["pallet-evm"] - +sgx = [ + "sgx_rand", +] std = [ "codec/std", "scale-info/std", @@ -50,6 +56,7 @@ std = [ "pallet-timestamp/std", "pallet-transaction-payment/std", "pallet-parentchain/std", + "pallet-raffles/std", "sp-api/std", "sp-core/std", "sp-runtime/std", diff --git a/app-libs/sgx-runtime/src/lib.rs b/app-libs/sgx-runtime/src/lib.rs index 93b1026d1a..08aa5ee112 100644 --- a/app-libs/sgx-runtime/src/lib.rs +++ b/app-libs/sgx-runtime/src/lib.rs @@ -281,6 +281,41 @@ impl pallet_parentchain::Config for Runtime { type Moment = Moment; } +impl pallet_raffles::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + + #[cfg(feature = "sgx")] + type Shuffle = SgxShuffle; + + #[cfg(not(feature = "sgx"))] + type Shuffle = MockShuffler; +} + +pub struct MockShuffler; + +impl pallet_raffles::Shuffle for MockShuffler { + /// Switch the first two values if there are at least two values. + fn shuffle(values: &mut [T]) { + if values.len() > 1 { + values.swap(0, 1); + } + } +} + +pub struct SgxShuffle; + +#[cfg(feature = "sgx")] +impl pallet_raffles::Shuffle for SgxShuffle { + /// Use SGX's true RNG to shuffle the values. + fn shuffle(values: &mut [T]) { + use sgx_rand::Rng; + let mut rng = + sgx_rand::SgxRng::new().expect("Can't fail, internal new returns Ok directly; qed"); + rng.shuffle(values); + } +} + // The plain sgx-runtime without the `evm-pallet` #[cfg(not(feature = "evm"))] construct_runtime!( @@ -298,6 +333,8 @@ construct_runtime!( ParentchainIntegritee: pallet_parentchain::::{Pallet, Call, Event} = 10, ParentchainTargetA: pallet_parentchain::::{Pallet, Call, Event} = 11, ParentchainTargetB: pallet_parentchain::::{Pallet, Call, Event} = 12, + + Raffles: pallet_raffles = 30, } ); @@ -323,6 +360,8 @@ construct_runtime!( ParentchainTargetB: pallet_parentchain::::{Pallet, Call, Event} = 12, Evm: pallet_evm::{Pallet, Call, Storage, Config, Event} = 20, + + Raffles: pallet_raffles = 30, } ); @@ -356,3 +395,22 @@ impl_runtime_apis! { } } + +// Todo: the below helper should be implemented generically on `T: frame_system::Config` +// in a core lib. +pub type RuntimeEventOf = ::RuntimeEvent; +pub type HashOf = ::Hash; +use frame_system::EventRecord; +impl Runtime { + pub fn read_events() -> Vec, HashOf>> { + frame_system::Pallet::::read_events_no_consensus() + .map(|e| *e) + .collect() + } + + // Todo: derive `Clone` for event record in `no_std`. + // Supply upstream PR. + // pub fn last_event() -> Option, HashOf>> { + // Runtime::read_events().last().cloned() + // } +} diff --git a/app-libs/stf/Cargo.toml b/app-libs/stf/Cargo.toml index 6a76e9b352..3ba160a336 100644 --- a/app-libs/stf/Cargo.toml +++ b/app-libs/stf/Cargo.toml @@ -11,6 +11,8 @@ log = { version = "0.4", default-features = false } rlp = { version = "0.5", default-features = false } sha3 = { version = "0.10", default-features = false } +ita-raffle-stf = { default-features = false, path = "../raffle/stf" } + # sgx deps sgx_tstd = { branch = "master", features = ["untrusted_fs", "net", "backtrace"], git = "https://github.com/apache/teaclave-sgx-sdk.git", optional = true } @@ -46,10 +48,12 @@ sp-keyring = { git = "https://github.com/paritytech/substrate.git", branch = "po default = ["std"] evm = ["ita-sgx-runtime/evm"] sgx = [ - "sgx_tstd", + "ita-raffle-stf/sgx", + "ita-sgx-runtime/sgx", + "itp-node-api/sgx", "itp-sgx-externalities/sgx", + "sgx_tstd", "sp-io/sgx", - "itp-node-api/sgx", ] std = [ # crates.io @@ -57,6 +61,7 @@ std = [ "log/std", "rlp/std", # local + "ita-raffle-stf/std", "ita-sgx-runtime/std", "itp-hashing/std", "itp-sgx-externalities/std", diff --git a/app-libs/stf/src/getter.rs b/app-libs/stf/src/getter.rs index 77fe561e05..9fe8e037a8 100644 --- a/app-libs/stf/src/getter.rs +++ b/app-libs/stf/src/getter.rs @@ -28,6 +28,8 @@ use sp_runtime::traits::Verify; use sp_std::vec; use std::prelude::v1::*; +pub use ita_raffle_stf::{RafflePublicGetter, RaffleTrustedGetter}; + #[cfg(feature = "evm")] use ita_sgx_runtime::{AddressMapping, HashedAddressMapping}; @@ -94,6 +96,7 @@ impl PoolTransactionValidation for Getter { #[allow(non_camel_case_types)] pub enum PublicGetter { some_value, + raffle(RafflePublicGetter), } #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] @@ -102,6 +105,7 @@ pub enum TrustedGetter { free_balance(AccountId), reserved_balance(AccountId), nonce(AccountId), + raffle(RaffleTrustedGetter), #[cfg(feature = "evm")] evm_nonce(AccountId), #[cfg(feature = "evm")] @@ -116,6 +120,7 @@ impl TrustedGetter { TrustedGetter::free_balance(sender_account) => sender_account, TrustedGetter::reserved_balance(sender_account) => sender_account, TrustedGetter::nonce(sender_account) => sender_account, + TrustedGetter::raffle(getter) => getter.sender_account(), #[cfg(feature = "evm")] TrustedGetter::evm_nonce(sender_account) => sender_account, #[cfg(feature = "evm")] @@ -187,6 +192,7 @@ impl ExecuteGetter for TrustedGetterSigned { debug!("Account nonce is {}", nonce); Some(nonce.encode()) }, + TrustedGetter::raffle(getter) => getter.execute(), #[cfg(feature = "evm")] TrustedGetter::evm_nonce(who) => { let evm_account = get_evm_account(&who); @@ -228,6 +234,7 @@ impl ExecuteGetter for PublicGetter { fn execute(self) -> Option> { match self { PublicGetter::some_value => Some(42u32.encode()), + PublicGetter::raffle(getter) => getter.execute(), } } diff --git a/app-libs/stf/src/lib.rs b/app-libs/stf/src/lib.rs index 4b53d32148..5a5c26bbbc 100644 --- a/app-libs/stf/src/lib.rs +++ b/app-libs/stf/src/lib.rs @@ -43,6 +43,12 @@ pub mod stf_sgx_tests; pub mod test_genesis; pub mod trusted_call; +// raflle stuff +pub use ita_raffle_stf::{ + merkle_tree, RaffleCount, RaffleIndex, RaffleMetadata, RafflePublicGetter, RaffleTrustedCall, + RaffleTrustedGetter, WinnerCount, +}; + pub(crate) const ENCLAVE_ACCOUNT_KEY: &str = "Enclave_Account_Key"; // fixme: this if a temporary hack only diff --git a/app-libs/stf/src/trusted_call.rs b/app-libs/stf/src/trusted_call.rs index 201a098110..523833c3e4 100644 --- a/app-libs/stf/src/trusted_call.rs +++ b/app-libs/stf/src/trusted_call.rs @@ -61,6 +61,9 @@ use sp_io::hashing::blake2_256; use sp_runtime::{traits::Verify, MultiAddress, MultiSignature}; use std::{format, prelude::v1::*, sync::Arc}; +// raflle stuff +pub use ita_raffle_stf::RaffleTrustedCall; + #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] #[allow(non_camel_case_types)] pub enum TrustedCall { @@ -70,6 +73,7 @@ pub enum TrustedCall { balance_unshield(AccountId, AccountId, Balance, ShardIdentifier), // (AccountIncognito, BeneficiaryPublicAccount, Amount, Shard) balance_shield(AccountId, AccountId, Balance, ParentchainId), // (Root, AccountIncognito, Amount, origin parentchain) timestamp_set(AccountId, Moment, ParentchainId), // (Root, now) + raffle(RaffleTrustedCall), #[cfg(feature = "evm")] evm_withdraw(AccountId, H160, Balance), // (Origin, Address EVM Account, Value) // (Origin, Source, Target, Input, Value, Gas limit, Max fee per gas, Max priority fee per gas, Nonce, Access list) @@ -124,6 +128,7 @@ impl TrustedCall { Self::balance_unshield(sender_account, ..) => sender_account, Self::balance_shield(sender_account, ..) => sender_account, Self::timestamp_set(sender_account, ..) => sender_account, + Self::raffle(call) => call.sender_account(), #[cfg(feature = "evm")] Self::evm_withdraw(sender_account, ..) => sender_account, #[cfg(feature = "evm")] @@ -441,7 +446,7 @@ where }; Ok(()) }, - + TrustedCall::raffle(call) => call.execute(calls, node_metadata_repo), #[cfg(feature = "evm")] TrustedCall::evm_withdraw(from, address, value) => { debug!("evm_withdraw({}, {}, {})", account_id_to_string(&from), address, value); @@ -572,7 +577,6 @@ where TrustedCall::balance_unshield(..) => debug!("No storage updates needed..."), TrustedCall::balance_shield(..) => debug!("No storage updates needed..."), TrustedCall::timestamp_set(..) => debug!("No storage updates needed..."), - #[cfg(feature = "evm")] _ => debug!("No storage updates needed..."), }; key_hashes diff --git a/cli/RaffleReadme.md b/cli/RaffleReadme.md new file mode 100644 index 0000000000..8396efd8f4 --- /dev/null +++ b/cli/RaffleReadme.md @@ -0,0 +1,75 @@ +# Raffle Demo: + +## Preliminary + +Run the local setup with one worker + +```bash +./local-setup/launch.py ./local-setup/config/one-worker.json +``` + +# Setup + +```bash +NPORT=9944 +NODEURL=ws://127.0.0.1 + +WORKER1PORT=2000 +WORKER1URL=wss://127.0.0.1 + +CLIENT_BIN=../bin/integritee-cli + +RAFFLE_INDEX=0 + +CLIENT="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" + +# Query workers +$CLIENT list-workers +``` + +Assign mrenclave from above list-workers command to env var. + +```bash +MRENCLAVE= +``` + +# Create Raffle + +Create a raffle with Alice + +```bash +WINNERS_COUNT=2 +$CLIENT trusted --mrenclave ${MRENCLAVE} --direct add-raffle //Alice ${WINNERS_COUNT} +``` + +# Have some users register for the raffle + +```bash +USER_COUNT=50 + +for ((i=1; i<=USER_COUNT; i++)); do + # Register users in the background + $CLIENT trusted --mrenclave "$MRENCLAVE" --direct register-for-raffle "//RaffleUser${i}" "$RAFFLE_INDEX" & +done + +# await background processes + +wait +echo "Registered ${USER_COUNT} users" +``` + +# Draw winners + +Only the Raffle creator, //Alice, may draw the winners + +```bash +$CLIENT trusted --mrenclave ${MRENCLAVE} --direct draw-winners //Alice ${RAFFLE_INDEX} +``` + +# Get and verify the registration + +Get the merkle proof of a raffle user and verify it on chain. + +```bash +$CLIENT trusted --mrenclave ${MRENCLAVE} --direct get-and-verify-registration-proof //RaffleUser10 ${RAFFLE_INDEX} +``` \ No newline at end of file diff --git a/cli/demo_raffle.sh b/cli/demo_raffle.sh new file mode 100755 index 0000000000..253415bfe9 --- /dev/null +++ b/cli/demo_raffle.sh @@ -0,0 +1,120 @@ +#!/bin/bash +set -euo pipefail + +# Creates a merkle-root of a set of orders and verifies the proof afterwards. +# +# Note this script is the basis for a full fledget demo of the energy market. +# Things that are missing: +# * Perform the pay as bid operation +# * Check the merkle root hash on chain +# +# +# setup: +# run all on localhost: +# integritee-node purge-chain --dev +# integritee-node --tmp --dev -lruntime=debug +# rm light_client_db.bin +# export RUST_LOG=integritee_service=info,ita_stf=debug +# integritee-service init_shard +# integritee-service run +# +# then run this script + +# usage: +# demo_energy_market.sh -p -P -t -O + +while getopts ":p:A:P:u:V:C:I:O:T:" opt; do + case $opt in + p) + NPORT=$OPTARG + ;; + A) + WORKER1PORT=$OPTARG + ;; + P) + WORKER1PORT=$OPTARG + ;; + u) + NODEURL=$OPTARG + ;; + V) + WORKER1URL=$OPTARG + ;; + C) + CLIENT_BIN=$OPTARG + ;; + I) + ACTOR_ID=$OPTARG + ;; + O) + ORDERS_FILE=$OPTARG + ;; + T) + TIMESTAMP=$OPTARG + ;; + *) + echo "Invalid Argument Supplied" + exit 1 + ;; + esac +done + +# Using default port if none given as arguments. +NPORT=${NPORT:-9944} +NODEURL=${NODEURL:-"ws://127.0.0.1"} + +WORKER1PORT=${WORKER1PORT:-2000} +WORKER1URL=${WORKER1URL:-"wss://127.0.0.1"} + +CLIENT_BIN=${CLIENT_BIN:-"./../bin/integritee-cli"} + +RAFFLE_INDEX=0 +WINNER_COUNT=2 + +echo "Using client binary ${CLIENT_BIN}" +echo "Using node uri ${NODEURL}:${NPORT}" +echo "Using trusted-worker uri ${WORKER1URL}:${WORKER1PORT}" +echo "Using raffle index ${RAFFLE_INDEX}" +echo "" + +echo "* Query the first registered raffle TEE onchain" +CLIENT="${CLIENT_BIN} -p ${NPORT} -P ${WORKER1PORT} -u ${NODEURL} -U ${WORKER1URL}" +read -r MRENCLAVE <<< "$($CLIENT list-workers | awk '/ MRENCLAVE: / { print $2; exit }')" + +# Create Raffle +echo "* Alice creates a raffle" +RESULT=`$CLIENT trusted --mrenclave ${MRENCLAVE} --direct add-raffle //Alice ${WINNER_COUNT}` +echo "Result: ${RESULT}" + +echo "* All ongoing raffles" +RESULT=`$CLIENT trusted --mrenclave ${MRENCLAVE} --direct get-all-raffles` +echo "Result: ${RESULT}" + +# Have some users register for the raffle +USER_COUNT=50 +echo "* Registering ${USER_COUNT} users for the for raffle number :${RAFFLE_INDEX}..." + +for ((i=1; i<=USER_COUNT; i++)); do + # Register 200 users in the background + RESULT=$($CLIENT trusted --mrenclave "$MRENCLAVE" --direct register-for-raffle "//RaffleUser${i}" "$RAFFLE_INDEX") & +done + +# await background processes +wait +echo "Registered ${USER_COUNT} users" + +# ensure the next parentchain block is created so that we get a separate block for the draw winners event +sleep 12 + +# Draw winners +echo "* Draw the Winners" +RESULT=`$CLIENT trusted --mrenclave ${MRENCLAVE} --direct draw-winners //Alice ${RAFFLE_INDEX}` +echo "Result: ${RESULT}" + +# Get and verify the registration +echo "* Get and verify the registration proofs" + +echo "* Verify the a users registration" +RESULT=`$CLIENT trusted --mrenclave ${MRENCLAVE} --direct get-and-verify-registration-proof //RaffleUser10 ${RAFFLE_INDEX}` +echo "Result: ${RESULT}" +echo "" diff --git a/cli/src/lib.rs b/cli/src/lib.rs index c456618c57..3a1c114ce5 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -36,6 +36,7 @@ mod command_utils; mod evm; #[cfg(feature = "teeracle")] mod oracle; +mod raffle; mod trusted_base_cli; mod trusted_cli; mod trusted_command_utils; @@ -105,6 +106,9 @@ pub enum CliResultOk { H160 { hash: H160, }, + String { + string: String, + }, // TODO should ideally be removed; or at least drastically less used // We WANT all commands exposed by the cli to return something useful for the caller(ie instead of printing) None, diff --git a/cli/src/raffle/mod.rs b/cli/src/raffle/mod.rs new file mode 100644 index 0000000000..433ef0b907 --- /dev/null +++ b/cli/src/raffle/mod.rs @@ -0,0 +1,38 @@ +use crate::{trusted_cli::TrustedCli, Cli, CliResult}; +use trusted_commands::{ + AddRaffleCmd, DrawWinnersCmd, GetAllRafflesCmd, GetAndVerifyRegistrationProof, + RegisterForRaffleCmd, +}; + +mod trusted_commands; + +/// affle subcommands for the CLI. +#[derive(Debug, clap::Subcommand)] +pub enum RaffleTrustedCommand { + /// Add a new raffle + AddRaffle(AddRaffleCmd), + + /// Register for a raffle + RegisterForRaffle(RegisterForRaffleCmd), + + /// Draw winners of a raffle + DrawWinners(DrawWinnersCmd), + + /// Get all ongoing raffles + GetAllRaffles(GetAllRafflesCmd), + + /// Get and verify the proof of raffle registration + GetAndVerifyRegistrationProof(GetAndVerifyRegistrationProof), +} + +impl RaffleTrustedCommand { + pub fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + match self { + Self::AddRaffle(cmd) => cmd.run(cli, trusted_args), + Self::RegisterForRaffle(cmd) => cmd.run(cli, trusted_args), + Self::DrawWinners(cmd) => cmd.run(cli, trusted_args), + Self::GetAllRaffles(cmd) => cmd.run(cli, trusted_args), + Self::GetAndVerifyRegistrationProof(cmd) => cmd.run(cli, trusted_args), + } + } +} diff --git a/cli/src/raffle/trusted_commands/add_raffle.rs b/cli/src/raffle/trusted_commands/add_raffle.rs new file mode 100644 index 0000000000..963db33feb --- /dev/null +++ b/cli/src/raffle/trusted_commands/add_raffle.rs @@ -0,0 +1,63 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_cli::TrustedCli, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_operation::perform_trusted_operation, + Cli, CliResult, CliResultOk, +}; +use ita_stf::{Index, RaffleTrustedCall, TrustedCall, WinnerCount}; +use itp_stf_primitives::{ + traits::TrustedCallSigning, + types::{KeyPair, TrustedOperation}, +}; +use itp_types::AccountId; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Debug, Parser)] +pub struct AddRaffleCmd { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Winner count of the raffle + winner_count: WinnerCount, +} + +impl AddRaffleCmd { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + let sender = get_pair_from_str(trusted_args, &self.from); + let sender_acc: AccountId = sender.public().into(); + + info!("senders ss58 is {}", sender.public().to_ss58check()); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(sender, cli, trusted_args); + + let add_raffle_call = + RaffleTrustedCall::addRaffle { origin: sender_acc, winner_count: self.winner_count }; + + let function_call = TrustedCall::raffle(add_raffle_call) + .sign(&KeyPair::Sr25519(Box::new(sender)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + Ok(perform_trusted_operation::<()>(cli, trusted_args, &function_call) + .map(|_| CliResultOk::None)?) + } +} diff --git a/cli/src/raffle/trusted_commands/draw_winners.rs b/cli/src/raffle/trusted_commands/draw_winners.rs new file mode 100644 index 0000000000..19f2eb4284 --- /dev/null +++ b/cli/src/raffle/trusted_commands/draw_winners.rs @@ -0,0 +1,63 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_cli::TrustedCli, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_operation::perform_trusted_operation, + Cli, CliResult, CliResultOk, +}; +use ita_stf::{Index, RaffleIndex, RaffleTrustedCall, TrustedCall}; +use itp_stf_primitives::{ + traits::TrustedCallSigning, + types::{KeyPair, TrustedOperation}, +}; +use itp_types::AccountId; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Debug, Parser)] +pub struct DrawWinnersCmd { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Raffle index of the raffle to be evaluated + raffle_index: RaffleIndex, +} + +impl DrawWinnersCmd { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + let sender = get_pair_from_str(trusted_args, &self.from); + let sender_acc: AccountId = sender.public().into(); + + info!("senders ss58 is {}", sender.public().to_ss58check()); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(sender, cli, trusted_args); + + let add_raffle_call = + RaffleTrustedCall::drawWinners { origin: sender_acc, raffle_index: self.raffle_index }; + + let function_call = TrustedCall::raffle(add_raffle_call) + .sign(&KeyPair::Sr25519(Box::new(sender)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + Ok(perform_trusted_operation::<()>(cli, trusted_args, &function_call) + .map(|_| CliResultOk::None)?) + } +} diff --git a/cli/src/raffle/trusted_commands/get_all_raffles.rs b/cli/src/raffle/trusted_commands/get_all_raffles.rs new file mode 100644 index 0000000000..fdd4f7bfd0 --- /dev/null +++ b/cli/src/raffle/trusted_commands/get_all_raffles.rs @@ -0,0 +1,46 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + trusted_cli::TrustedCli, trusted_operation::perform_trusted_operation, Cli, CliResult, + CliResultOk, +}; +use ita_stf::{Getter, PublicGetter, RaffleMetadata, RafflePublicGetter}; +use itp_types::AccountId; +use log::*; +use std::vec::Vec; + +#[derive(Debug, Parser)] +pub struct GetAllRafflesCmd; + +impl GetAllRafflesCmd { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + info!("Getting all raffles"); + + let function_call = + Getter::public(PublicGetter::raffle(RafflePublicGetter::all_ongoing_raffles)); + + let res = perform_trusted_operation::>>( + cli, + trusted_args, + &function_call.into(), + )?; + + println!("{:?}", res); + Ok(CliResultOk::String { string: format!("{:?}", res) }) + } +} diff --git a/cli/src/raffle/trusted_commands/get_and_verify_registration_proof.rs b/cli/src/raffle/trusted_commands/get_and_verify_registration_proof.rs new file mode 100644 index 0000000000..a12a1c8d85 --- /dev/null +++ b/cli/src/raffle/trusted_commands/get_and_verify_registration_proof.rs @@ -0,0 +1,76 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + trusted_cli::TrustedCli, trusted_command_utils::get_pair_from_str, + trusted_operation::perform_trusted_operation, Cli, CliResult, CliResultOk, +}; +use ita_stf::{ + merkle_tree::{verify_proof, Keccak256, MerkleProofWithCodec}, + Getter, RaffleIndex, RaffleTrustedGetter, TrustedGetter, +}; +use itp_types::AccountId; +use itp_utils::stringify::account_id_to_string; +use sp_core::{Pair, H256}; + +#[derive(Debug, Parser)] +pub struct GetAndVerifyRegistrationProof { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// Raffle index to get the proof for + raffle_index: RaffleIndex, +} + +impl GetAndVerifyRegistrationProof { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + let sender = get_pair_from_str(trusted_args, &self.from); + let sender_acc: AccountId = sender.public().into(); + println!("senders account hex is {}", account_id_to_string(&sender_acc)); + + let get_registration_proof = RaffleTrustedGetter::merkle_proof { + origin: sender_acc, + raffle_index: self.raffle_index, + }; + + let getter = + Getter::trusted(TrustedGetter::raffle(get_registration_proof).sign(&sender.into())); + + let proof = perform_trusted_operation::>>( + cli, + trusted_args, + &getter.into(), + )?; + + println!("{:?}", proof); + + let is_valid = verify_proof::( + &proof.root, + proof.proof.clone(), + proof + .number_of_leaves + .try_into() + .expect("Target Architecture needs usize > 32bits "), + proof.leaf_index.try_into().expect("Target Architecture needs usize > 32bits "), + &proof.leaf, + ); + + println!("Proof is valid: {:?}", is_valid); + + Ok(CliResultOk::String { string: format!("Merkle proof is valid: {}", is_valid) }) + } +} diff --git a/cli/src/raffle/trusted_commands/mod.rs b/cli/src/raffle/trusted_commands/mod.rs new file mode 100644 index 0000000000..d2f9edbe36 --- /dev/null +++ b/cli/src/raffle/trusted_commands/mod.rs @@ -0,0 +1,11 @@ +mod add_raffle; +mod draw_winners; +mod get_all_raffles; +mod get_and_verify_registration_proof; +mod register_for_raffle; + +pub use add_raffle::AddRaffleCmd; +pub use draw_winners::DrawWinnersCmd; +pub use get_all_raffles::GetAllRafflesCmd; +pub use get_and_verify_registration_proof::GetAndVerifyRegistrationProof; +pub use register_for_raffle::RegisterForRaffleCmd; diff --git a/cli/src/raffle/trusted_commands/register_for_raffle.rs b/cli/src/raffle/trusted_commands/register_for_raffle.rs new file mode 100644 index 0000000000..e53e68b896 --- /dev/null +++ b/cli/src/raffle/trusted_commands/register_for_raffle.rs @@ -0,0 +1,64 @@ +/* + Copyright 2021 Integritee AG and Supercomputing Systems AG + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +*/ + +use crate::{ + get_layer_two_nonce, + trusted_cli::TrustedCli, + trusted_command_utils::{get_identifiers, get_pair_from_str}, + trusted_operation::perform_trusted_operation, + Cli, CliResult, CliResultOk, +}; +use ita_stf::{Index, RaffleIndex, RaffleTrustedCall, TrustedCall}; +use itp_stf_primitives::{ + traits::TrustedCallSigning, + types::{KeyPair, TrustedOperation}, +}; +use itp_types::AccountId; +use log::*; +use sp_core::{crypto::Ss58Codec, Pair}; +use std::boxed::Box; + +#[derive(Debug, Parser)] +pub struct RegisterForRaffleCmd { + /// Sender's incognito AccountId in ss58check format + from: String, + + /// The raffle to register for + raffle_index: RaffleIndex, +} + +impl RegisterForRaffleCmd { + pub(crate) fn run(&self, cli: &Cli, trusted_args: &TrustedCli) -> CliResult { + let sender = get_pair_from_str(trusted_args, &self.from); + let sender_acc: AccountId = sender.public().into(); + info!("senders ss58 is {}", sender.public().to_ss58check()); + + let (mrenclave, shard) = get_identifiers(trusted_args); + let nonce = get_layer_two_nonce!(sender, cli, trusted_args); + + let add_raffle_call = RaffleTrustedCall::registerForRaffle { + origin: sender_acc, + raffle_index: self.raffle_index, + }; + + let function_call = TrustedCall::raffle(add_raffle_call) + .sign(&KeyPair::Sr25519(Box::new(sender)), nonce, &mrenclave, &shard) + .into_trusted_operation(trusted_args.direct); + Ok(perform_trusted_operation::<()>(cli, trusted_args, &function_call) + .map(|_| CliResultOk::None)?) + } +} diff --git a/cli/src/trusted_cli.rs b/cli/src/trusted_cli.rs index 5c1f5d6553..b0f36f99e0 100644 --- a/cli/src/trusted_cli.rs +++ b/cli/src/trusted_cli.rs @@ -19,7 +19,7 @@ use crate::{benchmark::BenchmarkCommand, Cli, CliResult}; #[cfg(feature = "evm")] use crate::evm::EvmCommand; -use crate::trusted_base_cli::TrustedBaseCommand; +use crate::{raffle::RaffleTrustedCommand, trusted_base_cli::TrustedBaseCommand}; #[derive(Args)] pub struct TrustedCli { @@ -52,6 +52,9 @@ pub enum TrustedCommand { #[clap(flatten)] EvmCommands(EvmCommand), + #[clap(flatten)] + Raffle(RaffleTrustedCommand), + /// Run Benchmark Benchmark(BenchmarkCommand), } @@ -60,6 +63,7 @@ impl TrustedCli { pub(crate) fn run(&self, cli: &Cli) -> CliResult { match &self.command { TrustedCommand::BaseTrusted(cmd) => cmd.run(cli, self), + TrustedCommand::Raffle(cmd) => cmd.run(cli, self), TrustedCommand::Benchmark(cmd) => cmd.run(cli, self), #[cfg(feature = "evm")] TrustedCommand::EvmCommands(cmd) => cmd.run(cli, self), diff --git a/enclave-runtime/Cargo.lock b/enclave-runtime/Cargo.lock index 3ddfa816bf..329d1898fb 100644 --- a/enclave-runtime/Cargo.lock +++ b/enclave-runtime/Cargo.lock @@ -1680,6 +1680,38 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "ita-raffle-stf" +version = "0.9.0" +dependencies = [ + "frame-support", + "frame-system", + "ita-sgx-runtime", + "itp-hashing", + "itp-node-api", + "itp-node-api-metadata", + "itp-sgx-externalities", + "itp-sgx-runtime-primitives", + "itp-stf-interface", + "itp-stf-primitives", + "itp-storage", + "itp-types", + "itp-utils", + "log", + "pallet-balances", + "pallet-parentchain", + "pallet-raffles", + "pallet-sudo", + "parity-scale-codec", + "rlp", + "sgx_tstd", + "sha3 0.10.8", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "ita-sgx-runtime" version = "0.9.0" @@ -1691,11 +1723,13 @@ dependencies = [ "pallet-balances", "pallet-evm", "pallet-parentchain", + "pallet-raffles", "pallet-sudo", "pallet-timestamp", "pallet-transaction-payment", "parity-scale-codec", "scale-info", + "sgx_rand", "sp-api", "sp-core", "sp-runtime", @@ -1709,6 +1743,7 @@ version = "0.9.0" dependencies = [ "frame-support", "frame-system", + "ita-raffle-stf", "ita-sgx-runtime", "itp-hashing", "itp-node-api", @@ -1976,6 +2011,15 @@ dependencies = [ "yasna", ] +[[package]] +name = "itp-binary-merkle-tree" +version = "0.8.0" +dependencies = [ + "binary-merkle-tree", + "parity-scale-codec", + "serde 1.0.192", +] + [[package]] name = "itp-component-container" version = "0.8.0" @@ -2971,6 +3015,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-raffles" +version = "0.11.0" +dependencies = [ + "frame-support", + "frame-system", + "itp-binary-merkle-tree", + "log", + "parity-scale-codec", + "scale-info", + "serde 1.0.192", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-sudo" version = "4.0.0-dev"