fuzz/
main.rs

1// Copyright 2025 Security Research Labs GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License./ DEALINGS IN THE SOFTWARE.
15#![allow(clippy::upper_case_acronyms)]
16mod grammar;
17
18use frame_system::Account;
19use fuzzed_runtime::{Balance, Balances, BalancesConfig, Runtime};
20use grammar::FuzzData;
21use pallet_balances::{Holds, TotalIssuance};
22use pallet_evm::{GasWeightMapping, Runner};
23use sp_core::H160;
24use sp_state_machine::BasicExternalities;
25
26fn main() {
27	ziggy::fuzz!(|data: FuzzData| {
28		let target = H160::from_low_u64_ne(2);
29		let gas_limit: u64 = 1_000_000;
30		new_test_ext().execute_with(|| {
31			let initial_total_issuance = TotalIssuance::<Runtime>::get();
32			let mut weight_limit =
33				pallet_evm::FixedGasWeightMapping::<Runtime>::gas_to_weight(gas_limit, true);
34			if data.check_proof_size {
35				*weight_limit.proof_size_mut() = weight_limit.proof_size() / 2;
36			}
37			let max_proof_size = weight_limit.proof_size();
38			let mut contract = vec![];
39			for op in &data.contract {
40				op.to_bytes(&mut contract);
41			}
42			pallet_evm::AccountCodes::<Runtime>::insert(target, contract);
43			let res = <Runtime as pallet_evm::Config>::Runner::call(
44				H160::default(),
45				target,
46				data.call_data,
47				data.value.into(),
48				gas_limit,
49				Some(1_000_000_000.into()),
50				None,
51				None,
52				Vec::new(),
53				Vec::new(),
54				true,
55				true,
56				Some(weight_limit),
57				Some(0u64),
58				&<Runtime as pallet_evm::Config>::config().clone(),
59			);
60			let proof_size = match res {
61				Ok(ref info) => info
62					.weight_info
63					.expect("weight info")
64					.proof_size_usage
65					.expect("proof size usage"),
66				Err(ref _info) => 0,
67			};
68			assert!(proof_size <= max_proof_size);
69			check_invariants(initial_total_issuance);
70		});
71	});
72}
73
74pub fn new_test_ext() -> BasicExternalities {
75	use sp_consensus_aura::sr25519::AuthorityId as AuraId;
76	use sp_runtime::{app_crypto::ByteArray, BuildStorage};
77	let accounts: Vec<fuzzed_runtime::AccountId> = (0..5).map(|i| [i; 32].into()).collect();
78	let t = fuzzed_runtime::RuntimeGenesisConfig {
79		system: Default::default(),
80		balances: BalancesConfig {
81			// Configure endowed accounts with initial balance of 1 << 80.
82			balances: accounts.iter().cloned().map(|k| (k, 1 << 80)).collect(),
83			..Default::default()
84		},
85		base_fee: Default::default(),
86		evm_chain_id: Default::default(),
87		aura: fuzzed_runtime::AuraConfig {
88			authorities: vec![AuraId::from_slice(&[0; 32]).unwrap()],
89		},
90		sudo: fuzzed_runtime::SudoConfig { key: None },
91		transaction_payment: Default::default(),
92		grandpa: Default::default(),
93		manual_seal: Default::default(),
94		ethereum: Default::default(),
95		evm: Default::default(),
96	}
97	.build_storage()
98	.unwrap();
99	BasicExternalities::new(t)
100}
101
102fn check_invariants(initial_total_issuance: Balance) {
103	// After execution of all blocks, we run general polkadot-sdk invariants
104	let mut counted_free: Balance = 0;
105	let mut counted_reserved: Balance = 0;
106	for (account, info) in Account::<Runtime>::iter() {
107		let consumers = info.consumers;
108		let providers = info.providers;
109		assert!(!(consumers > 0 && providers == 0), "Invalid c/p state");
110		counted_free += info.data.free;
111		counted_reserved += info.data.reserved;
112		let max_lock: Balance = Balances::locks(&account)
113			.iter()
114			.map(|l| l.amount)
115			.max()
116			.unwrap_or_default();
117		assert_eq!(
118			max_lock, info.data.frozen,
119			"Max lock should be equal to frozen balance"
120		);
121		let sum_holds: Balance = Holds::<Runtime>::get(account)
122			.iter()
123			.map(|l| l.amount)
124			.sum();
125		assert!(
126			sum_holds <= info.data.reserved,
127			"Sum of all holds ({sum_holds}) should be less than or equal to reserved balance {}",
128			info.data.reserved
129		);
130	}
131	let total_issuance = TotalIssuance::<Runtime>::get();
132	let counted_issuance = counted_free + counted_reserved;
133	assert_eq!(total_issuance, counted_issuance);
134	assert!(total_issuance <= initial_total_issuance);
135}