referrerpolicy=no-referrer-when-downgrade

sc_executor/
executor.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19use crate::{
20	error::{Error, Result},
21	wasm_runtime::{RuntimeCache, WasmExecutionMethod},
22	RuntimeVersionOf,
23};
24
25use std::{
26	marker::PhantomData,
27	panic::{AssertUnwindSafe, UnwindSafe},
28	path::PathBuf,
29	sync::Arc,
30	time::Duration,
31};
32
33use codec::Encode;
34use sc_executor_common::{
35	runtime_blob::RuntimeBlob,
36	wasm_runtime::{
37		AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY,
38	},
39};
40use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode};
41use sp_version::RuntimeVersion;
42use sp_wasm_interface::HostFunctions;
43
44/// Set up the externalities and safe calling environment to execute runtime calls.
45///
46/// If the inner closure panics, it will be caught and return an error.
47pub fn with_externalities_safe<F, U>(ext: &mut dyn Externalities, f: F) -> Result<U>
48where
49	F: UnwindSafe + FnOnce() -> U,
50{
51	sp_externalities::set_and_run_with_externalities(ext, move || {
52		// Substrate uses custom panic hook that terminates process on panic. Disable
53		// termination for the native call.
54		let _guard = sp_panic_handler::AbortGuard::force_unwind();
55		std::panic::catch_unwind(f).map_err(|e| {
56			if let Some(err) = e.downcast_ref::<String>() {
57				Error::RuntimePanicked(err.clone())
58			} else if let Some(err) = e.downcast_ref::<&'static str>() {
59				Error::RuntimePanicked(err.to_string())
60			} else {
61				Error::RuntimePanicked("Unknown panic".into())
62			}
63		})
64	})
65}
66
67fn unwrap_heap_pages(pages: Option<HeapAllocStrategy>) -> HeapAllocStrategy {
68	pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY)
69}
70
71/// Builder for creating a [`WasmExecutor`] instance.
72pub struct WasmExecutorBuilder<H = sp_io::SubstrateHostFunctions> {
73	_phantom: PhantomData<H>,
74	method: WasmExecutionMethod,
75	onchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
76	offchain_heap_alloc_strategy: Option<HeapAllocStrategy>,
77	ignore_onchain_heap_pages: bool,
78	max_runtime_instances: usize,
79	cache_path: Option<PathBuf>,
80	allow_missing_host_functions: bool,
81	runtime_cache_size: u8,
82}
83
84impl<H> WasmExecutorBuilder<H> {
85	/// Create a new instance of `Self`
86	///
87	/// - `method`: The wasm execution method that should be used by the executor.
88	pub fn new() -> Self {
89		Self {
90			_phantom: PhantomData,
91			method: WasmExecutionMethod::default(),
92			onchain_heap_alloc_strategy: None,
93			offchain_heap_alloc_strategy: None,
94			ignore_onchain_heap_pages: false,
95			max_runtime_instances: 2,
96			runtime_cache_size: 4,
97			allow_missing_host_functions: false,
98			cache_path: None,
99		}
100	}
101
102	/// Create the wasm executor with execution method that should be used by the executor.
103	pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self {
104		self.method = method;
105		self
106	}
107
108	/// Create the wasm executor with the given number of `heap_alloc_strategy` for onchain runtime
109	/// calls.
110	pub fn with_onchain_heap_alloc_strategy(
111		mut self,
112		heap_alloc_strategy: HeapAllocStrategy,
113	) -> Self {
114		self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy);
115		self
116	}
117
118	/// Create the wasm executor with the given number of `heap_alloc_strategy` for offchain runtime
119	/// calls.
120	pub fn with_offchain_heap_alloc_strategy(
121		mut self,
122		heap_alloc_strategy: HeapAllocStrategy,
123	) -> Self {
124		self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy);
125		self
126	}
127
128	/// Create the wasm executor and follow/ignore onchain heap pages value.
129	///
130	/// By default this the onchain heap pages value is followed.
131	pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self {
132		self.ignore_onchain_heap_pages = ignore_onchain_heap_pages;
133		self
134	}
135
136	/// Create the wasm executor with the given maximum number of `instances`.
137	///
138	/// The number of `instances` defines how many different instances of a runtime the cache is
139	/// storing.
140	///
141	/// By default the maximum number of `instances` is `2`.
142	pub fn with_max_runtime_instances(mut self, instances: usize) -> Self {
143		self.max_runtime_instances = instances;
144		self
145	}
146
147	/// Create the wasm executor with the given `cache_path`.
148	///
149	/// The `cache_path` is A path to a directory where the executor can place its files for
150	/// purposes of caching. This may be important in cases when there are many different modules
151	/// with the compiled execution method is used.
152	///
153	/// By default there is no `cache_path` given.
154	pub fn with_cache_path(mut self, cache_path: impl Into<PathBuf>) -> Self {
155		self.cache_path = Some(cache_path.into());
156		self
157	}
158
159	/// Create the wasm executor and allow/forbid missing host functions.
160	///
161	/// If missing host functions are forbidden, the instantiation of a wasm blob will fail
162	/// for imported host functions that the executor is not aware of. If they are allowed,
163	/// a stub is generated that will return an error when being called while executing the wasm.
164	///
165	/// By default missing host functions are forbidden.
166	pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self {
167		self.allow_missing_host_functions = allow;
168		self
169	}
170
171	/// Create the wasm executor with the given `runtime_cache_size`.
172	///
173	/// Defines the number of different runtimes/instantiated wasm blobs the cache stores.
174	/// Runtimes/wasm blobs are differentiated based on the hash and the number of heap pages.
175	///
176	/// By default this value is set to `4`.
177	pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self {
178		self.runtime_cache_size = runtime_cache_size;
179		self
180	}
181
182	/// Build the configured [`WasmExecutor`].
183	pub fn build(self) -> WasmExecutor<H> {
184		WasmExecutor {
185			method: self.method,
186			default_offchain_heap_alloc_strategy: unwrap_heap_pages(
187				self.offchain_heap_alloc_strategy,
188			),
189			default_onchain_heap_alloc_strategy: unwrap_heap_pages(
190				self.onchain_heap_alloc_strategy,
191			),
192			ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
193			cache: Arc::new(RuntimeCache::new(
194				self.max_runtime_instances,
195				self.cache_path.clone(),
196				self.runtime_cache_size,
197			)),
198			cache_path: self.cache_path,
199			allow_missing_host_functions: self.allow_missing_host_functions,
200			phantom: PhantomData,
201		}
202	}
203}
204
205/// An abstraction over Wasm code executor. Supports selecting execution backend and
206/// manages runtime cache.
207pub struct WasmExecutor<H = sp_io::SubstrateHostFunctions> {
208	/// Method used to execute fallback Wasm code.
209	method: WasmExecutionMethod,
210	/// The heap allocation strategy for onchain Wasm calls.
211	default_onchain_heap_alloc_strategy: HeapAllocStrategy,
212	/// The heap allocation strategy for offchain Wasm calls.
213	default_offchain_heap_alloc_strategy: HeapAllocStrategy,
214	/// Ignore onchain heap pages value.
215	ignore_onchain_heap_pages: bool,
216	/// WASM runtime cache.
217	cache: Arc<RuntimeCache>,
218	/// The path to a directory which the executor can leverage for a file cache, e.g. put there
219	/// compiled artifacts.
220	cache_path: Option<PathBuf>,
221	/// Ignore missing function imports.
222	allow_missing_host_functions: bool,
223	phantom: PhantomData<H>,
224}
225
226impl<H> Clone for WasmExecutor<H> {
227	fn clone(&self) -> Self {
228		Self {
229			method: self.method,
230			default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy,
231			default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy,
232			ignore_onchain_heap_pages: self.ignore_onchain_heap_pages,
233			cache: self.cache.clone(),
234			cache_path: self.cache_path.clone(),
235			allow_missing_host_functions: self.allow_missing_host_functions,
236			phantom: self.phantom,
237		}
238	}
239}
240
241impl Default for WasmExecutor<sp_io::SubstrateHostFunctions> {
242	fn default() -> Self {
243		WasmExecutorBuilder::new().build()
244	}
245}
246
247impl<H> WasmExecutor<H> {
248	/// Create new instance.
249	///
250	/// # Parameters
251	///
252	/// `method` - Method used to execute Wasm code.
253	///
254	/// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this
255	/// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the
256	/// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None`
257	/// is provided.
258	///
259	/// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse.
260	///
261	/// `cache_path` - A path to a directory where the executor can place its files for purposes of
262	///   caching. This may be important in cases when there are many different modules with the
263	///   compiled execution method is used.
264	///
265	/// `runtime_cache_size` - The capacity of runtime cache.
266	#[deprecated(note = "use `Self::builder` method instead of it")]
267	pub fn new(
268		method: WasmExecutionMethod,
269		default_heap_pages: Option<u64>,
270		max_runtime_instances: usize,
271		cache_path: Option<PathBuf>,
272		runtime_cache_size: u8,
273	) -> Self {
274		WasmExecutor {
275			method,
276			default_onchain_heap_alloc_strategy: unwrap_heap_pages(
277				default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
278			),
279			default_offchain_heap_alloc_strategy: unwrap_heap_pages(
280				default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }),
281			),
282			ignore_onchain_heap_pages: false,
283			cache: Arc::new(RuntimeCache::new(
284				max_runtime_instances,
285				cache_path.clone(),
286				runtime_cache_size,
287			)),
288			cache_path,
289			allow_missing_host_functions: false,
290			phantom: PhantomData,
291		}
292	}
293
294	/// Instantiate a builder for creating an instance of `Self`.
295	pub fn builder() -> WasmExecutorBuilder<H> {
296		WasmExecutorBuilder::new()
297	}
298
299	/// Ignore missing function imports if set true.
300	#[deprecated(note = "use `Self::builder` method instead of it")]
301	pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) {
302		self.allow_missing_host_functions = allow_missing_host_functions
303	}
304}
305
306impl<H> WasmExecutor<H>
307where
308	H: HostFunctions,
309{
310	/// Heap allocation strategy for on-chain calls into `runtime_code`.
311	fn on_chain_heap_alloc_strategy(&self, runtime_code: &RuntimeCode) -> HeapAllocStrategy {
312		if self.ignore_onchain_heap_pages {
313			return self.default_onchain_heap_alloc_strategy;
314		}
315
316		runtime_code
317			.heap_pages
318			.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ })
319			.unwrap_or(self.default_onchain_heap_alloc_strategy)
320	}
321
322	/// Heap allocation strategy for calling into `runtime_code` in the given `context`.
323	fn heap_alloc_strategy(
324		&self,
325		runtime_code: &RuntimeCode,
326		context: CallContext,
327	) -> HeapAllocStrategy {
328		match context {
329			CallContext::Offchain => self.default_offchain_heap_alloc_strategy,
330			CallContext::Onchain { import: false } => {
331				self.on_chain_heap_alloc_strategy(runtime_code)
332			},
333			CallContext::Onchain { import: true } => {
334				self.on_chain_heap_alloc_strategy(runtime_code).double()
335			},
336		}
337	}
338
339	/// Execute the given closure `f` with the latest runtime (based on `runtime_code`).
340	///
341	/// The closure `f` is expected to return `Err(_)` when there happened a `panic!` in native code
342	/// while executing the runtime in Wasm. If a `panic!` occurred, the runtime is invalidated to
343	/// prevent any poisoned state. Native runtime execution does not need to report back
344	/// any `panic!`.
345	///
346	/// # Safety
347	///
348	/// `runtime` and `ext` are given as `AssertUnwindSafe` to the closure. As described above, the
349	/// runtime is invalidated on any `panic!` to prevent a poisoned state. `ext` is already
350	/// implicitly handled as unwind safe, as we store it in a global variable while executing the
351	/// native runtime.
352	pub fn with_instance<R, F>(
353		&self,
354		runtime_code: &RuntimeCode,
355		ext: &mut dyn Externalities,
356		heap_alloc_strategy: HeapAllocStrategy,
357		f: F,
358	) -> Result<R>
359	where
360		F: FnOnce(
361			AssertUnwindSafe<&dyn WasmModule>,
362			AssertUnwindSafe<&mut dyn WasmInstance>,
363			Option<&RuntimeVersion>,
364			AssertUnwindSafe<&mut dyn Externalities>,
365		) -> Result<Result<R>>,
366	{
367		match self.cache.with_instance::<H, _, _>(
368			runtime_code,
369			ext,
370			self.method,
371			heap_alloc_strategy,
372			self.allow_missing_host_functions,
373			|module, instance, version, ext| {
374				let module = AssertUnwindSafe(module);
375				let instance = AssertUnwindSafe(instance);
376				let ext = AssertUnwindSafe(ext);
377				f(module, instance, version, ext)
378			},
379		)? {
380			Ok(r) => r,
381			Err(e) => Err(e),
382		}
383	}
384
385	/// Execute the given closure `f` with a fresh instance of the latest runtime (based on
386	/// `runtime_code`) supporting execution timeouts, see [`WasmInstance::call_with_timeout`].
387	///
388	/// May block until the interruptible module is available — it may still be compiling in the
389	/// background.
390	///
391	/// The closure `f` is expected to catch any `panic!` during the call and return it as
392	/// `Err(_)` (see [`with_externalities_safe`]).
393	///
394	/// # Safety
395	///
396	/// `instance` and `ext` are given as `AssertUnwindSafe` to the closure. The instance is fresh
397	/// and dropped after the call, so a `panic!` cannot poison any state. `ext` is already
398	/// implicitly handled as unwind safe, as we store it in a global variable while executing the
399	/// native runtime.
400	fn with_interruptible_instance<R, F>(
401		&self,
402		runtime_code: &RuntimeCode,
403		ext: &mut dyn Externalities,
404		heap_alloc_strategy: HeapAllocStrategy,
405		f: F,
406	) -> Result<R>
407	where
408		F: FnOnce(
409			AssertUnwindSafe<&mut dyn WasmInstance>,
410			Option<&RuntimeVersion>,
411			AssertUnwindSafe<&mut dyn Externalities>,
412		) -> Result<Result<R>>,
413	{
414		match self.cache.with_interruptible_instance::<H, _, _>(
415			runtime_code,
416			ext,
417			self.method,
418			heap_alloc_strategy,
419			self.allow_missing_host_functions,
420			|instance, version, ext| {
421				let instance = AssertUnwindSafe(instance);
422				let ext = AssertUnwindSafe(ext);
423				f(instance, version, ext)
424			},
425		)? {
426			Ok(r) => r,
427			Err(e) => Err(e),
428		}
429	}
430
431	/// Perform a call into the given runtime.
432	///
433	/// The runtime is passed as a [`RuntimeBlob`]. The runtime will be instantiated with the
434	/// parameters this `WasmExecutor` was initialized with.
435	///
436	/// In case of problems with during creation of the runtime or instantiation, a `Err` is
437	/// returned. that describes the message.
438	#[doc(hidden)] // We use this function for tests across multiple crates.
439	pub fn uncached_call(
440		&self,
441		runtime_blob: RuntimeBlob,
442		ext: &mut dyn Externalities,
443		allow_missing_host_functions: bool,
444		export_name: &str,
445		call_data: &[u8],
446	) -> std::result::Result<Vec<u8>, Error> {
447		self.uncached_call_impl(
448			runtime_blob,
449			ext,
450			allow_missing_host_functions,
451			export_name,
452			call_data,
453			&mut None,
454		)
455	}
456
457	/// Same as `uncached_call`, except it also returns allocation statistics.
458	#[doc(hidden)] // We use this function in tests.
459	pub fn uncached_call_with_allocation_stats(
460		&self,
461		runtime_blob: RuntimeBlob,
462		ext: &mut dyn Externalities,
463		allow_missing_host_functions: bool,
464		export_name: &str,
465		call_data: &[u8],
466	) -> (std::result::Result<Vec<u8>, Error>, Option<AllocationStats>) {
467		let mut allocation_stats = None;
468		let result = self.uncached_call_impl(
469			runtime_blob,
470			ext,
471			allow_missing_host_functions,
472			export_name,
473			call_data,
474			&mut allocation_stats,
475		);
476		(result, allocation_stats)
477	}
478
479	fn uncached_call_impl(
480		&self,
481		runtime_blob: RuntimeBlob,
482		ext: &mut dyn Externalities,
483		allow_missing_host_functions: bool,
484		export_name: &str,
485		call_data: &[u8],
486		allocation_stats_out: &mut Option<AllocationStats>,
487	) -> std::result::Result<Vec<u8>, Error> {
488		let module = crate::wasm_runtime::create_wasm_runtime_with_code::<H>(
489			self.method,
490			self.default_onchain_heap_alloc_strategy,
491			runtime_blob,
492			allow_missing_host_functions,
493			self.cache_path.as_deref(),
494		)
495		.map_err(|e| format!("Failed to create module: {}", e))?;
496
497		let instance = module
498			.new_instance(self.default_onchain_heap_alloc_strategy)
499			.map_err(|e| format!("Failed to create instance: {}", e))?;
500
501		let mut instance = AssertUnwindSafe(instance);
502		let mut ext = AssertUnwindSafe(ext);
503		let mut allocation_stats_out = AssertUnwindSafe(allocation_stats_out);
504
505		with_externalities_safe(&mut **ext, move || {
506			let (result, allocation_stats) =
507				instance.call_with_allocation_stats(export_name.into(), call_data);
508			**allocation_stats_out = allocation_stats;
509			result
510		})
511		.and_then(|r| r)
512	}
513}
514
515impl<H> sp_core::traits::ReadRuntimeVersion for WasmExecutor<H>
516where
517	H: HostFunctions,
518{
519	fn read_runtime_version(
520		&self,
521		wasm_code: &[u8],
522		ext: &mut dyn Externalities,
523	) -> std::result::Result<Vec<u8>, String> {
524		let runtime_blob = RuntimeBlob::uncompress_if_needed(wasm_code)
525			.map_err(|e| format!("Failed to create runtime blob: {:?}", e))?;
526
527		if let Some(version) = crate::wasm_runtime::read_embedded_version(&runtime_blob)
528			.map_err(|e| format!("Failed to read the static section: {:?}", e))
529			.map(|v| v.map(|v| v.encode()))?
530		{
531			return Ok(version);
532		}
533
534		// If the blob didn't have embedded runtime version section, we fallback to the legacy
535		// way of fetching the version: i.e. instantiating the given instance and calling
536		// `Core_version` on it.
537
538		self.uncached_call(
539			runtime_blob,
540			ext,
541			// If a runtime upgrade introduces new host functions that are not provided by
542			// the node, we should not fail at instantiation. Otherwise nodes that are
543			// updated could run this successfully and it could lead to a storage root
544			// mismatch when importing this block.
545			true,
546			"Core_version",
547			&[],
548		)
549		.map_err(|e| e.to_string())
550	}
551}
552
553impl<H> CodeExecutor for WasmExecutor<H>
554where
555	H: HostFunctions,
556{
557	type Error = Error;
558
559	fn call(
560		&self,
561		ext: &mut dyn Externalities,
562		runtime_code: &RuntimeCode,
563		method: &str,
564		data: &[u8],
565		context: CallContext,
566	) -> (Result<Vec<u8>>, bool) {
567		tracing::trace!(
568			target: "executor",
569			%method,
570			"Executing function",
571		);
572
573		let heap_alloc_strategy = self.heap_alloc_strategy(runtime_code, context);
574
575		let result = self.with_instance(
576			runtime_code,
577			ext,
578			heap_alloc_strategy,
579			|_, mut instance, _on_chain_version, mut ext| {
580				with_externalities_safe(&mut **ext, move || instance.call_export(method, data))
581			},
582		);
583
584		(result, false)
585	}
586
587	/// The error result of [`Error::ExecutionTimeout`] indicates the timeout has been reached.
588	///
589	/// Runs on a fresh, non-pooled instance of the runtime compiled with support for execution
590	/// timeouts. May block until that runtime finishes compiling in the background.
591	fn call_with_execution_timeout(
592		&self,
593		ext: &mut dyn Externalities,
594		runtime_code: &RuntimeCode,
595		method: &str,
596		data: &[u8],
597		context: CallContext,
598		timeout: Duration,
599	) -> Result<Vec<u8>> {
600		tracing::trace!(
601			target: "executor",
602			%method,
603			"Executing function with timeout",
604		);
605
606		let heap_alloc_strategy = self.heap_alloc_strategy(runtime_code, context);
607
608		self.with_interruptible_instance(
609			runtime_code,
610			ext,
611			heap_alloc_strategy,
612			|mut instance, _on_chain_version, mut ext| {
613				with_externalities_safe(&mut **ext, move || {
614					instance.call_with_timeout(method, data, timeout)
615				})
616			},
617		)
618	}
619}
620
621impl<H> RuntimeVersionOf for WasmExecutor<H>
622where
623	H: HostFunctions,
624{
625	fn runtime_version(
626		&self,
627		ext: &mut dyn Externalities,
628		runtime_code: &RuntimeCode,
629	) -> Result<RuntimeVersion> {
630		self.with_instance(
631			runtime_code,
632			ext,
633			self.on_chain_heap_alloc_strategy(runtime_code),
634			|_module, _instance, version, _ext| {
635				Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into())))
636			},
637		)
638	}
639}
640
641#[cfg(test)]
642mod tests {
643	use super::*;
644	use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface};
645	use sp_wasm_interface::ExtendedHostFunctions;
646
647	#[runtime_interface]
648	trait MyInterface {
649		fn say_hello_world(data: PassFatPointerAndRead<&str>) {
650			println!("Hello world from: {}", data);
651		}
652	}
653
654	#[test]
655	fn wasm_executor_registers_custom_interface() {
656		type Hosts = ExtendedHostFunctions<
657			sp_io::SubstrateHostFunctions,
658			(my_interface::HostFunctions, my_interface::HostFunctions),
659		>;
660
661		let executor = WasmExecutor::<Hosts>::builder().build();
662
663		fn extract_host_functions<H>(
664			_: &WasmExecutor<H>,
665		) -> Vec<&'static dyn sp_wasm_interface::Function>
666		where
667			H: HostFunctions,
668		{
669			H::host_functions()
670		}
671
672		my_interface::HostFunctions::host_functions().iter().for_each(|function| {
673			assert_eq!(
674				extract_host_functions(&executor).iter().filter(|f| f == &function).count(),
675				2
676			);
677		});
678
679		my_interface::say_hello_world("hey");
680	}
681}