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
//! Moonbase 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::{
28
	currency::UNIT, AccountId, AuthorFilterConfig, AuthorMappingConfig, Balance, BalancesConfig,
29
	CrowdloanRewardsConfig, EVMConfig, EligibilityValue, EthereumChainIdConfig, EthereumConfig,
30
	GenesisAccount, InflationInfo, MaintenanceModeConfig, MoonbeamOrbitersConfig,
31
	OpenTechCommitteeCollectiveConfig, ParachainInfoConfig, ParachainStakingConfig,
32
	PolkadotXcmConfig, Precompiles, Range, RuntimeGenesisConfig, SudoConfig,
33
	TransactionPaymentConfig, TreasuryCouncilCollectiveConfig, XcmTransactorConfig, HOURS,
34
	WASM_BINARY,
35
};
36
use nimbus_primitives::NimbusId;
37
use pallet_transaction_payment::Multiplier;
38
use sc_service::ChainType;
39
#[cfg(test)]
40
use sp_core::ecdsa;
41
use sp_runtime::{traits::One, Perbill, Percent};
42

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

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

            
58
2
	// Prefund the benchmark account for frontier, if compiling for benchmarks
59
2
	#[cfg(feature = "runtime-benchmarks")]
60
2
	accounts.push(AccountId::from(hex!(
61
2
		"1000000000000000000000000000000000000001"
62
2
	)));
63
2

            
64
2
	ChainSpec::builder(
65
2
		WASM_BINARY.expect("WASM binary was not build, please build it!"),
66
2
		Extensions {
67
2
			relay_chain: "dev-service".into(),
68
2
			para_id: Default::default(),
69
2
		},
70
2
	)
71
2
	.with_name("Moonbase Development Testnet")
72
2
	.with_id("moonbase_dev")
73
2
	.with_chain_type(ChainType::Development)
74
2
	.with_properties(
75
2
		serde_json::from_str(
76
2
			"{\"tokenDecimals\": 18, \"tokenSymbol\": \"UNIT\", \"SS58Prefix\": 1287}",
77
2
		)
78
2
		.expect("Provided valid json map"),
79
2
	)
80
2
	.with_genesis_config(testnet_genesis(
81
2
		// Alith is Sudo
82
2
		accounts[0],
83
2
		// Treasury Council members: Baltathar, Charleth and Dorothy
84
2
		vec![accounts[1], accounts[2], accounts[3]],
85
2
		// Open Tech committee members: Alith, Baltathar and Charleth
86
2
		vec![accounts[0], accounts[1], accounts[2]],
87
2
		// Collator Candidate: Alice -> Alith
88
2
		vec![(
89
2
			accounts[0],
90
2
			get_from_seed::<NimbusId>("Alice"),
91
2
			1_000 * UNIT,
92
2
		)],
93
2
		// Delegations
94
2
		vec![],
95
2
		accounts.clone(),
96
2
		3_000_000 * UNIT,
97
2
		Default::default(), // para_id
98
2
		1281,               //ChainId
99
2
	))
100
2
	.build()
