Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
281 lines (227 sloc) 8.96 KB
/// A runtime module template with necessary imports
/// Feel free to remove or edit this file as needed.
/// If you change the name of this file, make sure to update its references in runtime/src/lib.rs
/// If you remove this file, you can remove those references
/// For more guidance on Substrate modules, see the example module
/// https://github.com/paritytech/substrate/blob/master/srml/example/src/lib.rs
use support::{decl_module, decl_storage, decl_event, StorageValue, dispatch::Result};
use system::ensure_signed;
use support::traits::{Currency, WithdrawReason, ExistenceRequirement};
use runtime_primitives::traits::{Zero, Hash, Saturating};
use parity_codec::Encode;
/// The module's configuration trait.
pub trait Trait: balances::Trait {
// TODO: Add other types and constants required configure this module.
/// The overarching event type.
type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
}
/// This module's storage items.
decl_storage! {
trait Store for Module<T: Trait> as mymodule {
Payment get(payment): Option<T::Balance>;
Pot get(pot): T::Balance;
Nonce get(nonce): u64;
}
}
decl_module! {
/// The module declaration.
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// Initializing events
// this is needed only if you are using events in your module
fn deposit_event<T>() = default;
// This function initializes the `payment` storage item
// It also populates the pot with an initial value
fn set_payment(origin, value: T::Balance) -> Result {
// Ensure that the function call is a signed message (i.e. a transaction)
let _ = ensure_signed(origin)?;
// If `payment` is not initialized with some value
if Self::payment().is_none() {
// Set the value of `payment`
<Payment<T>>::put(value);
// Initialize the `pot` with the same value
<Pot<T>>::put(value);
// Raise event for the set payment
Self::deposit_event(RawEvent::PaymentSet(value));
}
// Return Ok(()) when everything happens successfully
Ok(())
}
// This function is allows a user to play our coin-flip game
fn play(origin) -> Result {
// Ensure that the function call is a signed message (i.e. a transaction)
// Additionally, derive the sender address from the signed message
let sender = ensure_signed(origin)?;
// Ensure that `payment` storage item has been set
let payment = Self::payment().ok_or("Must have payment amount set")?;
// Read our storage values, and place them in memory variables
let mut nonce = Self::nonce();
let mut pot = Self::pot();
// Try to withdraw the payment from the account, making sure that it will not kill the account
let _ = <balances::Module<T> as Currency<_>>::withdraw(&sender, payment, WithdrawReason::Reserve, ExistenceRequirement::KeepAlive)?;
let mut winnings = Zero::zero();
// Generate a random hash between 0-255 using a csRNG algorithm
if (<system::Module<T>>::random_seed(), &sender, nonce)
.using_encoded(<T as system::Trait>::Hashing::hash)
.using_encoded(|e| e[0] < 128)
{
// If the user won the coin flip, deposit the pot winnings; cannot fail
let _ = <balances::Module<T> as Currency<_>>::deposit_into_existing(&sender, pot)
.expect("`sender` must exist since a transaction is being made and withdraw will keep alive; qed.");
// Set the winnings
winnings = pot;
// Reduce the pot to zero
pot = Zero::zero();
}
// No matter the outcome, increase the pot by the payment amount
pot = pot.saturating_add(payment);
// Increment the nonce
nonce = nonce.wrapping_add(1);
// Store the updated values for our module
<Pot<T>>::put(pot);
<Nonce<T>>::put(nonce);
// Raise event for the play result
Self::deposit_event(RawEvent::PlayResult(sender, winnings));
// Return Ok(()) when everything happens successfully
Ok(())
}
}
}
decl_event!(
pub enum Event<T> where
AccountId = <T as system::Trait>::AccountId,
Balance = <T as balances::Trait>::Balance {
PaymentSet(Balance),
PlayResult(AccountId, Balance),
}
);
/// tests for this module
#[cfg(test)]
mod tests {
use super::*;
use runtime_io::with_externalities;
use primitives::{H256, Blake2Hasher};
use support::{
impl_outer_origin,
assert_ok,
assert_noop,
};
use runtime_primitives::{
BuildStorage,
traits::{BlakeTwo256, IdentityLookup},
testing::{Digest, DigestItem, Header}
};
impl_outer_origin! {
pub enum Origin for Test {}
}
// For testing the module, we construct most of a mock runtime. This means
// first constructing a configuration type (`Test`) which `impl`s each of the
// configuration traits of modules we want to use.
#[derive(Clone, Eq, PartialEq)]
pub struct Test;
impl system::Trait for Test {
type Origin = Origin;
type Index = u64;
type BlockNumber = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type Digest = Digest;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header;
type Event = ();
type Log = DigestItem;
}
impl balances::Trait for Test {
type Balance = u64;
type OnFreeBalanceZero = ();
type OnNewAccount = ();
type Event = ();
type TransactionPayment = ();
type TransferPayment = ();
type DustRemoval = ();
}
impl Trait for Test {
type Event = ();
}
type System = system::Module<Test>;
type Balances = balances::Module<Test>;
type mymodule = Module<Test>;
// This function basically just builds a genesis storage key/value store according to
// our desired mockup.
fn new_test_ext() -> runtime_io::TestExternalities<Blake2Hasher> {
let (mut t, mut c) = system::GenesisConfig::<Test>::default().build_storage().unwrap();
let _ = balances::GenesisConfig::<Test>{
balances: vec![
(1, 10),
(2, 20),
],
transaction_base_fee: 0,
transaction_byte_fee: 0,
existential_deposit: 0,
transfer_fee: 0,
creation_fee: 0,
vesting: vec![],
}.assimilate_storage(&mut t, &mut c).unwrap();
t.into()
}
#[test]
fn set_payment_should_work() {
with_externalities(&mut new_test_ext(), || {
// Set payment when payment is none
assert_ok!(mymodule::set_payment(Origin::signed(1), 100));
assert_eq!(mymodule::payment(), Some(100));
assert_eq!(mymodule::pot(), 100);
// Set payment when payment is not none
assert_ok!(mymodule::set_payment(Origin::signed(1), 200));
assert_eq!(mymodule::payment(), Some(100));
assert_eq!(mymodule::pot(), 100);
});
}
#[test]
fn play_security_check_should_work() {
with_externalities(&mut new_test_ext(), || {
// Test ensure_signed
assert_noop!(mymodule::play(Origin::ROOT), "bad origin: expected to be a signed origin");
// Test payment must be set
assert_noop!(mymodule::play(Origin::signed(2)), "Must have payment amount set");
// Check the balances in genesis config
assert_eq!(Balances::total_balance(&2), 20);
// set payment and pot, higher than the balances
<Payment<Test>>::put(30);
<Pot<Test>>::put(30);
assert_noop!(mymodule::play(Origin::signed(2)), "too few free funds in account");
// set payment and pot, lower than the balances
<Payment<Test>>::put(10);
<Pot<Test>>::put(10);
assert_ok!(mymodule::play(Origin::signed(2)));
})
}
#[test]
fn play_should_work_for_win() {
with_externalities(&mut new_test_ext(), || {
<Payment<Test>>::put(10);
<Pot<Test>>::put(30);
<Nonce<Test>>::put(1);
System::set_random_seed(H256::from_low_u64_be(100));
assert_ok!(mymodule::play(Origin::signed(2)));
assert_eq!(mymodule::payment(), Some(10));
assert_eq!(mymodule::pot(), 10);
assert_eq!(Balances::total_balance(&2), 40); // 20 - 10 (payment) + 30 (reward)
assert_eq!(mymodule::nonce(), 2);
})
}
#[test]
fn play_should_work_for_lose() {
with_externalities(&mut new_test_ext(), || {
<Payment<Test>>::put(10);
<Pot<Test>>::put(30);
<Nonce<Test>>::put(1);
System::set_random_seed(H256::from_low_u64_be(10));
assert_ok!(mymodule::play(Origin::signed(2)));
assert_eq!(mymodule::payment(), Some(10));
assert_eq!(mymodule::pot(), 40);
assert_eq!(Balances::total_balance(&2), 10);
assert_eq!(mymodule::nonce(), 2);
})
}
}
You can’t perform that action at this time.