referrerpolicy=no-referrer-when-downgrade

pallet_revive/
precompiles.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Exposes types that can be used to extend `pallet_revive` with additional functionality.
19//!
20//! In order to add a pre-compile:
21//!
22//! - Implement [`Precompile`] on a type. Most likely another pallet.
23//! - Add the type to a tuple passed into [`Config::Precompiles`].
24//! - Use the types inside the `run` module to test and benchmark your pre-compile.
25//!
26//! Use `alloy` through our re-export in this module to implement Eth ABI.
27
28mod builtin;
29
30mod tests;
31
32pub use crate::{
33	AddressMapper, TransactionLimits,
34	access_list::StorageOp,
35	exec::{
36		ExecError, PrecompileExt as Ext, PrecompileWithInfoExt as ExtWithInfo, ReentrancyProtection,
37	},
38	metering::{Diff, Token},
39	vm::RuntimeCosts,
40};
41pub use alloy_core as alloy;
42pub use sp_core::{H160, H256, U256};
43
44use crate::{
45	Config, Error as CrateError, exec::ExecResult, precompiles::builtin::Builtin,
46	primitives::ExecReturnValue,
47};
48use alloc::vec::Vec;
49use alloy::sol_types::{Panic, PanicKind, Revert, SolError, SolInterface};
50use core::num::NonZero;
51use pallet_revive_uapi::ReturnFlags;
52use sp_runtime::DispatchError;
53
54#[cfg(feature = "runtime-benchmarks")]
55pub(crate) use builtin::{
56	IBenchmarking, NoInfo as BenchmarkNoInfo, Storage as BenchmarkStorage,
57	System as BenchmarkSystem, WithInfo as BenchmarkWithInfo,
58};
59
60const UNIMPLEMENTED: &str = "A precompile must either implement `call` or `call_with_info`";
61
62/// A minimal EVM bytecode to be returned when a pre-compile is queried for its code.
63pub(crate) const EVM_REVERT: [u8; 5] = sp_core::hex2array!("60006000fd");
64
65/// The composition of all available pre-compiles.
66///
67/// This is how the rest of the pallet discovers and calls pre-compiles.
68pub(crate) type All<T> = (Builtin<T>, <T as Config>::Precompiles);
69
70/// Used by [`Precompile`] in order to declare at which addresses it will be called.
71///
72/// The 2 byte integer supplied here will be interpreted as big endian and copied to
73/// `address[16,17]`. Address `address[18,19]` is reserved for builtin precompiles. All other
74/// bytes are set to zero.
75///
76/// Big endian is chosen because it lines up with how you would invoke a pre-compile in Solidity.
77/// For example writing `staticcall(..., 0x05, ...)` in Solidity sets the highest (`address[19]`)
78/// byte to `5`.
79pub enum AddressMatcher {
80	/// The pre-compile will only be called for a single address.
81	///
82	/// This means the precompile will only be invoked for:
83	/// ```ignore
84	/// 00000000000000000000000000000000pppp0000
85	/// ```
86	///
87	/// Where `p` is the `u16` defined here as big endian.
88	Fixed(NonZero<u16>),
89	/// The pre-compile will be called for multiple addresses.
90	///
91	/// This is useful when some information should be encoded into the address.
92	///
93	/// This means the precompile will be invoked for all `x`:
94	/// ```ignore
95	/// xxxxxxxx000000000000000000000000pppp0000
96	/// ```
97	///
98	/// Where `p` is the `u16` defined here as big endian. Hence a maximum of 2 byte can be encoded
99	/// into the address. Allowing more bytes could lead to the situation where legitimate
100	/// accounts could exist at this address. Either by accident or on purpose.
101	Prefix(NonZero<u16>),
102}
103
104/// Same as `AddressMatcher` but for builtin pre-compiles.
105///
106/// It works in the same way as `AddressMatcher` but allows setting the full 4 byte prefix.
107/// Builtin pre-compiles must only use values `<= u16::MAX` to prevent collisions with
108/// external pre-compiles.
109pub(crate) enum BuiltinAddressMatcher {
110	Fixed(NonZero<u32>),
111	Prefix(NonZero<u32>),
112}
113
114/// A pre-compile can error in the same way that a real contract can.
115#[derive(derive_more::From, Debug, Eq, PartialEq)]
116pub enum Error {
117	/// This is the same as a contract writing `revert("I reverted")`.
118	///
119	/// Those are the errors that are commonly caught by Solidity try-catch blocks. Encodes
120	/// a string onto the output buffer.
121	Revert(Revert),
122	/// An error generated by Solidity itself.
123	///
124	/// Encodes an error code into the output buffer.
125	Panic(PanicKind),
126	/// Don't encode anything into the output buffer. Just trap.
127	///
128	/// Commonly used for out of gas or other resource errors.
129	Error(ExecError),
130}
131
132impl From<DispatchError> for Error {
133	fn from(error: DispatchError) -> Self {
134		Self::Error(error.into())
135	}
136}
137
138impl<T: Config> From<CrateError<T>> for Error {
139	fn from(error: CrateError<T>) -> Self {
140		Self::Error(DispatchError::from(error).into())
141	}
142}
143
144impl Error {
145	pub fn try_to_revert<T: Config>(e: DispatchError) -> Self {
146		let delegate_denied = CrateError::<T>::PrecompileDelegateDenied.into();
147		let construct = CrateError::<T>::TerminatedInConstructor.into();
148		let message = match () {
149			_ if e == delegate_denied => "illegal to call this pre-compile via delegate call",
150			_ if e == construct => "terminate pre-compile cannot be called from the constructor",
151			_ => return e.into(),
152		};
153		Self::Revert(message.into())
154	}
155}
156
157/// Type that can be implemented in other crates to extend the list of pre-compiles.
158///
159/// Only implement exactly one function. Either `call` or `call_with_info`.
160///
161/// # Warning
162///
163/// Pre-compiles are unmetered code. Hence they have to charge an appropriate amount of weight
164/// themselves. Generally, their first line of code should be a call to `env.charge(weight)`.
165pub trait Precompile {
166	/// Your runtime.
167	type T: Config;
168	/// The Solidity ABI definition of this pre-compile.
169	///
170	/// Use the [`self::alloy::sol`] macro to define your interface using Solidity syntax.
171	/// The input the caller passes to the pre-compile will be validated and parsed
172	/// according to this interface.
173	///
174	/// Please note that the return value is not validated and it is the pre-compiles
175	/// duty to return the abi encoded bytes conformant with the interface here.
176	type Interface: SolInterface;
177	/// Defines at which addresses this pre-compile exists.
178	const MATCHER: AddressMatcher;
179	/// Defines whether this pre-compile needs a contract info data structure in storage.
180	///
181	/// Enabling it unlocks more APIs for the pre-compile to use. Only pre-compiles with a
182	/// fixed matcher can set this to true. This is enforced at compile time. Reason is that
183	/// contract info is per address and not per pre-compile. Too many contract info structures
184	/// and accounts would be created otherwise.
185	///
186	/// # When set to **true**
187	///
188	/// - An account will be created at the pre-compiles address when it is called for the first
189	///   time. The ed is minted.
190	/// - Contract info data structure will be created in storage on first call.
191	/// - Only `call_with_info` should be implemented. `call` is never called.
192	///
193	/// # When set to **false**
194	///
195	/// - No account or any other state will be created for the address.
196	/// - Only `call` should be implemented. `call_with_info` is never called.
197	///
198	/// # What to use
199	///
200	/// Should be set to false if the additional functionality is not needed. A pre-compile with
201	/// contract info will incur both a storage read and write to its contract metadata when called.
202	///
203	/// The contract info enables additional functionality:
204	/// - Storage deposits: Collect deposits from the origin rather than the caller. This makes it
205	///   easier for contracts to interact with the pre-compile as deposits
206	/// 	are paid by the transaction signer (just like gas). It also makes refunding easier.
207	/// - Contract storage: You can use the contracts key value child trie storage instead of
208	///   providing your own state.
209	/// 	The contract storage automatically takes care of deposits.
210	/// 	Providing your own storage and using pallet_revive to collect deposits is also possible,
211	/// though.
212	/// - Instantitation: Contract instantiation requires the instantiator to have an account. This
213	/// 	is because its nonce is used to derive the new contracts account id and child trie id.
214	///
215	/// Have a look at [`ExtWithInfo`] to learn about the additional APIs that a contract info
216	/// unlocks.
217	const HAS_CONTRACT_INFO: bool;
218
219	/// Entry point for your pre-compile when `HAS_CONTRACT_INFO = false`.
220	#[allow(unused_variables)]
221	fn call(
222		address: &[u8; 20],
223		input: &Self::Interface,
224		env: &mut impl Ext<T = Self::T>,
225	) -> Result<Vec<u8>, Error> {
226		unimplemented!("{UNIMPLEMENTED}")
227	}
228
229	/// Entry point for your pre-compile when `HAS_CONTRACT_INFO = true`.
230	#[allow(unused_variables)]
231	fn call_with_info(
232		address: &[u8; 20],
233		input: &Self::Interface,
234		env: &mut impl ExtWithInfo<T = Self::T>,
235	) -> Result<Vec<u8>, Error> {
236		unimplemented!("{UNIMPLEMENTED}")
237	}
238}
239
240/// Same as `Precompile` but meant to be used by builtin pre-compiles.
241///
242/// This enabled builtin precompiles to exist at the highest bits. Those are not
243/// available to external pre-compiles in order to avoid collisions.
244///
245/// Automatically implemented for all types that implement `Precompile`.
246pub(crate) trait BuiltinPrecompile {
247	type T: Config;
248	type Interface: SolInterface;
249	const MATCHER: BuiltinAddressMatcher;
250	const HAS_CONTRACT_INFO: bool;
251	const CODE: &[u8] = &EVM_REVERT;
252
253	fn call(
254		_address: &[u8; 20],
255		_input: &Self::Interface,
256		_env: &mut impl Ext<T = Self::T>,
257	) -> Result<Vec<u8>, Error> {
258		unimplemented!("{UNIMPLEMENTED}")
259	}
260
261	fn call_with_info(
262		_address: &[u8; 20],
263		_input: &Self::Interface,
264		_env: &mut impl ExtWithInfo<T = Self::T>,
265	) -> Result<Vec<u8>, Error> {
266		unimplemented!("{UNIMPLEMENTED}")
267	}
268}
269
270/// A low level pre-compile that does not use Solidity ABI.
271///
272/// It is used to implement the original Ethereum pre-compiles which do not
273/// use Solidity ABI but just encode inputs and outputs packed in memory.
274///
275/// Automatically implemented for all types that implement `BuiltinPrecompile`.
276/// By extension also automatically implemented for all types implementing `Precompile`.
277pub(crate) trait PrimitivePrecompile {
278	type T: Config;
279	const MATCHER: BuiltinAddressMatcher;
280	const HAS_CONTRACT_INFO: bool;
281	const CODE: &[u8] = &[];
282
283	fn call(
284		_address: &[u8; 20],
285		_input: Vec<u8>,
286		_env: &mut impl Ext<T = Self::T>,
287	) -> Result<Vec<u8>, Error> {
288		unimplemented!("{UNIMPLEMENTED}")
289	}
290
291	fn call_with_info(
292		_address: &[u8; 20],
293		_input: Vec<u8>,
294		_env: &mut impl ExtWithInfo<T = Self::T>,
295	) -> Result<Vec<u8>, Error> {
296		unimplemented!("{UNIMPLEMENTED}")
297	}
298}
299
300/// A pre-compile ready to be called.
301pub(crate) struct Instance<E> {
302	has_contract_info: bool,
303	address: [u8; 20],
304	/// This is the function inside `PrimitivePrecompile` at `address`.
305	function: fn(&[u8; 20], Vec<u8>, &mut E) -> Result<Vec<u8>, Error>,
306}
307
308impl<E> Instance<E> {
309	pub fn has_contract_info(&self) -> bool {
310		self.has_contract_info
311	}
312
313	pub fn call(&self, input: Vec<u8>, env: &mut E) -> ExecResult {
314		let result = (self.function)(&self.address, input, env);
315		match result {
316			Ok(data) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data }),
317			Err(Error::Revert(msg)) => {
318				Ok(ExecReturnValue { flags: ReturnFlags::REVERT, data: msg.abi_encode() })
319			},
320			Err(Error::Panic(kind)) => Ok(ExecReturnValue {
321				flags: ReturnFlags::REVERT,
322				data: Panic::from(kind).abi_encode(),
323			}),
324			Err(Error::Error(err)) => Err(err.into()),
325		}
326	}
327}
328
329/// A composition of pre-compiles.
330///
331/// Automatically implemented for tuples of types that implement any of the
332/// pre-compile traits.
333pub(crate) trait Precompiles<T: Config> {
334	/// Used to generate compile time error when multiple pre-compiles use the same matcher.
335	const CHECK_COLLISION: ();
336	/// Does any of the pre-compiles use the range reserved for external pre-compiles.
337	///
338	/// This is just used to generate a compile time error if `Builtin` is using the external
339	/// range by accident.
340	const USES_EXTERNAL_RANGE: bool;
341
342	/// Returns the code of the pre-compile.
343	///
344	/// Just used when queried by `EXTCODESIZE` or the RPC. It is just
345	/// a bogus code that is never executed. Returns None if no pre-compile
346	/// exists at the specified address.
347	fn code(address: &[u8; 20]) -> Option<&'static [u8]>;
348
349	/// Get a reference to a specific pre-compile.
350	///
351	/// Returns `None` if no pre-compile exists at `address`.
352	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>>;
353}
354
355impl<P: Precompile> BuiltinPrecompile for P {
356	type T = <Self as Precompile>::T;
357	type Interface = <Self as Precompile>::Interface;
358	const MATCHER: BuiltinAddressMatcher = P::MATCHER.into_builtin();
359	const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
360
361	fn call(
362		address: &[u8; 20],
363		input: &Self::Interface,
364		env: &mut impl Ext<T = Self::T>,
365	) -> Result<Vec<u8>, Error> {
366		Self::call(address, input, env)
367	}
368
369	fn call_with_info(
370		address: &[u8; 20],
371		input: &Self::Interface,
372		env: &mut impl ExtWithInfo<T = Self::T>,
373	) -> Result<Vec<u8>, Error> {
374		Self::call_with_info(address, input, env)
375	}
376}
377
378impl<P: BuiltinPrecompile> PrimitivePrecompile for P {
379	type T = <Self as BuiltinPrecompile>::T;
380	const MATCHER: BuiltinAddressMatcher = P::MATCHER;
381	const HAS_CONTRACT_INFO: bool = P::HAS_CONTRACT_INFO;
382	const CODE: &[u8] = P::CODE;
383
384	fn call(
385		address: &[u8; 20],
386		input: Vec<u8>,
387		env: &mut impl Ext<T = Self::T>,
388	) -> Result<Vec<u8>, Error> {
389		log::trace!(target: crate::LOG_TARGET, "pre-compile call at {:?} with {:x?}", address, input);
390		let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
391			.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
392		let res = <Self as BuiltinPrecompile>::call(address, &call, env);
393		log::trace!(target: crate::LOG_TARGET, "pre-compile call at {:?} result: {:x?}", address, res);
394		res
395	}
396
397	fn call_with_info(
398		address: &[u8; 20],
399		input: Vec<u8>,
400		env: &mut impl ExtWithInfo<T = Self::T>,
401	) -> Result<Vec<u8>, Error> {
402		log::trace!(target: crate::LOG_TARGET, "pre-compile call_with_info at {:?} with {:x?}", address, input);
403		let call = <Self as BuiltinPrecompile>::Interface::abi_decode_validate(&input)
404			.map_err(|_| Error::Panic(PanicKind::ResourceError))?;
405		let res = <Self as BuiltinPrecompile>::call_with_info(address, &call, env);
406		log::trace!(target: crate::LOG_TARGET, "pre-compile call_with_info at {:?} result: {:x?}", address, res);
407		res
408	}
409}
410
411/// The collision check is verified by a trybuild test in `ui-tests/src/ui/precompiles_ui.rs`.
412#[impl_trait_for_tuples::impl_for_tuples(20)]
413#[tuple_types_custom_trait_bound(PrimitivePrecompile<T=T>)]
414impl<T: Config> Precompiles<T> for Tuple {
415	const CHECK_COLLISION: () = {
416		let matchers = [for_tuples!( #( Tuple::MATCHER ),* )];
417		if BuiltinAddressMatcher::has_duplicates(&matchers) {
418			panic!("Precompiles with duplicate matcher detected")
419		}
420		for_tuples!(
421			#(
422				let is_fixed = Tuple::MATCHER.is_fixed();
423				let has_info = Tuple::HAS_CONTRACT_INFO;
424				assert!(is_fixed || !has_info, "Only fixed precompiles can have a contract info.");
425			)*
426		);
427	};
428	const USES_EXTERNAL_RANGE: bool = {
429		let mut uses_external = false;
430		for_tuples!(
431			#(
432				if Tuple::MATCHER.suffix() > u16::MAX as u32 {
433					uses_external = true;
434				}
435			)*
436		);
437		uses_external
438	};
439
440	fn code(address: &[u8; 20]) -> Option<&'static [u8]> {
441		for_tuples!(
442			#(
443				if Tuple::MATCHER.matches(address) {
444					return Some(Tuple::CODE)
445				}
446			)*
447		);
448		None
449	}
450
451	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
452		let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
453		let mut instance: Option<Instance<E>> = None;
454		for_tuples!(
455			#(
456				if Tuple::MATCHER.matches(address) {
457					if Tuple::HAS_CONTRACT_INFO {
458						instance = Some(Instance {
459							address: *address,
460							has_contract_info: true,
461							function: Tuple::call_with_info,
462						})
463					} else {
464						instance = Some(Instance {
465							address: *address,
466							has_contract_info: false,
467							function: Tuple::call,
468						})
469					}
470				}
471			)*
472		);
473		instance
474	}
475}
476
477/// This references the private trait inside the crate.
478#[cfg(feature = "trybuild")]
479#[allow(private_bounds)]
480pub const fn check_collision_for<T: Config, Tuple: Precompiles<T>>() {
481	let _ = <Tuple as Precompiles<T>>::CHECK_COLLISION;
482}
483
484impl<T: Config> Precompiles<T> for (Builtin<T>, <T as Config>::Precompiles) {
485	const CHECK_COLLISION: () = {
486		assert!(
487			!<Builtin<T>>::USES_EXTERNAL_RANGE,
488			"Builtin precompiles must not use addresses reserved for external precompiles"
489		);
490	};
491	const USES_EXTERNAL_RANGE: bool = { <T as Config>::Precompiles::USES_EXTERNAL_RANGE };
492
493	fn code(address: &[u8; 20]) -> Option<&'static [u8]> {
494		<Builtin<T>>::code(address).or_else(|| <T as Config>::Precompiles::code(address))
495	}
496
497	fn get<E: ExtWithInfo<T = T>>(address: &[u8; 20]) -> Option<Instance<E>> {
498		let _ = <Self as Precompiles<T>>::CHECK_COLLISION;
499		<Builtin<T>>::get(address).or_else(|| <T as Config>::Precompiles::get(address))
500	}
501}
502
503impl AddressMatcher {
504	pub const fn base_address(&self) -> [u8; 20] {
505		self.into_builtin().base_address()
506	}
507
508	pub const fn highest_address(&self) -> [u8; 20] {
509		self.into_builtin().highest_address()
510	}
511
512	pub const fn matches(&self, address: &[u8; 20]) -> bool {
513		self.into_builtin().matches(address)
514	}
515
516	const fn into_builtin(&self) -> BuiltinAddressMatcher {
517		const fn left_shift(val: NonZero<u16>) -> NonZero<u32> {
518			let shifted = (val.get() as u32) << 16;
519			NonZero::new(shifted).expect(
520				"Value was non zero before.
521				The shift is small enough to not truncate any existing bits.
522				Hence the value is still non zero; qed",
523			)
524		}
525
526		match self {
527			Self::Fixed(i) => BuiltinAddressMatcher::Fixed(left_shift(*i)),
528			Self::Prefix(i) => BuiltinAddressMatcher::Prefix(left_shift(*i)),
529		}
530	}
531}
532
533impl BuiltinAddressMatcher {
534	pub const fn base_address(&self) -> [u8; 20] {
535		let suffix = self.suffix().to_be_bytes();
536		let mut address = [0u8; 20];
537		let mut i = 16;
538		while i < address.len() {
539			address[i] = suffix[i - 16];
540			i = i + 1;
541		}
542		address
543	}
544
545	pub const fn highest_address(&self) -> [u8; 20] {
546		let mut address = self.base_address();
547		match self {
548			Self::Fixed(_) => (),
549			Self::Prefix(_) => {
550				address[0] = 0xFF;
551				address[1] = 0xFF;
552				address[2] = 0xFF;
553				address[3] = 0xFF;
554			},
555		}
556		address
557	}
558
559	pub const fn matches(&self, address: &[u8; 20]) -> bool {
560		let base_address = self.base_address();
561		let mut i = match self {
562			Self::Fixed(_) => 0,
563			Self::Prefix(_) => 4,
564		};
565		while i < base_address.len() {
566			if address[i] != base_address[i] {
567				return false;
568			}
569			i = i + 1;
570		}
571		true
572	}
573
574	const fn suffix(&self) -> u32 {
575		match self {
576			Self::Fixed(i) => i.get(),
577			Self::Prefix(i) => i.get(),
578		}
579	}
580
581	const fn has_duplicates(nums: &[Self]) -> bool {
582		let len = nums.len();
583		let mut i = 0;
584		while i < len {
585			let mut j = i + 1;
586			while j < len {
587				if nums[i].suffix() == nums[j].suffix() {
588					return true;
589				}
590				j += 1;
591			}
592			i += 1;
593		}
594		false
595	}
596
597	const fn is_fixed(&self) -> bool {
598		matches!(self, Self::Fixed(_))
599	}
600}
601
602/// Types to run a pre-compile during testing or benchmarking.
603///
604/// Use the types exported from this module in order to test or benchmark
605/// your pre-compile. Module only exists when compiles for benchmarking
606/// or tests.
607#[cfg(any(test, feature = "runtime-benchmarks"))]
608pub mod run {
609	pub use crate::{
610		BalanceOf, MomentOf,
611		call_builder::{CallSetup, Contract, VmBinaryModule},
612	};
613	pub use sp_core::{H256, U256};
614
615	use super::*;
616
617	/// Convenience function to run pre-compiles for testing or benchmarking purposes.
618	///
619	/// Use [`CallSetup`] to create an appropriate environment to pass as the `ext` parameter.
620	/// Panics in case the `MATCHER` of `P` does not match the passed `address`.
621	pub fn precompile<P, E>(
622		ext: &mut E,
623		address: &[u8; 20],
624		input: &P::Interface,
625	) -> Result<Vec<u8>, Error>
626	where
627		P: Precompile<T = E::T>,
628		E: ExtWithInfo,
629	{
630		assert!(P::MATCHER.into_builtin().matches(address));
631		if P::HAS_CONTRACT_INFO {
632			P::call_with_info(address, input, ext)
633		} else {
634			P::call(address, input, ext)
635		}
636	}
637
638	/// Convenience function to run builtin pre-compiles from benchmarks.
639	#[cfg(feature = "runtime-benchmarks")]
640	pub(crate) fn builtin<E>(ext: &mut E, address: &[u8; 20], input: Vec<u8>) -> ExecResult
641	where
642		E: ExtWithInfo,
643	{
644		let precompile = <Builtin<E::T>>::get(address)
645			.ok_or(DispatchError::from("No pre-compile at address"))
646			.inspect_err(|_| {
647				log::debug!(target: crate::LOG_TARGET, "No pre-compile at address {address:?}");
648			})?;
649		precompile.call(input, ext)
650	}
651}