101
2
}
102

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

            
170
const COLLATOR_COMMISSION: Perbill = Perbill::from_percent(20);
171
const PARACHAIN_BOND_RESERVE_PERCENT: Percent = Percent::from_percent(30);
172
const BLOCKS_PER_ROUND: u32 = 2 * HOURS;
173
const BLOCKS_PER_YEAR: u32 = 31_557_600 / 6;
174
const NUM_SELECTED_CANDIDATES: u32 = 8;
175
5
pub fn moonbase_inflation_config() -> InflationInfo<Balance> {
176
5
	fn to_round_inflation(annual: Range<Perbill>) -> Range<Perbill> {
177
5
		use pallet_parachain_staking::inflation::perbill_annual_to_perbill_round;
178
5
		perbill_annual_to_perbill_round(
179
5
			annual,
180
5
			// rounds per year
181
5
			BLOCKS_PER_YEAR / BLOCKS_PER_ROUND,
182
5
		)
183
5
	}
184
5
	let annual = Range {
185
5
		min: Perbill::from_percent(4),
186
5
		ideal: Perbill::from_percent(5),
187
5
		max: Perbill::from_percent(5),
188
5
	};
189
5
	InflationInfo {
190
5
		// staking expectations
191
5
		expect: Range {
192
5
			min: 100_000 * UNIT,
193
5
			ideal: 200_000 * UNIT,
194
5
			max: 500_000 * UNIT,
195
5
		},
196
5
		// annual inflation
197
5
		annual,
198
5
		round: to_round_inflation(annual),
199
5
	}
200
5
}
201

            
202
5
pub fn testnet_genesis(
203
5
	root_key: AccountId,
204
5
	treasury_council_members: Vec<AccountId>,
205
5
	open_tech_committee_members: Vec<AccountId>,
206
5
	candidates: Vec<(AccountId, NimbusId, Balance)>,
207
5
	delegations: Vec<(AccountId, AccountId, Balance, Percent)>,
208
5
	endowed_accounts: Vec<AccountId>,
209
5
	crowdloan_fund_pot: Balance,
210
5
	para_id: ParaId,
211
5
	chain_id: u64,
212
5
) -> serde_json::Value {
213
5
	// This is the simplest bytecode to revert without returning any data.
214
5
	// We will pre-deploy it under all of our precompiles to ensure they can be called from
215
5
	// within contracts.
216
5
	// (PUSH1 0x00 PUSH1 0x00 REVERT)
217
5
	let revert_bytecode = vec![0x60, 0x00, 0x60, 0x00, 0xFD];
218
5

            
219
5
	let config = RuntimeGenesisConfig {
220
5
		system: Default::default(),
221
5
		balances: BalancesConfig {
222
5
			balances: endowed_accounts
223
5
				.iter()
224
5
				.cloned()
225
25
				.map(|k| (k, 1 << 80))
226
5
				.collect(),
227
5
		},
228
5
		crowdloan_rewards: CrowdloanRewardsConfig {
229
5
			funded_amount: crowdloan_fund_pot,
230
5
		},
231
5
		sudo: SudoConfig {
232
5
			key: Some(root_key),
233
5
		},
234
5
		parachain_info: ParachainInfoConfig {
235
5
			parachain_id: para_id,
236
5
			..Default::default()
237
5
		},
238
5
		ethereum_chain_id: EthereumChainIdConfig {
239
5
			chain_id,
240
5
			..Default::default()
241
5
		},
242
5
		evm: EVMConfig {
243
5
			// We need _some_ code inserted at the precompile address so that
244
5
			// the evm will actually call the address.
245
5
			accounts: Precompiles::used_addresses()
246
285
				.map(|addr| {
247
285
					(
248
285
						addr.into(),
249
285
						GenesisAccount {
250
285
							nonce: Default::default(),
251
285
							balance: Default::default(),
252
285
							storage: Default::default(),
253
285
							code: revert_bytecode.clone(),
254
285
						},
255
285
					)
256
285
				})
257
5
				.collect(),
258
5
			..Default::default()
259
5
		},
260
5
		ethereum: EthereumConfig {
261
5
			..Default::default()
262
5
		},
263
5
		parachain_staking: ParachainStakingConfig {
264
5
			candidates: candidates
265
5
				.iter()
266
5
				.cloned()
267
5
				.map(|(account, _, bond)| (account, bond))
268
5
				.collect(),
269
5
			delegations,
270
5
			inflation_config: moonbase_inflation_config(),
271
5
			collator_commission: COLLATOR_COMMISSION,
272
5
			parachain_bond_reserve_percent: PARACHAIN_BOND_RESERVE_PERCENT,
273
5
			blocks_per_round: BLOCKS_PER_ROUND,
274
5
			num_selected_candidates: NUM_SELECTED_CANDIDATES,
275
5
		},
276
5
		treasury_council_collective: TreasuryCouncilCollectiveConfig {
277
5
			phantom: Default::default(),
278
5
			members: treasury_council_members,
279
5
		},
280
5
		open_tech_committee_collective: OpenTechCommitteeCollectiveConfig {
281
5
			phantom: Default::default(),
282
5
			members: open_tech_committee_members,
283
5
		},
284
5
		author_filter: AuthorFilterConfig {
285
5
			eligible_count: EligibilityValue::new_unchecked(50),
286
5
			..Default::default()
287
5
		},
288
5
		author_mapping: AuthorMappingConfig {
289
5
			mappings: candidates
290
5
				.iter()
291
5
				.cloned()
292
5
				.map(|(account_id, author_id, _)| (author_id, account_id))
293
5
				.collect(),
294
5
		},
295
5
		proxy_genesis_companion: Default::default(),
296
5
		treasury: Default::default(),
297
5
		migrations: Default::default(),
298
5
		maintenance_mode: MaintenanceModeConfig {
299
5
			start_in_maintenance_mode: false,
300
5
			..Default::default()
301
5
		},
302
5
		// This should initialize it to whatever we have set in the pallet
303
5
		polkadot_xcm: PolkadotXcmConfig::default(),
304
5
		transaction_payment: TransactionPaymentConfig {
305
5
			multiplier: Multiplier::from(8u128),
306
5
			..Default::default()
307
5
		},
308
5
		moonbeam_orbiters: MoonbeamOrbitersConfig {
309
5
			min_orbiter_deposit: One::one(),
310
5
		},
311
5
		xcm_transactor: XcmTransactorConfig {
312
5
			relay_indices: moonbeam_relay_encoder::westend::WESTEND_RELAY_INDICES,
313
5
			..Default::default()
314
5
		},
315
5
	};
316
5

            
317
5
	serde_json::to_value(&config).expect("Could not build genesis config.")
318
5
}
319

            
320
#[cfg(test)]
321
mod tests {
322
	use super::*;
323
	#[test]
324
1
	fn test_derived_pairs_1() {
325
1
		let mnemonic =
326
1
			"bottom drive obey lake curtain smoke basket hold race lonely fit walk".to_string();
327
1
		let accounts = 10;
328
1
		let pairs = derive_bip44_pairs_from_mnemonic::<ecdsa::Public>(&mnemonic, accounts);
329
1
		let first_account = get_account_id_from_pair(pairs.first().unwrap().clone()).unwrap();
330
1
		let last_account = get_account_id_from_pair(pairs.last().unwrap().clone()).unwrap();
331
1

            
332
1
		let expected_first_account =
333
1
			AccountId::from(hex!("f24FF3a9CF04c71Dbc94D0b566f7A27B94566cac"));
334
1
		let expected_last_account =
335
1
			AccountId::from(hex!("2898FE7a42Be376C8BC7AF536A940F7Fd5aDd423"));
336
1
		assert_eq!(first_account, expected_first_account);
337
1
		assert_eq!(last_account, expected_last_account);
338
1
		assert_eq!(pairs.len(), 10);
339
1
	}
340
	#[test]
341
1
	fn test_derived_pairs_2() {
342
1
		let mnemonic =
343
1
			"slab nerve salon plastic filter inherit valve ozone crash thumb quality whale"
344
1
				.to_string();
345
1
		let accounts = 20;
346
1
		let pairs = derive_bip44_pairs_from_mnemonic::<ecdsa::Public>(&mnemonic, accounts);
347
1
		let first_account = get_account_id_from_pair(pairs.first().unwrap().clone()).unwrap();
348
1
		let last_account = get_account_id_from_pair(pairs.last().unwrap().clone()).unwrap();
349
1

            
350
1
		let expected_first_account =
351
1
			AccountId::from(hex!("1e56ca71b596f2b784a27a2fdffef053dbdeff83"));
352
1
		let expected_last_account =
353
1
			AccountId::from(hex!("4148202BF0c0Ad7697Cff87EbB83340C80c947f8"));
354
1
		assert_eq!(first_account, expected_first_account);
355
1
		assert_eq!(last_account, expected_last_account);
356
1
		assert_eq!(pairs.len(), 20);
357
1
	}
358
}