Skip to content
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ cumulus-pallet-parachain-system = { git = "https://github.com/paritytech/cumulus
cumulus-primitives-core = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "rococo-v1" }
parachain-info = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "rococo-v1" }

[dev-dependencies]
cumulus-test-relay-sproof-builder = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "rococo-v1" }
cumulus-primitives-parachain-inherent = { git = "https://github.com/paritytech/cumulus", default-features = false, branch = "rococo-v1" }

[build-dependencies]
substrate-wasm-builder = { version = "4.0.0", git = "https://github.com/paritytech/substrate", branch = "rococo-v1" }

Expand Down
303 changes: 303 additions & 0 deletions runtime/tests/integration_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,303 @@
// Copyright 2019-2021 PureStake Inc.
// This file is part of Moonbeam.

// Moonbeam is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Moonbeam is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Moonbeam. If not, see <http://www.gnu.org/licenses/>.

//! Moonbeam Runtime Integration Tests

#![cfg(test)]

use cumulus_primitives_parachain_inherent::ParachainInherentData;
use frame_support::{
assert_noop, assert_ok,
dispatch::Dispatchable,
traits::{GenesisBuild, OnFinalize, OnInitialize},
};
use moonbeam_runtime::{
AccountId, AuthorInherent, Balance, Balances, Call, Event, InflationInfo, ParachainStaking,
Range, Runtime, System, GLMR,
};
use sp_runtime::{DispatchError, Perbill};

fn run_to_block(n: u32) {
while System::block_number() < n {
AuthorInherent::on_finalize(System::block_number());
ParachainStaking::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
AuthorInherent::on_initialize(System::block_number());
}
}

fn last_event() -> Event {
System::events().pop().expect("Event expected").event
}

struct ExtBuilder {
// balances
balances: Vec<(AccountId, Balance)>,
// [collator, amount]
collators: Vec<(AccountId, Balance)>,
// [nominator, collator, nomination_amount]
nominators: Vec<(AccountId, AccountId, Balance)>,
}

impl Default for ExtBuilder {
fn default() -> ExtBuilder {
ExtBuilder {
balances: vec![],
nominators: vec![],
collators: vec![],
}
}
}

impl ExtBuilder {
fn with_balances(mut self, balances: Vec<(AccountId, Balance)>) -> Self {
self.balances = balances;
self
}

fn with_collators(mut self, collators: Vec<(AccountId, Balance)>) -> Self {
self.collators = collators;
self
}

fn with_nominators(mut self, nominators: Vec<(AccountId, AccountId, Balance)>) -> Self {
self.nominators = nominators;
self
}

fn build(self) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default()
.build_storage::<Runtime>()
.unwrap();

pallet_balances::GenesisConfig::<Runtime> {
balances: self.balances,
}
.assimilate_storage(&mut t)
.unwrap();

let mut stakers: Vec<(AccountId, Option<AccountId>, Balance)> = Vec::new();
for collator in self.collators {
stakers.push((collator.0, None, collator.1));
}
for nominator in self.nominators {
stakers.push((nominator.0, Some(nominator.1), nominator.2));
}
parachain_staking::GenesisConfig::<Runtime> {
stakers,
inflation_config: InflationInfo {
expect: Range {
min: 100_000 * GLMR,
ideal: 200_000 * GLMR,
max: 500_000 * GLMR,
},
// unrealistically high parameterization, only for testing
round: Range {
min: Perbill::from_percent(5),
ideal: Perbill::from_percent(5),
max: Perbill::from_percent(5),
},
},
}
.assimilate_storage(&mut t)
.unwrap();

let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
ext
}
}

const ALICE: [u8; 20] = [4u8; 20];
const BOB: [u8; 20] = [5u8; 20];
const CHARLIE: [u8; 20] = [6u8; 20];
const DAVE: [u8; 20] = [7u8; 20];

fn origin_of(account_id: AccountId) -> <Runtime as frame_system::Config>::Origin {
<Runtime as frame_system::Config>::Origin::signed(account_id)
}

fn inherent_origin() -> <Runtime as frame_system::Config>::Origin {
<Runtime as frame_system::Config>::Origin::none()
}

#[test]
fn join_collator_candidates() {
ExtBuilder::default()
.with_balances(vec![
(AccountId::from(ALICE), 2_000 * GLMR),
(AccountId::from(BOB), 2_000 * GLMR),
(AccountId::from(CHARLIE), 1_100 * GLMR),
(AccountId::from(DAVE), 1_000 * GLMR),
])
.with_collators(vec![
(AccountId::from(ALICE), 1_000 * GLMR),
(AccountId::from(BOB), 1_000 * GLMR),
])
.with_nominators(vec![
(AccountId::from(CHARLIE), AccountId::from(ALICE), 50 * GLMR),
(AccountId::from(CHARLIE), AccountId::from(BOB), 50 * GLMR),
])
.build()
.execute_with(|| {
assert_noop!(
ParachainStaking::join_candidates(origin_of(AccountId::from(ALICE)), 1_000 * GLMR,),
parachain_staking::Error::<Runtime>::CandidateExists
);
assert_noop!(
ParachainStaking::join_candidates(
origin_of(AccountId::from(CHARLIE)),
1_000 * GLMR
),
parachain_staking::Error::<Runtime>::NominatorExists
);
assert!(System::events().is_empty());
assert_ok!(ParachainStaking::join_candidates(
origin_of(AccountId::from(DAVE)),
1_000 * GLMR,
));
assert_eq!(
last_event(),
Event::parachain_staking(parachain_staking::Event::JoinedCollatorCandidates(
AccountId::from(DAVE),
1_000 * GLMR,
3_100 * GLMR
))
);
});
}

