referrerpolicy=no-referrer-when-downgrade

xcm_fuzzer/
fuzz.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17// We do not declare all features used by `construct_runtime`
18#[allow(unexpected_cfgs)]
19mod parachain;
20
21// We do not declare all features used by `construct_runtime`
22#[allow(unexpected_cfgs)]
23mod relay_chain;
24
25use codec::DecodeLimit;
26use polkadot_core_primitives::AccountId;
27use polkadot_parachain_primitives::primitives::Id as ParaId;
28use sp_runtime::{traits::AccountIdConversion, BuildStorage};
29use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt};
30
31#[cfg(feature = "try-runtime")]
32use frame_support::traits::{TryState, TryStateSelect::All};
33use frame_support::{assert_ok, traits::IntegrityTest};
34use xcm::{latest::prelude::*, MAX_XCM_DECODE_DEPTH};
35
36use arbitrary::{Arbitrary, Error, Unstructured};
37
38pub const INITIAL_BALANCE: u128 = 1_000_000_000;
39
40decl_test_parachain! {
41	pub struct ParaA {
42		Runtime = parachain::Runtime,
43		XcmpMessageHandler = parachain::MsgQueue,
44		DmpMessageHandler = parachain::MsgQueue,
45		new_ext = para_ext(1),
46	}
47}
48
49decl_test_parachain! {
50	pub struct ParaB {
51		Runtime = parachain::Runtime,
52		XcmpMessageHandler = parachain::MsgQueue,
53		DmpMessageHandler = parachain::MsgQueue,
54		new_ext = para_ext(2),
55	}
56}
57
58decl_test_parachain! {
59	pub struct ParaC {
60		Runtime = parachain::Runtime,
61		XcmpMessageHandler = parachain::MsgQueue,
62		DmpMessageHandler = parachain::MsgQueue,
63		new_ext = para_ext(3),
64	}
65}
66
67decl_test_relay_chain! {
68	pub struct Relay {
69		Runtime = relay_chain::Runtime,
70		RuntimeCall = relay_chain::RuntimeCall,
71		RuntimeEvent = relay_chain::RuntimeEvent,
72		XcmConfig = relay_chain::XcmConfig,
73		MessageQueue = relay_chain::MessageQueue,
74		System = relay_chain::System,
75		new_ext = relay_ext(),
76	}
77}
78
79decl_test_network! {
80	pub struct MockNet {
81		relay_chain = Relay,
82		parachains = vec![
83			(1, ParaA),
84			(2, ParaB),
85			(3, ParaC),
86		],
87	}
88}
89
90// An XCM message that will be generated by the fuzzer through the Arbitrary trait
91struct XcmMessage {
92	// Source chain
93	source: u32,
94	// Destination chain
95	destination: u32,
96	// XCM message
97	message: Xcm<()>,
98}
99
100impl<'a> Arbitrary<'a> for XcmMessage {
101	fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, Error> {
102		let source: u32 = u.arbitrary()?;
103		let destination: u32 = u.arbitrary()?;
104		let mut encoded_message: &[u8] = u.arbitrary()?;
105		if let Ok(message) =
106			DecodeLimit::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut encoded_message)
107		{
108			return Ok(XcmMessage { source, destination, message });
109		}
110		Err(Error::IncorrectFormat)
111	}
112}
113
114pub fn para_account_id(id: u32) -> relay_chain::AccountId {
115	ParaId::from(id).into_account_truncating()
116}
117
118pub fn para_ext(para_id: u32) -> sp_io::TestExternalities {
119	use parachain::{MsgQueue, Runtime, System};
120
121	let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
122
123	pallet_balances::GenesisConfig::<Runtime> {
124		balances: (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect(),
125		..Default::default()
126	}
127	.assimilate_storage(&mut t)
128	.unwrap();
129
130	let mut ext = sp_io::TestExternalities::new(t);
131	ext.execute_with(|| {
132		System::set_block_number(1);
133		MsgQueue::set_para_id(para_id.into());
134	});
135	ext
136}
137
138pub fn relay_ext() -> sp_io::TestExternalities {
139	use relay_chain::{Runtime, System};
140
141	let mut t = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
142
143	let mut balances: Vec<(AccountId, u128)> = vec![];
144	balances.append(&mut (1..=3).map(|i| (para_account_id(i), INITIAL_BALANCE)).collect());
145	balances.append(&mut (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect());
146
147	pallet_balances::GenesisConfig::<Runtime> { balances, ..Default::default() }
148		.assimilate_storage(&mut t)
149		.unwrap();
150
151	let mut ext = sp_io::TestExternalities::new(t);
152	ext.execute_with(|| System::set_block_number(1));
153	ext
154}
155
156pub type RelayChainPalletXcm = pallet_xcm::Pallet<relay_chain::Runtime>;
157pub type ParachainPalletXcm = pallet_xcm::Pallet<parachain::Runtime>;
158
159// We check XCM messages recursively for blocklisted messages
160fn recursively_matches_blocklisted_messages(message: &Instruction<()>) -> bool {
161	match message {
162		DepositReserveAsset { xcm, .. } |
163		ExportMessage { xcm, .. } |
164		InitiateReserveWithdraw { xcm, .. } |
165		InitiateTeleport { xcm, .. } |
166		TransferReserveAsset { xcm, .. } |
167		SetErrorHandler(xcm) |
168		SetAppendix(xcm) => xcm.iter().any(recursively_matches_blocklisted_messages),
169		// The blocklisted message is the Transact instruction.
170		m => matches!(m, Transact { .. }),
171	}
172}
173
174fn run_input(xcm_messages: [XcmMessage; 5]) {
175	MockNet::reset();
176
177	#[cfg(not(fuzzing))]
178	println!();
179
180	for xcm_message in xcm_messages {
181		if xcm_message.message.iter().any(recursively_matches_blocklisted_messages) {
182			println!("  skipping message\n");
183			continue;
184		}
185
186		if xcm_message.source % 4 == 0 {
187			// We get the destination for the message
188			let parachain_id = (xcm_message.destination % 3) + 1;
189			let destination: Location = Parachain(parachain_id).into();
190			#[cfg(not(fuzzing))]
191			{
192				println!("  source:      Relay Chain");
193				println!("  destination: Parachain {parachain_id}");
194				println!("  message:     {:?}", xcm_message.message);
195			}
196			Relay::execute_with(|| {
197				assert_ok!(RelayChainPalletXcm::send_xcm(Here, destination, xcm_message.message));
198			})
199		} else {
200			// We get the source's execution method
201			let execute_with = match xcm_message.source % 4 {
202				1 => ParaA::execute_with,
203				2 => ParaB::execute_with,
204				_ => ParaC::execute_with,
205			};
206			// We get the destination for the message
207			let destination: Location = match xcm_message.destination % 4 {
208				n @ 1..=3 => (Parent, Parachain(n)).into(),
209				_ => Parent.into(),
210			};
211			#[cfg(not(fuzzing))]
212			{
213				let destination_str = match xcm_message.destination % 4 {
214					n @ 1..=3 => format!("Parachain {n}"),
215					_ => "Relay Chain".to_string(),
216				};
217				println!("  source:      Parachain {}", xcm_message.source % 4);
218				println!("  destination: {}", destination_str);
219				println!("  message:     {:?}", xcm_message.message);
220			}
221			// We execute the message with the appropriate source and destination
222			execute_with(|| {
223				assert_ok!(ParachainPalletXcm::send_xcm(Here, destination, xcm_message.message));
224			});
225		}
226		#[cfg(not(fuzzing))]
227		println!();
228		// We run integrity tests and try_runtime invariants
229		[ParaA::execute_with, ParaB::execute_with, ParaC::execute_with].iter().for_each(
230			|execute_with| {
231				execute_with(|| {
232					#[cfg(feature = "try-runtime")]
233					parachain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap();
234					parachain::AllPalletsWithSystem::integrity_test();
235				});
236			},
237		);
238		Relay::execute_with(|| {
239			#[cfg(feature = "try-runtime")]
240			relay_chain::AllPalletsWithSystem::try_state(Default::default(), All).unwrap();
241			relay_chain::AllPalletsWithSystem::integrity_test();
242		});
243	}
244}
245
246fn main() {
247	#[cfg(fuzzing)]
248	{
249		loop {
250			honggfuzz::fuzz!(|xcm_messages: [XcmMessage; 5]| {
251				run_input(xcm_messages);
252			})
253		}
254	}
255	#[cfg(not(fuzzing))]
256	{
257		use std::{env, fs, fs::File, io::Read};
258		let args: Vec<_> = env::args().collect();
259		let md = fs::metadata(&args[1]).unwrap();
260		let all_files = match md.is_dir() {
261			true => fs::read_dir(&args[1])
262				.unwrap()
263				.map(|x| x.unwrap().path().to_str().unwrap().to_string())
264				.collect::<Vec<String>>(),
265			false => (args[1..]).to_vec(),
266		};
267		println!("All_files {:?}", all_files);
268		for argument in all_files {
269			println!("Now doing file {:?}", argument);
270			let mut buffer: Vec<u8> = Vec::new();
271			let mut f = File::open(argument).unwrap();
272			f.read_to_end(&mut buffer).unwrap();
273			let mut unstructured = Unstructured::new(&buffer);
274			if let Ok(xcm_messages) = unstructured.arbitrary() {
275				run_input(xcm_messages);
276			}
277		}
278	}
279}