diff --git a/Cargo.lock b/Cargo.lock index a13fd5143..059beaad2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -528,6 +528,7 @@ dependencies = [ "pallet-assets", "pallet-evm", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-dispatch", "pallet-xc-asset-config", "parity-scale-codec", "scale-info", @@ -4936,6 +4937,7 @@ dependencies = [ "pallet-ethereum-checked", "pallet-evm", "pallet-evm-precompile-assets-erc20", + "pallet-evm-precompile-dispatch", "pallet-proxy", "pallet-unified-accounts", "pallet-utility", diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 8f0effb94..737ff4306 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -39,6 +39,7 @@ pallet-evm = { workspace = true } # Astar pallets & dependencies pallet-evm-precompile-assets-erc20 = { workspace = true } +pallet-evm-precompile-dispatch = { workspace = true } pallet-xc-asset-config = { workspace = true } [features] @@ -64,5 +65,6 @@ std = [ "pallet-assets/std", "pallet-evm/std", "pallet-evm-precompile-assets-erc20/std", + "pallet-evm-precompile-dispatch/std", ] runtime-benchmarks = ["xcm-builder/runtime-benchmarks", "pallet-assets/runtime-benchmarks"] diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 9cf71cb37..010feba29 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -32,6 +32,9 @@ pub mod xvm; /// EVM primitives. pub mod evm; +/// Precompiles +pub mod precompiles; + /// Benchmark primitives #[cfg(feature = "runtime-benchmarks")] pub mod benchmarks; diff --git a/primitives/src/precompiles.rs b/primitives/src/precompiles.rs new file mode 100644 index 000000000..26e5abef5 --- /dev/null +++ b/primitives/src/precompiles.rs @@ -0,0 +1,57 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; + +use fp_evm::{ExitError, PrecompileFailure}; +use frame_support::{ + dispatch::{DispatchClass, GetDispatchInfo, Pays}, + traits::Contains, +}; +use pallet_evm_precompile_dispatch::DispatchValidateT; + +/// Struct that allows only calls based on `Filter` to pass through. +pub struct DispatchFilterValidate>( + PhantomData<(RuntimeCall, Filter)>, +); + +impl> + DispatchValidateT for DispatchFilterValidate +{ + fn validate_before_dispatch( + _origin: &AccountId, + call: &RuntimeCall, + ) -> Option { + let info = call.get_dispatch_info(); + let paid_normal_call = info.pays_fee == Pays::Yes && info.class == DispatchClass::Normal; + if !paid_normal_call { + return Some(PrecompileFailure::Error { + exit_status: ExitError::Other("invalid call".into()), + }); + } + if Filter::contains(call) { + None + } else { + Some(PrecompileFailure::Error { + exit_status: ExitError::Other("call filtered out".into()), + }) + } + } +} diff --git a/runtime/astar/src/lib.rs b/runtime/astar/src/lib.rs index 92578b231..5ac39086f 100644 --- a/runtime/astar/src/lib.rs +++ b/runtime/astar/src/lib.rs @@ -22,6 +22,7 @@ // `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. #![recursion_limit = "256"] +pub use crate::precompiles::WhitelistedCalls; pub use astar_primitives::{ evm::EvmRevertCodeHandler, xcm::AssetLocationIdConverter, AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, diff --git a/runtime/astar/src/precompiles.rs b/runtime/astar/src/precompiles.rs index cc0d81180..ffe0e2cbd 100644 --- a/runtime/astar/src/precompiles.rs +++ b/runtime/astar/src/precompiles.rs @@ -18,6 +18,9 @@ //! The Astar Network EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. +use crate::RuntimeCall; +use astar_primitives::precompiles::DispatchFilterValidate; +use frame_support::traits::Contains; use pallet_evm::{ ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, @@ -45,6 +48,23 @@ use xcm::latest::prelude::MultiLocation; /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +/// Filter that only allows whitelisted runtime call to pass through dispatch precompile + +pub struct WhitelistedCalls; + +impl Contains for WhitelistedCalls { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { + calls.iter().all(|call| WhitelistedCalls::contains(call)) + } + RuntimeCall::DappsStaking(_) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + _ => false, + } + } +} /// The PrecompileSet installed in the Astar runtime. #[derive(Debug, Default, Clone, Copy)] pub struct AstarNetworkPrecompiles(PhantomData<(R, C)>); @@ -74,7 +94,7 @@ where DappsStakingWrapper: Precompile, BatchPrecompile: Precompile, XcmPrecompile: Precompile, - Dispatch: Precompile, + Dispatch>: Precompile, R: pallet_evm::Config + pallet_assets::Config + pallet_xcm::Config @@ -106,7 +126,10 @@ where a if a == hash(9) => Some(Blake2F::execute(handle)), // nor Ethereum precompiles : a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::::execute(handle)), + a if a == hash(1025) => Some(Dispatch::< + R, + DispatchFilterValidate, + >::execute(handle)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), // Astar precompiles (starts from 0x5000): diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index f183bc7c5..be79454ca 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -63,6 +63,7 @@ pub use astar_primitives::{ Balance, BlockNumber, Hash, Header, Index, Signature, }; +pub use crate::precompiles::WhitelistedCalls; #[cfg(feature = "std")] use sp_version::NativeVersion; use sp_version::RuntimeVersion; diff --git a/runtime/local/src/precompiles.rs b/runtime/local/src/precompiles.rs index 8520a7522..b2ccb203b 100644 --- a/runtime/local/src/precompiles.rs +++ b/runtime/local/src/precompiles.rs @@ -18,6 +18,9 @@ //! The Local EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. +use crate::RuntimeCall; +use astar_primitives::precompiles::DispatchFilterValidate; +use frame_support::traits::Contains; use pallet_evm::{ ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, @@ -43,6 +46,23 @@ use sp_std::marker::PhantomData; /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +/// Filter that only allows whitelisted runtime call to pass through dispatch precompile +pub struct WhitelistedCalls; + +impl Contains for WhitelistedCalls { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { + calls.iter().all(|call| WhitelistedCalls::contains(call)) + } + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + RuntimeCall::DappsStaking(_) => true, + _ => false, + } + } +} + /// The PrecompileSet installed in the Local runtime. #[derive(Debug, Default, Clone, Copy)] pub struct LocalNetworkPrecompiles(PhantomData); @@ -72,7 +92,7 @@ where DappsStakingWrapper: Precompile, BatchPrecompile: Precompile, XvmPrecompile>: Precompile, - Dispatch: Precompile, + Dispatch>: Precompile, R: pallet_evm::Config + pallet_xvm::Config + pallet_assets::Config @@ -103,7 +123,10 @@ where a if a == hash(9) => Some(Blake2F::execute(handle)), // nor Ethereum precompiles : a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::::execute(handle)), + a if a == hash(1025) => Some(Dispatch::< + R, + DispatchFilterValidate, + >::execute(handle)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), // Astar precompiles (starts from 0x5000): diff --git a/runtime/shibuya/src/lib.rs b/runtime/shibuya/src/lib.rs index cbe0283f8..26908d4b7 100644 --- a/runtime/shibuya/src/lib.rs +++ b/runtime/shibuya/src/lib.rs @@ -73,6 +73,8 @@ pub use astar_primitives::{ AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, }; +pub use crate::precompiles::WhitelistedCalls; + use pallet_evm_precompile_assets_erc20::AddressToAssetId; #[cfg(any(feature = "std", test))] diff --git a/runtime/shibuya/src/precompiles.rs b/runtime/shibuya/src/precompiles.rs index 3761e33ff..d87b7e31f 100644 --- a/runtime/shibuya/src/precompiles.rs +++ b/runtime/shibuya/src/precompiles.rs @@ -18,6 +18,9 @@ //! The Shibuya Network EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. +use crate::RuntimeCall; +use astar_primitives::precompiles::DispatchFilterValidate; +use frame_support::traits::Contains; use pallet_evm::{ ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, @@ -46,6 +49,23 @@ use xcm::latest::prelude::MultiLocation; /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +/// Filter that only allows whitelisted runtime call to pass through dispatch precompile +pub struct WhitelistedCalls; + +impl Contains for WhitelistedCalls { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { + calls.iter().all(|call| WhitelistedCalls::contains(call)) + } + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + RuntimeCall::DappsStaking(_) => true, + _ => false, + } + } +} + /// The PrecompileSet installed in the Shiden runtime. #[derive(Debug, Default, Clone, Copy)] pub struct ShibuyaNetworkPrecompiles(PhantomData<(R, C)>); @@ -77,7 +97,7 @@ where XcmPrecompile: Precompile, BatchPrecompile: Precompile, XvmPrecompile>: Precompile, - Dispatch: Precompile, + Dispatch>: Precompile, R: pallet_evm::Config + pallet_assets::Config + pallet_xcm::Config @@ -110,7 +130,10 @@ where a if a == hash(9) => Some(Blake2F::execute(handle)), // nor Ethereum precompiles : a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::::execute(handle)), + a if a == hash(1025) => Some(Dispatch::< + R, + DispatchFilterValidate, + >::execute(handle)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), // Astar precompiles (starts from 0x5000): diff --git a/runtime/shiden/src/lib.rs b/runtime/shiden/src/lib.rs index ecb37a6bc..e49698d88 100644 --- a/runtime/shiden/src/lib.rs +++ b/runtime/shiden/src/lib.rs @@ -65,6 +65,7 @@ use sp_runtime::{ }; use sp_std::prelude::*; +pub use crate::precompiles::WhitelistedCalls; pub use astar_primitives::{ evm::EvmRevertCodeHandler, xcm::AssetLocationIdConverter, AccountId, Address, AssetId, Balance, BlockNumber, Hash, Header, Index, Signature, diff --git a/runtime/shiden/src/precompiles.rs b/runtime/shiden/src/precompiles.rs index 62b1023e5..9e714eceb 100644 --- a/runtime/shiden/src/precompiles.rs +++ b/runtime/shiden/src/precompiles.rs @@ -18,6 +18,9 @@ //! The Shiden Network EVM precompiles. This can be compiled with ``#[no_std]`, ready for Wasm. +use crate::RuntimeCall; +use astar_primitives::precompiles::DispatchFilterValidate; +use frame_support::traits::Contains; use pallet_evm::{ ExitRevert, IsPrecompileResult, Precompile, PrecompileFailure, PrecompileHandle, PrecompileResult, PrecompileSet, @@ -45,6 +48,23 @@ use xcm::latest::prelude::MultiLocation; /// to Erc20AssetsPrecompileSet pub const ASSET_PRECOMPILE_ADDRESS_PREFIX: &[u8] = &[255u8; 4]; +/// Filter that only allows whitelisted runtime call to pass through dispatch precompile +pub struct WhitelistedCalls; + +impl Contains for WhitelistedCalls { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::Utility(pallet_utility::Call::batch { calls }) + | RuntimeCall::Utility(pallet_utility::Call::batch_all { calls }) => { + calls.iter().all(|call| WhitelistedCalls::contains(call)) + } + RuntimeCall::DappsStaking(_) => true, + RuntimeCall::Assets(pallet_assets::Call::transfer { .. }) => true, + _ => false, + } + } +} + /// The PrecompileSet installed in the Shiden runtime. #[derive(Debug, Default, Clone, Copy)] pub struct ShidenNetworkPrecompiles(PhantomData<(R, C)>); @@ -74,7 +94,7 @@ where DappsStakingWrapper: Precompile, BatchPrecompile: Precompile, XcmPrecompile: Precompile, - Dispatch: Precompile, + Dispatch>: Precompile, R: pallet_evm::Config + pallet_assets::Config + pallet_xcm::Config @@ -106,7 +126,10 @@ where a if a == hash(9) => Some(Blake2F::execute(handle)), // nor Ethereum precompiles : a if a == hash(1024) => Some(Sha3FIPS256::execute(handle)), - a if a == hash(1025) => Some(Dispatch::::execute(handle)), + a if a == hash(1025) => Some(Dispatch::< + R, + DispatchFilterValidate, + >::execute(handle)), a if a == hash(1026) => Some(ECRecoverPublicKey::execute(handle)), a if a == hash(1027) => Some(Ed25519Verify::execute(handle)), // Astar precompiles (starts from 0x5000): diff --git a/tests/integration/Cargo.toml b/tests/integration/Cargo.toml index 091dff78b..22779b38b 100644 --- a/tests/integration/Cargo.toml +++ b/tests/integration/Cargo.toml @@ -35,6 +35,7 @@ sp-runtime = { workspace = true } # astar dependencies pallet-ethereum-checked = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } +pallet-evm-precompile-dispatch = { workspace = true } pallet-unified-accounts = { workspace = true } precompile-utils = { workspace = true } diff --git a/tests/integration/src/dispatch_precompile_filter.rs b/tests/integration/src/dispatch_precompile_filter.rs new file mode 100644 index 000000000..1099d592f --- /dev/null +++ b/tests/integration/src/dispatch_precompile_filter.rs @@ -0,0 +1,223 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . +#![cfg(test)] + +use crate::setup::*; +use astar_primitives::precompiles::DispatchFilterValidate; +use fp_evm::{ExitError, PrecompileFailure}; +use frame_support::{ + dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays}, + traits::Contains, +}; +use pallet_evm_precompile_dispatch::DispatchValidateT; +use parity_scale_codec::Compact; + +/// Whitelisted Calls are defined in the runtime +#[test] +fn filter_accepts_batch_call_with_whitelisted_calls() { + ExtBuilder::default().build().execute_with(|| { + let contract = SmartContract::Evm(H160::repeat_byte(0x01)); + let inner_call = RuntimeCall::DappsStaking(DappStakingCall::Call::claim_staker { + contract_id: contract.clone(), + }); + let call = RuntimeCall::Utility(UtilityCall::batch { + calls: vec![inner_call], + }); + assert!(WhitelistedCalls::contains(&call)); + }); +} + +#[test] +fn filter_rejects_non_whitelisted_batch_calls() { + ExtBuilder::default().build().execute_with(|| { + // CASE1 - only non whitelisted calls + let transfer_call = RuntimeCall::Balances(BalancesCall::transfer { + dest: MultiAddress::Id(CAT), + value: 100_000_000_000, + }); + let transfer = Box::new(transfer_call); + let call = Box::new(RuntimeCall::Utility(UtilityCall::batch { + calls: vec![*transfer.clone()], + })); + + // Utility call containing Balances Call + assert!(!WhitelistedCalls::contains(&call)); + + // CASE 2 - now whitelisted mixed with whitelisted calls + + let contract = SmartContract::Evm(H160::repeat_byte(0x01)); + let staking_call = RuntimeCall::DappsStaking(DappStakingCall::Call::claim_staker { + contract_id: contract.clone(), + }); + let staking = Box::new(staking_call); + + let call = Box::new(RuntimeCall::Utility(UtilityCall::batch { + calls: vec![*transfer, *staking.clone()], + })); + + // Utility call containing Balances Call and Dappsstaking Call Fails filter + assert!(!WhitelistedCalls::contains(&call)); + }); +} + +#[test] +fn filter_accepts_whitelisted_calls() { + ExtBuilder::default().build().execute_with(|| { + // Dappstaking call works + let contract = SmartContract::Evm(H160::repeat_byte(0x01)); + let stake_call = RuntimeCall::DappsStaking(DappStakingCall::Call::claim_staker { + contract_id: contract.clone(), + }); + assert!(WhitelistedCalls::contains(&stake_call)); + + // Pallet::Assets transfer call works + let transfer_call = RuntimeCall::Assets(pallet_assets::Call::transfer { + id: Compact(0), + target: MultiAddress::Address20(H160::repeat_byte(0x01).into()), + amount: 100, + }); + assert!(WhitelistedCalls::contains(&transfer_call)); + }); +} + +#[test] +fn filter_rejects_non_whitelisted_calls() { + ExtBuilder::default().build().execute_with(|| { + // Random call from non whitelisted pallet doesn't work + let transfer_call = RuntimeCall::Balances(BalancesCall::transfer { + dest: MultiAddress::Id(CAT), + value: 100_000_000_000, + }); + assert!(!WhitelistedCalls::contains(&transfer_call)); + + // Only `transfer` call from pallet assets work + // Other random call from Pallet Assets doesn't work + let thaw_asset_call = + RuntimeCall::Assets(pallet_assets::Call::thaw_asset { id: Compact(0) }); + assert!(!WhitelistedCalls::contains(&thaw_asset_call)); + }) +} + +#[test] +fn filter_accepts_whitelisted_batch_all_calls() { + ExtBuilder::default().build().execute_with(|| { + let contract = SmartContract::Evm(H160::repeat_byte(0x01)); + let inner_call1 = RuntimeCall::DappsStaking(DappStakingCall::Call::claim_staker { + contract_id: contract.clone(), + }); + let inner_call2 = RuntimeCall::DappsStaking(DappStakingCall::Call::claim_staker { + contract_id: contract.clone(), + }); + let transfer_call = RuntimeCall::Assets(pallet_assets::Call::transfer { + id: Compact(0), + target: MultiAddress::Address20(H160::repeat_byte(0x01).into()), + amount: 100, + }); + let call = RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![inner_call1, inner_call2, transfer_call], + }); + assert!(WhitelistedCalls::contains(&call)); + }); +} + +#[test] +fn test_correct_dispatch_info_works() { + ExtBuilder::default().build().execute_with(|| { + // Mock implementation + struct Filter; + struct AccountId; + enum RuntimeCall { + System, + DappsStaking, + } + impl GetDispatchInfo for RuntimeCall { + fn get_dispatch_info(&self) -> DispatchInfo { + // Default is Pays::Yes and DispatchCall::Normal + DispatchInfo::default() + } + } + impl Contains for Filter { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::DappsStaking => true, + _ => false, + } + } + } + // Case 1: Whitelisted Call with correct Dispatch info + assert_eq!( + DispatchFilterValidate::::validate_before_dispatch( + &AccountId, + &RuntimeCall::DappsStaking + ), + Option::None + ); + // Case 2: Non-Whitelisted Call with correct Dispatch Info + assert_eq!( + DispatchFilterValidate::::validate_before_dispatch( + &AccountId, + &RuntimeCall::System + ), + Option::Some(PrecompileFailure::Error { + exit_status: ExitError::Other("call filtered out".into()), + }) + ); + }); +} + +#[test] +fn test_incorrect_dispatch_info_fails() { + ExtBuilder::default().build().execute_with(|| { + // Mock implementation + struct Filter; + struct AccountId; + enum RuntimeCall { + System, + DappsStaking, + } + impl GetDispatchInfo for RuntimeCall { + fn get_dispatch_info(&self) -> DispatchInfo { + DispatchInfo { + weight: Weight::default(), + class: DispatchClass::Normal, + // Should have been Pays::Yes for call to pass + pays_fee: Pays::No, + } + } + } + impl Contains for Filter { + fn contains(t: &RuntimeCall) -> bool { + match t { + RuntimeCall::DappsStaking => true, + _ => false, + } + } + } + + // WhiteListed Call fails because of incorrect DispatchInfo + assert_eq!( + DispatchFilterValidate::::validate_before_dispatch( + &AccountId, + &RuntimeCall::DappsStaking + ), + Option::Some(PrecompileFailure::Error { + exit_status: ExitError::Other("invalid call".into()), + }) + ); + }) +} diff --git a/tests/integration/src/lib.rs b/tests/integration/src/lib.rs index 9b599b80f..d43b84541 100644 --- a/tests/integration/src/lib.rs +++ b/tests/integration/src/lib.rs @@ -34,3 +34,5 @@ mod xvm; #[cfg(feature = "shibuya")] mod account; +#[cfg(any(feature = "shibuya", feature = "shiden", feature = "astar"))] +mod dispatch_precompile_filter;