1
// Copyright 2019-2022 PureStake Inc.
2
// This file is part of Moonbeam.
3

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

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

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

            
17
//! Moonriver Chain Specifications and utilities for building them.
18
//!
19
//! Learn more about Substrate chain specifications at
20
//! https://substrate.dev/docs/en/knowledgebase/integrate/chain-spec
21

            
22
#[cfg(test)]
23
use crate::chain_spec::{derive_bip44_pairs_from_mnemonic, get_account_id_from_pair};
24
use crate::chain_spec::{generate_accounts, get_from_seed, Extensions};
25
use cumulus_primitives_core::ParaId;
26
use hex_literal::hex;
27
use moonbase_runtime::EligibilityValue;
28
use moonriver_runtime::{
29
	currency::MOVR, AccountId, AuthorFilterConfig, AuthorMappingConfig, Balance, BalancesConfig,
30
	CrowdloanRewardsConfig, EVMConfig, EthereumChainIdConfig, EthereumConfig, GenesisAccount,
31
	InflationInfo, MaintenanceModeConfig, OpenTechCommitteeCollectiveConfig, ParachainInfoConfig,
32
	ParachainStakingConfig, PolkadotXcmConfig, Precompiles, Range, RuntimeGenesisConfig,
33
	TransactionPaymentConfig, TreasuryCouncilCollectiveConfig, HOURS, WASM_BINARY,
34
};
35
use nimbus_primitives::NimbusId;
36
use pallet_transaction_payment::Multiplier;
37
use sc_service::ChainType;
38
#[cfg(test)]
39
use sp_core::ecdsa;
40
use sp_runtime::{Perbill, Percent};
41

            
42
/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
43
pub type ChainSpec = sc_service::GenericChainSpec<Extensions>;
44

            
45
/// Generate a chain spec for use with the development service.
46
pub fn development_chain_spec(mnemonic: Option<String>, num_accounts: Option<u32>) -> ChainSpec {
47
	// Default mnemonic if none was provided
48
	let parent_mnemonic = mnemonic.unwrap_or_else(|| {
49
		"bottom drive obey lake curtain smoke basket hold race lonely fit walk".to_string()
50
	});
51
	let mut accounts = generate_accounts(parent_mnemonic, num_accounts.unwrap_or(10));
52
	// We add Gerald here
53
	accounts.push(AccountId::from(hex!(
54
		"6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b"
55
	)));
56

            
57
	ChainSpec::builder(
58
		WASM_BINARY.expect("WASM binary was not build, please build it!"),
59
		Extensions {
60
			relay_chain: "dev-service".into(),
61
			para_id: Default::default(),
62
		},
63
	)
64
	.with_name("Moonriver Development Testnet")
65
	.with_id("moonriver_dev")
66
	.with_chain_type(ChainType::Development)
67
	.with_properties(
68
		serde_json::from_str(
69
			"{\"tokenDecimals\": 18, \"tokenSymbol\": \"MOVR\", \"SS58Prefix\": 1285}",
70
		)
71
		.expect("Provided valid json map"),
72
	)
73
	.with_genesis_config(testnet_genesis(
74
		// Treasury Council members: Baltathar, Charleth and Dorothy
75
		vec![accounts[1], accounts[2], accounts[3]],
76
		// Open Tech committee members: Alith and Baltathar
77
		vec![accounts[0], accounts[1]],
78
		// Collator Candidate: Alice -> Alith
79
		vec![(
80
			AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")),
81
			get_from_seed::<NimbusId>("Alice"),
82
			100_000 * MOVR,
83
		)],
84
		// Delegations
85
		vec![],
86
		accounts.clone(),
87
		3_000_000 * MOVR,
88
		Default::default(), // para_id
89
		1281,               //ChainId
90
	))
91
	.build()
92
}
93

            
94
/// Generate a default spec for the parachain service. Use this as a starting point when launching
95
/// a custom chain.
96
pub fn get_chain_spec(para_id: ParaId) -> ChainSpec {
97
	ChainSpec::builder(
98
		WASM_BINARY.expect("WASM binary was not build, please build it!"),
99
		Extensions {
100
			relay_chain: "kusama-local".into(),
101
			para_id: para_id.into(),
102
		},
103
	)
104
	// TODO Apps depends on this string to determine whether the chain is an ethereum compat
105
	// or not. We should decide the proper strings, and update Apps accordingly.
106
	// Or maybe Apps can be smart enough to say if the string contains "moonbeam" at all...
107
	.with_name("Moonriver Local Testnet")
108
	.with_id("moonriver_local")
109
	.with_chain_type(ChainType::Local)
110
	.with_properties(
111
		serde_json::from_str(
112
			"{\"tokenDecimals\": 18, \"tokenSymbol\": \"MOVR\", \"SS58Prefix\": 1285}",
113
		)
114
		.expect("Provided valid json map"),
115
	)
116
	.with_genesis_config(testnet_genesis(
117
		// Treasury Council members: Baltathar, Charleth and Dorothy
118
		vec![
119
			AccountId::from(hex!("3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0")),
120
			AccountId::from(hex!("798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc")),
121
			AccountId::from(hex!("773539d4Ac0e786233D90A233654ccEE26a613D9")),
122
		],
123
		// Open Tech committee members: Alith and Baltathar
124
		vec![
125
			AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")),
126
			AccountId::from(hex!("3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0")),
127
		],
128
		// Collator Candidates
129
		vec![
130
			// Alice -> Alith
131
			(
132
				AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")),
133
				get_from_seed::<NimbusId>("Alice"),
134
				100_000 * MOVR,
135
			),
136
			// Bob -> Baltathar
137
			(
138
				AccountId::from(hex!("3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0")),
139
				get_from_seed::<NimbusId>("Bob"),
140
				100_000 * MOVR,
141
			),
142
		],
143
		// Delegations
144
		vec![],
145
		// Endowed: Alith, Baltathar, Charleth and Dorothy
146
		vec![
147
			AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac")),
148
			AccountId::from(hex!("3Cd0A705a2DC65e5b1E1205896BaA2be8A07c6e0")),
149
			AccountId::from(hex!("798d4Ba9baf0064Ec19eB4F0a1a45785ae9D6DFc")),
150
			AccountId::from(hex!("773539d4Ac0e786233D90A233654ccEE26a613D9")),
151
		],
152
		3_000_000 * MOVR,
153
		para_id,
154
		1280, //ChainId
155
	))
156
	.build()
157
}
158

            
159
const COLLATOR_COMMISSION: Perbill = Perbill::from_percent(20);
160
const PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(30);
161
const BLOCKS_PER_ROUND: u32 = 2 * HOURS;
162
const BLOCKS_PER_YEAR: u32 = 31_557_600 / 12;
163
const NUM_SELECTED_CANDIDATES: u32 = 8;
164
pub fn moonriver_inflation_config() -> InflationInfo<Balance> {
165
	fn to_round_inflation(annual: Range<Perbill>) -> Range<Perbill> {
166
		use pallet_parachain_staking::inflation::perbill_annual_to_perbill_round;
167
		perbill_annual_to_perbill_round(
168
			annual,
169
			// rounds per year
170
			BLOCKS_PER_YEAR / BLOCKS_PER_ROUND,
171
		)
172
	}
173
	let annual = Range {
174
		min: Perbill::from_percent(4),
175
		ideal: Perbill::from_percent(5),
176
		max: Perbill::from_percent(5),
177
	};
178
	InflationInfo {
179
		// staking expectations
180
		expect: Range {
181
			min: 100_000 * MOVR,
182
			ideal: 200_000 * MOVR,
183
			max: 500_000 * MOVR,
184
		},
185
		// annual inflation
186
		annual,
187
		round: to_round_inflation(annual),
188
	}
189
}
190

            
191
pub fn testnet_genesis(
192
	treasury_council_members: Vec<AccountId>,
193
	open_tech_committee_members: Vec<AccountId>,
194
	candidates: Vec<(AccountId, NimbusId, Balance)>,
195
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
196
	endowed_accounts: Vec<AccountId>,
197
	crowdloan_fund_pot: Balance,
198
	para_id: ParaId,
199
	chain_id: u64,
200
) -> serde_json::Value {
201
	// This is the simplest bytecode to revert without returning any data.
202
	// We will pre-deploy it under all of our precompiles to ensure they can be called from
203
	// within contracts.
204
	// (PUSH1 0x00 PUSH1 0x00 REVERT)
205
	let revert_bytecode = vec![0x60, 0x00, 0x60, 0x00, 0xFD];
206

            
207
	let config = RuntimeGenesisConfig {
208
		system: Default::default(),
209
		balances: BalancesConfig {
210
			balances: endowed_accounts
211
				.iter()
212
				.cloned()
213
				.map(|k| (k, 1 << 80))
214
				.collect(),
215
		},
216
		crowdloan_rewards: CrowdloanRewardsConfig {
217
			funded_amount: crowdloan_fund_pot,
218
		},
219
		parachain_info: ParachainInfoConfig {
220
			parachain_id: para_id,
221
			..Default::default()
222
		},
223
		ethereum_chain_id: EthereumChainIdConfig {
224
			chain_id,
225
			..Default::default()
226
		},
227
		evm: EVMConfig {
228
			// We need _some_ code inserted at the precompile address so that
229
			// the evm will actually call the address.
230
			accounts: Precompiles::used_addresses()
231
				.map(|addr| {
232
					(
233
						addr.into(),
234
						GenesisAccount {
235
							nonce: Default::default(),
236
							balance: Default::default(),
237
							storage: Default::default(),
238
							code: revert_bytecode.clone(),
239
						},
240
					)
241
				})
242
				.collect(),
243
			..Default::default()
244
		},
245
		ethereum: EthereumConfig {
246
			..Default::default()
247
		},
248
		parachain_staking: ParachainStakingConfig {
249
			candidates: candidates
250
				.iter()
251
				.cloned()
252
				.map(|(account, _, bond)| (account, bond))
253
				.collect(),
254
			delegations,
255
			inflation_config: moonriver_inflation_config(),
256
			collator_commission: COLLATOR_COMMISSION,
257
			parachain_bond_reserve_percent: PARACHAIN_BOND_RESERVE_PERCENT,
258
			blocks_per_round: BLOCKS_PER_ROUND,
259
			num_selected_candidates: NUM_SELECTED_CANDIDATES,
260
		},
261
		treasury_council_collective: TreasuryCouncilCollectiveConfig {
262
			phantom: Default::default(),
263
			members: treasury_council_members,
264
		},
265
		open_tech_committee_collective: OpenTechCommitteeCollectiveConfig {
266
			phantom: Default::default(),
267
			members: open_tech_committee_members,
268
		},
269
		author_filter: AuthorFilterConfig {
270
			eligible_count: EligibilityValue::new_unchecked(50),
271
			..Default::default()
272
		},
273
		author_mapping: AuthorMappingConfig {
274
			mappings: candidates
275
				.iter()
276
				.cloned()
277
				.map(|(account_id, author_id, _)| (author_id, account_id))
278
				.collect(),
279
		},
280
		proxy_genesis_companion: Default::default(),
281
		treasury: Default::default(),
282
		migrations: Default::default(),
283
		maintenance_mode: MaintenanceModeConfig {
284
			start_in_maintenance_mode: false,
285
			..Default::default()
286
		},
287
		// This should initialize it to whatever we have set in the pallet
288
		polkadot_xcm: PolkadotXcmConfig::default(),
289
		transaction_payment: TransactionPaymentConfig {
290
			multiplier: Multiplier::from(10u128),
291
			..Default::default()
292
		},
293
	};
294

            
295
	serde_json::to_value(&config).expect("Could not build genesis config.")
296
}
297

            
298
#[cfg(test)]
299
mod tests {
300
	use super::*;
301
	#[test]
302
1
	fn test_derived_pairs_1() {
303
1
		let mnemonic =
304
1
			"bottom drive obey lake curtain smoke basket hold race lonely fit walk".to_string();
305
1
		let accounts = 10;
306
1
		let pairs = derive_bip44_pairs_from_mnemonic::<ecdsa::Public>(&mnemonic, accounts);
307
1
		let first_account = get_account_id_from_pair(pairs.first().unwrap().clone()).unwrap();
308
1
		let last_account = get_account_id_from_pair(pairs.last().unwrap().clone()).unwrap();
309
1

            
310
1
		let expected_first_account =
311
1
			AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"));
312
1
		let expected_last_account =
313
1
			AccountId::from(hex!("2898FE7a42Be376C8BC7AF536A940F7Fd5aDd423"));
314
1
		assert_eq!(first_account, expected_first_account);
315
1
		assert_eq!(last_account, expected_last_account);
316
1
		assert_eq!(pairs.len(), 10);
317
1
	}
318
	#[test]
319
1
	fn test_derived_pairs_2() {
320
1
		let mnemonic =
321
1
			"slab nerve salon plastic filter inherit valve ozone crash thumb quality whale"
322
1
				.to_string();
323
1
		let accounts = 20;
324
1
		let pairs = derive_bip44_pairs_from_mnemonic::<ecdsa::Public>(&mnemonic, accounts);
325
1
		let first_account = get_account_id_from_pair(pairs.first().unwrap().clone()).unwrap();
326
1
		let last_account = get_account_id_from_pair(pairs.last().unwrap().clone()).unwrap();
327
1

            
328
1
		let expected_first_account =
329
1
			AccountId::from(hex!("1e56ca71b596f2b784a27a2fdffef053dbdeff83"));
330
1
		let expected_last_account =
331
1
			AccountId::from(hex!("4148202BF0c0Ad7697Cff87EbB83340C80c947f8"));
332
1
		assert_eq!(first_account, expected_first_account);
333
1
		assert_eq!(last_account, expected_last_account);
334
1
		assert_eq!(pairs.len(), 20);
335
1
	}
336
}