1
// Copyright 2024 Moonbeam foundation
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
use crate::chain_spec::generate_accounts;
18
use moonbeam_core_primitives::{AccountId, Balance};
19
use pallet_parachain_staking::{Bond, CandidateMetadata, CollatorSnapshot, Delegations};
20
use parity_scale_codec::Encode;
21
use serde::Deserialize;
22
use sp_core::{blake2_128, twox_64};
23
use std::io::Read;
24
use std::path::PathBuf;
25

            
26
#[derive(Deserialize, Debug, Clone)]
27
pub struct StateEntryConcrete {
28
	pub(crate) pallet: String,
29
	pub(crate) storage: String,
30
	#[serde(
31
		skip_serializing_if = "Option::is_none",
32
		deserialize_with = "serde_hex::deserialize_as_option",
33
		default
34
	)]
35
	pub(crate) key: Option<Vec<u8>>,
36
	#[serde(deserialize_with = "serde_hex::deserialize")]
37
	pub(crate) value: Vec<u8>,
38
}
39

            
40
#[derive(Deserialize, Debug, Clone)]
41
pub struct StateEntryRaw {
42
	#[serde(deserialize_with = "serde_hex::deserialize")]
43
	pub(crate) key: Vec<u8>,
44
	#[serde(deserialize_with = "serde_hex::deserialize")]
45
	pub(crate) value: Vec<u8>,
46
}
47

            
48
#[derive(Deserialize, Debug, Clone)]
49
#[serde(untagged)]
50
pub enum StateEntry {
51
	Concrete(StateEntryConcrete),
52
	Raw(StateEntryRaw),
53
}
54

            
55
/// Mandatory state overrides that most exist when starting a node in lazy loading mode.
56
pub fn base_state_overrides(runtime_code: Option<PathBuf>) -> Vec<StateEntry> {
57
	use hex_literal::hex;
58
	let alith_address = hex!("f24ff3a9cf04c71dbc94d0b566f7a27b94566cac");
59
	let alith_pub = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d");
60
	let alith_staking_bond: Balance = 1_000_000_000_000_000_000;
61
	let mut overrides = vec![
62
		// Override `PendingValidationCode` since it conflicts
63
		// with lazy loading
64
		StateEntry::Concrete(StateEntryConcrete {
65
			pallet: "ParachainSystem".to_string(),
66
			storage: "PendingValidationCode".to_string(),
67
			key: None,
68
			value: Vec::new(),
69
		}),
70
		// Reset LastRelayChainBlockNumber
71
		StateEntry::Concrete(StateEntryConcrete {
72
			pallet: "ParachainSystem".to_string(),
73
			storage: "LastRelayChainBlockNumber".to_string(),
74
			key: None,
75
			value: 0u32.encode(),
76
		}),
77
		StateEntry::Concrete(StateEntryConcrete {
78
			pallet: "AuthorMapping".to_string(),
79
			storage: "NimbusLookup".to_string(),
80
			key: Some(
81
				[
82
					&blake2_128(alith_address.as_slice()),
83
					alith_address.as_slice(),
84
				]
85
				.concat(),
86
			),
87
			value: alith_pub.to_vec(),
88
		}),
89
		StateEntry::Concrete(StateEntryConcrete {
90
			pallet: "AuthorMapping".to_string(),
91
			storage: "MappingWithDeposit".to_string(),
92
			key: Some([&blake2_128(alith_pub.as_slice()), alith_pub.as_slice()].concat()),
93
			value: (alith_address, alith_staking_bond, alith_pub).encode(),
94
		}),
95
		// We only use one collator in lazy-loading
96
		StateEntry::Concrete(StateEntryConcrete {
97
			pallet: "ParachainStaking".to_string(),
98
			storage: "TotalSelected".to_string(),
99
			key: None,
100
			value: 1u32.encode(),
101
		}),
102
		// Set candidate pool
103
		StateEntry::Concrete(StateEntryConcrete {
104
			pallet: "ParachainStaking".to_string(),
105
			storage: "CandidateInfo".to_string(),
106
			key: Some(
107
				[&twox_64(alith_address.as_slice()), alith_address.as_slice()]
108
					.concat()
109
					.to_vec(),
110
			),
111
			value: CandidateMetadata::new(alith_staking_bond).encode(),
112
		}),
113
		StateEntry::Concrete(StateEntryConcrete {
114
			pallet: "ParachainStaking".to_string(),
115
			storage: "TopDelegations".to_string(),
116
			key: Some(
117
				[&twox_64(alith_address.as_slice()), alith_address.as_slice()]
118
					.concat()
119
					.to_vec(),
120
			),
121
			value: Delegations::<AccountId, Balance> {
122
				delegations: Default::default(),
123
				total: Default::default(),
124
			}
125
			.encode(),
126
		}),
127
		StateEntry::Concrete(StateEntryConcrete {
128
			pallet: "ParachainStaking".to_string(),
129
			storage: "CandidatePool".to_string(),
130
			key: None,
131
			value: {
132
				let bond = Bond::<AccountId, Balance> {
133
					owner: AccountId::from(alith_address),
134
					amount: alith_staking_bond,
135
				};
136

            
137
				vec![bond].encode()
138
			},
139
		}),
140
		// Set Alith as selected candidate
141
		StateEntry::Concrete(StateEntryConcrete {
142
			pallet: "ParachainStaking".to_string(),
143
			storage: "SelectedCandidates".to_string(),
144
			key: None,
145
			value: vec![alith_address].encode(),
146
		}),
147
		StateEntry::Concrete(StateEntryConcrete {
148
			pallet: "ParachainStaking".to_string(),
149
			storage: "AtStake".to_string(),
150
			key: {
151
				let round: u32 = 1;
152
				Some(
153
					[
154
						&twox_64(&round.encode()),
155
						round.encode().as_slice(),
156
						&twox_64(alith_address.as_slice()),
157
						alith_address.as_slice(),
158
					]
159
					.concat()
160
					.to_vec(),
161
				)
162
			},
163
			value: {
164
				CollatorSnapshot::<AccountId, Balance> {
165
					bond: alith_staking_bond.clone(),
166
					delegations: Default::default(),
167
					total: alith_staking_bond,
168
				}
169
				.encode()
170
			},
171
		}),
172
		// Reset SlotInfo
173
		StateEntry::Concrete(StateEntryConcrete {
174
			pallet: "AsyncBacking".to_string(),
175
			storage: "SlotInfo".to_string(),
176
			key: None,
177
			value: (1u64, 1u32).encode(),
178
		}),
179
	];
180

            
181
	// Default mnemonic if none was provided
182
	let test_mnemonic =
183
		"bottom drive obey lake curtain smoke basket hold race lonely fit walk".to_string();
184
	// Prefund the standard dev accounts
185
	for address in generate_accounts(test_mnemonic, 6) {
186
		overrides.push(StateEntry::Concrete(StateEntryConcrete {
187
			pallet: "System".to_string(),
188
			storage: "Account".to_string(),
189
			key: Some(
190
				[blake2_128(&address.0).as_slice(), address.0.as_slice()]
191
					.concat()
192
					.to_vec(),
193
			),
194
			value: frame_system::AccountInfo {
195
				nonce: 0u32,
196
				consumers: 0,
197
				providers: 1,
198
				sufficients: 0,
199
				data: pallet_balances::AccountData::<Balance> {
200
					free: Balance::MAX,
201
					reserved: Default::default(),
202
					frozen: Default::default(),
203
					flags: Default::default(),
204
				},
205
			}
206
			.encode(),
207
		}))
208
	}
209

            
210
	if let Some(path) = runtime_code {
211
		let mut reader = std::fs::File::open(path.clone())
212
			.expect(format!("Could not open file {:?}", path).as_str());
213
		let mut data = vec![];
214
		reader
215
			.read_to_end(&mut data)
216
			.expect("Runtime code override invalid.");
217

            
218
		overrides.push(StateEntry::Raw(StateEntryRaw {
219
			key: sp_core::storage::well_known_keys::CODE.to_vec(),
220
			value: data.to_vec(),
221
		}));
222
	}
223

            
224
	overrides
225
}
226

            
227
pub fn read(path: PathBuf) -> Result<Vec<StateEntry>, String> {
228
	let reader = std::fs::File::open(path).expect("Can open file");
229
	let state = serde_json::from_reader(reader).expect("Can parse state overrides JSON");
230

            
231
	Ok(state)
232
}
233

            
234
mod serde_hex {
235
	use hex::FromHex;
236
	use serde::{Deserialize, Deserializer};
237

            
238
	fn sanitize(data: &str) -> &str {
239
		if let Some(stripped_data) = data.strip_prefix("0x") {
240
			stripped_data
241
		} else {
242
			data
243
		}
244
	}
245

            
246
	pub fn deserialize_as_option<'de, D, T>(deserializer: D) -> Result<Option<T>, D::Error>
247
	where
248
		D: Deserializer<'de>,
249
		T: FromHex,
250
		<T as FromHex>::Error: std::fmt::Display + std::fmt::Debug,
251
	{
252
		Option::<String>::deserialize(deserializer).map(|value| {
253
			value.map(|data| FromHex::from_hex(sanitize(data.as_str())).expect("Invalid option"))
254
		})
255
	}
256

            
257
	pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
258
	where
259
		D: Deserializer<'de>,
260
		T: FromHex,
261
		<T as FromHex>::Error: std::fmt::Display + std::fmt::Debug,
262
	{
263
		String::deserialize(deserializer).map(|data| {
264
			FromHex::from_hex(sanitize(data.as_str())).expect("Invalid hex encoded string")
265
		})
266
	}
267
}