#[test]
fn transfer_through_evm_to_stake() {
ExtBuilder::default()
.with_balances(vec![(AccountId::from(ALICE), 3_000 * GLMR)])
.build()
.execute_with(|| {
// Charlie has no balance => fails to stake
assert_noop!(
ParachainStaking::join_candidates(
origin_of(AccountId::from(CHARLIE)),
1_000 * GLMR,
),
DispatchError::Module {
index: 3,
error: 3,
message: Some("InsufficientBalance")
}
);
// Alice stakes to become a collator candidate
assert_ok!(ParachainStaking::join_candidates(
origin_of(AccountId::from(ALICE)),
1_000 * GLMR,
));
// Alice transfer from free balance 1000 GLMR to Bob
assert_ok!(Balances::transfer(
origin_of(AccountId::from(ALICE)),
AccountId::from(BOB),
2_000 * GLMR,
));
assert_eq!(Balances::free_balance(AccountId::from(BOB)), 2_000 * GLMR,);
use sp_core::U256;
let gas_limit = 100000u64;
let gas_price: U256 = 1000.into();
// Bob transfers 1000 GLMR to Charlie via EVM
assert_ok!(Call::EVM(pallet_evm::Call::<Runtime>::call(
AccountId::from(BOB),
AccountId::from(CHARLIE),
Vec::new(),
(1_000 * GLMR).into(),
gas_limit,
gas_price,
None
))
.dispatch(origin_of(AccountId::from(BOB))));
assert_eq!(
Balances::free_balance(AccountId::from(CHARLIE)),
1_000 * GLMR,
);
// Charlie can stake now
assert_ok!(ParachainStaking::join_candidates(
origin_of(AccountId::from(CHARLIE)),
1_000 * GLMR,
),);
});
}

#[test]
fn reward_block_authors() {
ExtBuilder::default()
.with_balances(vec![
(AccountId::from(ALICE), 2_000 * GLMR),
(AccountId::from(BOB), 1_000 * GLMR),
])
.with_collators(vec![(AccountId::from(ALICE), 1_000 * GLMR)])
.with_nominators(vec![(
AccountId::from(BOB),
AccountId::from(ALICE),
500 * GLMR,
)])
.build()
.execute_with(|| {
// set parachain inherent data
use cumulus_primitives_core::PersistedValidationData;
use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
let (relay_parent_storage_root, relay_chain_state) =
RelayStateSproofBuilder::default().into_state_root_and_proof();
let vfp = PersistedValidationData {
relay_parent_number: 1u32,
relay_parent_storage_root,
..Default::default()
};
let parachain_inherent_data = ParachainInherentData {
validation_data: vfp,
relay_chain_state: relay_chain_state,
downward_messages: Default::default(),
horizontal_messages: Default::default(),
};
assert_ok!(Call::ParachainSystem(
cumulus_pallet_parachain_system::Call::<Runtime>::set_validation_data(
parachain_inherent_data
)
)
.dispatch(inherent_origin()));
fn set_alice_as_author() {
assert_ok!(
Call::AuthorInherent(author_inherent::Call::<Runtime>::set_author(
AccountId::from(ALICE)
))
.dispatch(inherent_origin())
);
}
for x in 2..1201 {
set_alice_as_author();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do you need to set alice as author if she is the only collator? Maybe set up a test with more than one collator and somehow count how many time each collator was selected as author and test that the balance reward is correct in a dynamic way

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An author must be set every block or on_finalize in author-inherent panics.

Having multiple collators to test correct reward distribution is already done in parachain-staking unit tests. I think it's reasonable to add the same test here as well.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joelamouche This is a good chance to learn about Substrate inherents. When a collator authors a block, they have the opportunity to include inherent extrinsics. In moonbeam there are three of them (timestamp, validation_data, and author) and all three are required. It is behind-the-scenes from a user perspective, but every time a collator authors a moonbeam block, they insert these three inherents. Since Amar is mocking here, and there is no collator process to do any actual block authoring, we have to call these inherents manually. So setting the author to Alice here is mocking Alice authoring the block.

Copy link
Contributor Author

@4meta5 4meta5 Apr 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe set up a test with more than one collator and somehow count how many time each collator was selected as author and test that the balance reward is correct in a dynamic way

I'd rather do this at the pallet level (and have done it there) where I can mock the set_author command more easily.

I added Charlie, Dave, and Eve to the set of collators in this test and none of them were included in the set of valid block authors so it kept returning the following error:

---- reward_block_authors stdout ----
thread 'reward_block_authors' panicked at 'Expected Ok(_). Got Err(
    DispatchErrorWithPostInfo {
        post_info: PostDispatchInfo {
            actual_weight: None,
            pays_fee: Pays::Yes,
        },
        error: DispatchError::Module {
            index: 17,
            error: 1,
            message: Some(
                "CannotBeAuthor",
            ),
        },
    },
)', runtime/tests/integration_test.rs:337:17

Testing multiple block authors in a runtime integration test is worthwhile, but I'd prefer for it to be pursued in a follow up...

run_to_block(x);
}
// no rewards doled out yet
assert_eq!(Balances::free_balance(AccountId::from(ALICE)), 1_000 * GLMR,);
assert_eq!(Balances::free_balance(AccountId::from(BOB)), 500 * GLMR,);
set_alice_as_author();
run_to_block(1201);
// rewards minted and distributed
assert_eq!(
Balances::free_balance(AccountId::from(ALICE)),
1109999999920000000000,
);
assert_eq!(
Balances::free_balance(AccountId::from(BOB)),
539999999960000000000,
);
});
}