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
//! Moonbeam 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 moonbeam_runtime::{
29
	currency::GLMR, currency::SUPPLY_FACTOR, AccountId, AuthorFilterConfig, AuthorMappingConfig,
30
	Balance, BalancesConfig, CrowdloanRewardsConfig, EVMConfig, EthereumChainIdConfig,
31
	EthereumConfig, GenesisAccount, InflationInfo, MaintenanceModeConfig,
32
	OpenTechCommitteeCollectiveConfig, ParachainInfoConfig, ParachainStakingConfig,
33
	PolkadotXcmConfig, Precompiles, Range, RuntimeGenesisConfig, TransactionPaymentConfig,
34
	TreasuryCouncilCollectiveConfig, HOURS, 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::{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
pub fn development_chain_spec(mnemonic: Option<String>, num_accounts: Option<u32>) -> ChainSpec {
48
	// Default mnemonic if none was provided
49
	let parent_mnemonic = mnemonic.unwrap_or_else(|| {
50
		"bottom drive obey lake curtain smoke basket hold race lonely fit walk".to_string()
51
	});
52
	let mut accounts = generate_accounts(parent_mnemonic, num_accounts.unwrap_or(10));
53
	// We add Gerald here
54
	accounts.push(AccountId::from(hex!(
55
		"6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b"
56
	)));
57

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

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

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

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

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

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

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

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

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