1use ink_engine::ext::Engine;
16use ink_primitives::{
17 Address,
18 CodeHashErr,
19 H256,
20 U256,
21 abi::{
22 AbiEncodeWith,
23 Ink,
24 Sol,
25 },
26 sol::SolResultEncode,
27 types::{
28 AccountIdMapper,
29 Environment,
30 },
31};
32use ink_storage_traits::{
33 Storable,
34 decode_all,
35};
36use pallet_revive_uapi::{
37 ReturnErrorCode,
38 ReturnFlags,
39};
40#[cfg(feature = "unstable-hostfn")]
41use schnorrkel::{
42 PublicKey,
43 Signature,
44};
45
46use super::EnvInstance;
47use crate::{
48 DecodeDispatch,
49 DispatchError,
50 EnvBackend,
51 Result,
52 TypedEnvBackend,
53 call::{
54 Call,
55 CallParams,
56 ConstructorReturnType,
57 CreateParams,
58 DelegateCall,
59 FromAddr,
60 LimitParamsV2,
61 utils::{
62 DecodeMessageResult,
63 EncodeArgsWith,
64 },
65 },
66 event::{
67 Event,
68 TopicEncoder,
69 TopicsBuilderBackend,
70 },
71 hash::{
72 Blake2x128,
73 Blake2x256,
74 CryptoHash,
75 HashOutput,
76 Keccak256,
77 Sha2x256,
78 },
79 test::callee,
80};
81
82const BUFFER_SIZE: usize = crate::BUFFER_SIZE;
86
87fn execute_contract_call<ContractRef>(input: Vec<u8>) -> Vec<u8>
89where
90 ContractRef: crate::ContractReverseReference,
91 <ContractRef as crate::ContractReverseReference>::Type:
92 crate::reflect::ContractMessageDecoder,
93{
94 let dispatch = <
95 <
96 <
97 ContractRef
98 as crate::ContractReverseReference
99 >::Type
100 as crate::reflect::ContractMessageDecoder
101 >::Type
102 as DecodeDispatch
103 >::decode_dispatch(&mut &input[..])
104 .unwrap_or_else(|e| panic!("Failed to decode constructor call: {e:?}"));
105
106 crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch)
107 .unwrap_or_else(|e| panic!("Message call failed: {e:?}"));
108
109 crate::test::get_return_value()
110}
111
112fn invoke_contract_impl<R, Abi>(
113 env: &mut EnvInstance,
114 _gas_limit: Option<u64>,
115 _call_flags: u32,
116 _transferred_value: Option<&U256>,
117 callee_account: Address,
118 input: Vec<u8>,
119) -> Result<ink_primitives::MessageResult<R>>
120where
121 R: DecodeMessageResult<Abi>,
122{
123 let callee_code_hash = env.code_hash(&callee_account).unwrap_or_else(|err| {
124 panic!("failed getting code hash for {callee_account:?}: {err:?}")
125 });
126
127 let handler = env
128 .engine
129 .database
130 .get_contract_message_handler(&callee_code_hash);
131 let old_callee = env.engine.get_callee();
132 env.engine.set_callee(callee_account);
133
134 let result = handler(input);
135
136 env.engine.set_callee(old_callee);
137
138 R::decode_output(&result, false)
140}
141
142fn invoke_contract_impl_delegate<R, Abi>(
143 env: &mut EnvInstance,
144 _gas_limit: Option<u64>,
145 _call_flags: u32,
146 _transferred_value: Option<&U256>,
147 callee_account: Address,
148 input: Vec<u8>,
149) -> Result<ink_primitives::MessageResult<R>>
150where
151 R: DecodeMessageResult<Abi>,
152{
153 let callee_code_hash = env.code_hash(&callee_account).unwrap_or_else(|err| {
154 panic!("failed getting code hash for {callee_account:?}: {err:?}")
155 });
156
157 let handler = env
158 .engine
159 .database
160 .get_contract_message_handler(&callee_code_hash);
161 let result = handler(input);
162
163 R::decode_output(&result, false)
165}
166
167impl CryptoHash for Blake2x128 {
168 fn hash(input: &[u8], output: &mut <Self as HashOutput>::Type) {
169 type OutputType = [u8; 16];
170 static_assertions::assert_type_eq_all!(
171 <Blake2x128 as HashOutput>::Type,
172 OutputType
173 );
174 let output: &mut OutputType = array_mut_ref!(output, 0, 16);
175 Engine::hash_blake2_128(input, output);
176 }
177
178 fn hash_with_buffer(
179 _input: &[u8],
180 _buffer: &mut [u8],
181 _output: &mut <Self as HashOutput>::Type,
182 ) {
183 unreachable!("not required, `hash` has been implemented.");
184 }
185}
186
187impl CryptoHash for Blake2x256 {
188 fn hash(input: &[u8], output: &mut <Self as HashOutput>::Type) {
189 type OutputType = [u8; 32];
192 static_assertions::assert_type_eq_all!(
193 <Blake2x256 as HashOutput>::Type,
194 OutputType
195 );
196 let output: &mut OutputType = array_mut_ref!(output, 0, 32);
197 Engine::hash_blake2_256(input, output);
198 }
199
200 fn hash_with_buffer(
201 _input: &[u8],
202 _buffer: &mut [u8],
203 _output: &mut <Self as HashOutput>::Type,
204 ) {
205 unreachable!("not required, `hash` has been implemented.");
206 }
207}
208
209impl CryptoHash for Sha2x256 {
210 fn hash(input: &[u8], output: &mut <Self as HashOutput>::Type) {
211 type OutputType = [u8; 32];
214 static_assertions::assert_type_eq_all!(
215 <Sha2x256 as HashOutput>::Type,
216 OutputType
217 );
218 let output: &mut OutputType = array_mut_ref!(output, 0, 32);
219 Engine::hash_sha2_256(input, output);
220 }
221
222 fn hash_with_buffer(
223 _input: &[u8],
224 _buffer: &mut [u8],
225 _output: &mut <Self as HashOutput>::Type,
226 ) {
227 unreachable!("not required, `hash` has been implemented.");
228 }
229}
230
231impl CryptoHash for Keccak256 {
232 fn hash(input: &[u8], output: &mut <Self as HashOutput>::Type) {
233 type OutputType = [u8; 32];
234 static_assertions::assert_type_eq_all!(
235 <Keccak256 as HashOutput>::Type,
236 OutputType
237 );
238 let output: &mut OutputType = array_mut_ref!(output, 0, 32);
239 Engine::hash_keccak_256(input, output);
240 }
241
242 fn hash_with_buffer(
243 _input: &[u8],
244 _buffer: &mut [u8],
245 _output: &mut <Self as HashOutput>::Type,
246 ) {
247 unreachable!("not required, `hash` has been implemented.");
248 }
249}
250
251#[derive(Default)]
252pub struct TopicsBuilder {
253 pub topics: Vec<[u8; 32]>,
254}
255
256impl<Abi> TopicsBuilderBackend<Abi> for TopicsBuilder
257where
258 Abi: TopicEncoder,
259{
260 type Output = Vec<[u8; 32]>;
261
262 fn push_topic<T>(&mut self, topic_value: &T)
263 where
264 T: AbiEncodeWith<Abi>,
265 {
266 let output = <Abi as TopicEncoder>::encode_topic(topic_value);
267 self.topics.push(output);
268 }
269
270 fn output(self) -> Self::Output {
271 self.topics
272 }
273}
274
275impl TopicEncoder for Ink {
276 const REQUIRES_BUFFER: bool = false;
277
278 fn encode_topic<T>(value: &T) -> [u8; 32]
279 where
280 T: AbiEncodeWith<Self>,
281 {
282 value.encode_topic(<Blake2x256 as CryptoHash>::hash)
283 }
284
285 fn encode_topic_with_hash_buffer<T>(
286 _value: &T,
287 _output: &mut [u8; 32],
288 _buffer: &mut [u8],
289 ) where
290 T: AbiEncodeWith<Self>,
291 {
292 unreachable!("not required, `encode_topic` has been implemented.");
293 }
294}
295
296impl TopicEncoder for Sol {
297 const REQUIRES_BUFFER: bool = false;
298
299 fn encode_topic<T>(value: &T) -> [u8; 32]
300 where
301 T: AbiEncodeWith<Self>,
302 {
303 value.encode_topic(<Keccak256 as CryptoHash>::hash)
304 }
305
306 fn encode_topic_with_hash_buffer<T>(
307 _value: &T,
308 _output: &mut [u8; 32],
309 _buffer: &mut [u8],
310 ) where
311 T: AbiEncodeWith<Self>,
312 {
313 unreachable!("not required, `encode_topic` has been implemented.");
314 }
315}
316
317impl EnvInstance {
318 fn get_property<T>(
320 &mut self,
321 ext_fn: fn(engine: &Engine, output: &mut &mut [u8]),
322 ) -> Result<T>
323 where
324 T: scale::Decode,
325 {
326 let mut full_scope: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
327 let full_scope = &mut &mut full_scope[..];
328 ext_fn(&self.engine, full_scope);
329 scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into)
330 }
331
332 pub fn get_return_value(&mut self) -> Vec<u8> {
333 self.engine.get_storage(&[255_u8; 32]).unwrap().to_vec()
334 }
335
336 pub fn upload_code<ContractRef>(&mut self) -> H256
337 where
338 ContractRef: crate::ContractReverseReference,
339 <ContractRef as crate::ContractReverseReference>::Type:
340 crate::reflect::ContractMessageDecoder,
341 {
342 H256::from(
343 self.engine
344 .database
345 .set_contract_message_handler(execute_contract_call::<ContractRef>),
346 )
347 }
348}
349
350impl EnvBackend for EnvInstance {
351 fn set_contract_storage<K, V>(&mut self, key: &K, value: &V) -> Option<u32>
352 where
353 K: scale::Encode,
354 V: Storable,
355 {
356 let mut v = vec![];
357 Storable::encode(value, &mut v);
358 self.engine.set_storage(&key.encode(), &v[..])
359 }
360
361 fn get_contract_storage<K, R>(&mut self, key: &K) -> Result<Option<R>>
362 where
363 K: scale::Encode,
364 R: Storable,
365 {
366 match self.engine.get_storage(&key.encode()) {
367 Ok(res) => {
368 let decoded = decode_all(&mut &res[..])?;
369 Ok(Some(decoded))
370 }
371 Err(ReturnErrorCode::KeyNotFound) => Ok(None),
372 Err(_) => panic!("encountered unexpected error"),
373 }
374 }
375
376 fn take_contract_storage<K, R>(&mut self, key: &K) -> Result<Option<R>>
377 where
378 K: scale::Encode,
379 R: Storable,
380 {
381 match self.engine.take_storage(&key.encode()) {
382 Ok(output) => {
383 let decoded = decode_all(&mut &output[..])?;
384 Ok(Some(decoded))
385 }
386 Err(ReturnErrorCode::KeyNotFound) => Ok(None),
387 Err(_) => panic!("encountered unexpected error"),
388 }
389 }
390
391 fn remaining_buffer(&mut self) -> usize {
392 BUFFER_SIZE
393 }
394
395 fn contains_contract_storage<K>(&mut self, key: &K) -> Option<u32>
396 where
397 K: scale::Encode,
398 {
399 self.engine.contains_storage(&key.encode())
400 }
401
402 fn clear_contract_storage<K>(&mut self, key: &K) -> Option<u32>
403 where
404 K: scale::Encode,
405 {
406 self.engine.clear_storage(&key.encode())
407 }
408
409 fn decode_input<T>(&mut self) -> core::result::Result<T, DispatchError>
410 where
411 T: DecodeDispatch,
412 {
413 unimplemented!("the off-chain env does not implement `input`")
414 }
415
416 #[cfg(not(feature = "std"))]
417 fn return_value<R>(&mut self, _flags: ReturnFlags, _return_value: &R) -> !
418 where
419 R: scale::Encode,
420 {
421 panic!("enable feature `std` to use `return_value()`")
422 }
423
424 #[cfg(feature = "std")]
425 fn return_value<R>(&mut self, _flags: ReturnFlags, return_value: &R)
426 where
427 R: scale::Encode,
428 {
429 let mut v = vec![];
430 return_value.encode_to(&mut v);
431 self.engine.set_storage(&[255_u8; 32], &v[..]);
432 }
433
434 fn return_value_solidity<R>(&mut self, _flags: ReturnFlags, _return_value: &R) -> !
435 where
436 R: for<'a> SolResultEncode<'a>,
437 {
438 unimplemented!("the off-chain env does not implement `return_value_solidity`")
439 }
440
441 fn hash_bytes<H>(&mut self, input: &[u8], output: &mut <H as HashOutput>::Type)
442 where
443 H: CryptoHash,
444 {
445 <H as CryptoHash>::hash(input, output)
446 }
447
448 fn hash_encoded<H, T>(&mut self, input: &T, output: &mut <H as HashOutput>::Type)
449 where
450 H: CryptoHash,
451 T: scale::Encode,
452 {
453 let enc_input = &scale::Encode::encode(input)[..];
454 <H as CryptoHash>::hash(enc_input, output)
455 }
456
457 #[allow(clippy::arithmetic_side_effects)] fn ecdsa_recover(
459 &mut self,
460 signature: &[u8; 65],
461 message_hash: &[u8; 32],
462 output: &mut [u8; 33],
463 ) -> Result<()> {
464 use secp256k1::{
465 Message,
466 SECP256K1,
467 ecdsa::{
468 RecoverableSignature,
469 RecoveryId,
470 },
471 };
472
473 let recovery_byte = if signature[64] > 26 {
477 signature[64] - 27
478 } else {
479 signature[64]
480 };
481 let recovery_id = RecoveryId::try_from(recovery_byte as i32)
482 .unwrap_or_else(|error| panic!("Unable to parse the recovery id: {error}"));
483 let message = Message::from_digest_slice(message_hash).unwrap_or_else(|error| {
484 panic!("Unable to create the message from hash: {error}")
485 });
486 let signature =
487 RecoverableSignature::from_compact(&signature[0..64], recovery_id)
488 .unwrap_or_else(|error| panic!("Unable to parse the signature: {error}"));
489
490 let pub_key = SECP256K1.recover_ecdsa(&message, &signature);
491 match pub_key {
492 Ok(pub_key) => {
493 *output = pub_key.serialize();
494 Ok(())
495 }
496 Err(_) => Err(ReturnErrorCode::EcdsaRecoveryFailed.into()),
497 }
498 }
499
500 #[cfg(feature = "unstable-hostfn")]
501 fn ecdsa_to_eth_address(
502 &mut self,
503 pubkey: &[u8; 33],
504 output: &mut [u8; 20],
505 ) -> Result<()> {
506 let pk = secp256k1::PublicKey::from_slice(pubkey)
507 .map_err(|_| ReturnErrorCode::EcdsaRecoveryFailed)?;
508 let uncompressed = pk.serialize_uncompressed();
509 let mut hash = <Keccak256 as HashOutput>::Type::default();
510 <Keccak256>::hash(&uncompressed[1..], &mut hash);
511 output.as_mut().copy_from_slice(&hash[12..]);
512 Ok(())
513 }
514
515 #[cfg(feature = "unstable-hostfn")]
516 fn sr25519_verify(
517 &mut self,
518 signature: &[u8; 64],
519 message: &[u8],
520 pub_key: &[u8; 32],
521 ) -> Result<()> {
522 let context = b"substrate";
526 let signature: Signature = Signature::from_bytes(signature)
528 .map_err(|_| ReturnErrorCode::Sr25519VerifyFailed)?;
529 let public_key: PublicKey = PublicKey::from_bytes(pub_key)
531 .map_err(|_| ReturnErrorCode::Sr25519VerifyFailed)?;
532 public_key
534 .verify_simple(context, message, &signature)
535 .map_err(|_| ReturnErrorCode::Sr25519VerifyFailed.into())
536 }
537
538 #[cfg(feature = "unstable-hostfn")]
539 fn set_code_hash(&mut self, code_hash: &H256) -> Result<()> {
540 self.engine
541 .database
542 .set_code_hash(&self.engine.get_callee(), code_hash);
543 Ok(())
544 }
545}
546
547impl TypedEnvBackend for EnvInstance {
548 fn caller(&mut self) -> Address {
549 self.get_property::<Address>(Engine::caller)
550 .unwrap_or_else(|error| panic!("could not read `caller` property: {error:?}"))
551 }
552
553 fn transferred_value(&mut self) -> U256 {
554 self.get_property(Engine::value_transferred)
555 .unwrap_or_else(|error| {
556 panic!("could not read `transferred_value` property: {error:?}")
557 })
558 }
559
560 fn block_timestamp<E: Environment>(&mut self) -> E::Timestamp {
561 self.get_property::<E::Timestamp>(Engine::block_timestamp)
562 .unwrap_or_else(|error| {
563 panic!("could not read `block_timestamp` property: {error:?}")
564 })
565 }
566
567 fn to_account_id<E: Environment>(&mut self, addr: Address) -> E::AccountId {
568 let mut full_scope: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
569 let full_scope = &mut &mut full_scope[..];
570 Engine::to_account_id(&self.engine, addr.as_bytes(), full_scope);
571 scale::Decode::decode(&mut &full_scope[..]).unwrap()
572 }
573
574 fn account_id<E: Environment>(&mut self) -> E::AccountId {
575 self.get_property::<E::AccountId>(Engine::account_id)
576 .unwrap_or_else(|error| {
577 panic!("could not read `account_id` property: {error:?}")
578 })
579 }
580
581 fn address(&mut self) -> Address {
582 self.get_property::<Address>(Engine::address)
583 .unwrap_or_else(|error| {
584 panic!("could not read `account_id` property: {error:?}")
585 })
586 }
587
588 fn balance(&mut self) -> U256 {
589 self.get_property::<U256>(Engine::balance)
590 .unwrap_or_else(|error| {
591 panic!("could not read `balance` property: {error:?}")
592 })
593 }
594
595 fn block_number<E: Environment>(&mut self) -> E::BlockNumber {
596 self.get_property::<E::BlockNumber>(Engine::block_number)
597 .unwrap_or_else(|error| {
598 panic!("could not read `block_number` property: {error:?}")
599 })
600 }
601
602 fn minimum_balance(&mut self) -> U256 {
603 self.get_property::<U256>(Engine::minimum_balance)
604 .unwrap_or_else(|error| {
605 panic!("could not read `minimum_balance` property: {error:?}")
606 })
607 }
608
609 fn emit_event<Evt, Abi>(&mut self, event: &Evt)
610 where
611 Evt: Event<Abi>,
612 Abi: TopicEncoder,
613 {
614 let builder = TopicsBuilder::default();
615 let enc_topics = event.topics(builder.into());
616 let enc_data = event.encode_data();
617 self.engine
618 .deposit_event(&enc_topics[..], enc_data.as_slice());
619 }
620
621 fn invoke_contract<E, Args, R, Abi>(
622 &mut self,
623 params: &CallParams<E, Call, Args, R, Abi>,
624 ) -> Result<ink_primitives::MessageResult<R>>
625 where
626 E: Environment,
627 Args: EncodeArgsWith<Abi>,
628 R: DecodeMessageResult<Abi>,
629 {
630 let call_flags = params.call_flags().bits();
631 let transferred_value = params.transferred_value();
632 let input = params.exec_input().encode();
633 let callee_account = params.callee();
634
635 invoke_contract_impl::<R, Abi>(
636 self,
637 None,
638 call_flags,
639 Some(transferred_value),
640 *callee_account, input,
642 )
643 }
644
645 fn invoke_contract_delegate<E, Args, R, Abi>(
646 &mut self,
647 params: &CallParams<E, DelegateCall, Args, R, Abi>,
648 ) -> Result<ink_primitives::MessageResult<R>>
649 where
650 E: Environment,
651 Args: EncodeArgsWith<Abi>,
652 R: DecodeMessageResult<Abi>,
653 {
654 let _addr = params.address(); let call_flags = params.call_flags().bits();
656 let input = params.exec_input().encode();
657
658 invoke_contract_impl_delegate::<R, Abi>(
659 self,
660 None,
661 call_flags,
662 None,
663 *params.address(),
664 input,
665 )
666 }
667
668 fn instantiate_contract<E, ContractRef, Args, R, Abi>(
669 &mut self,
670 params: &CreateParams<E, ContractRef, LimitParamsV2, Args, R, Abi>,
671 ) -> Result<
672 ink_primitives::ConstructorResult<
673 <R as ConstructorReturnType<ContractRef, Abi>>::Output,
674 >,
675 >
676 where
677 E: Environment,
678 ContractRef: FromAddr + crate::ContractReverseReference,
679 <ContractRef as crate::ContractReverseReference>::Type:
680 crate::reflect::ContractConstructorDecoder,
681 Args: EncodeArgsWith<Abi>,
682 R: ConstructorReturnType<ContractRef, Abi>,
683 {
684 let endowment = params.endowment();
685 let salt_bytes = params.salt_bytes();
686 let code_hash = params.code_hash();
687
688 let input = params.exec_input().encode();
689
690 let account_id_vec = {
692 let mut account_input = Vec::<u8>::new();
693 account_input.extend(&b"contract_addr".to_vec());
694 scale::Encode::encode_to(
695 &self.engine.exec_context.caller.as_bytes(),
696 &mut account_input,
697 );
698 account_input.extend(&code_hash.0);
699 account_input.extend(&input);
700 if let Some(salt) = salt_bytes {
701 account_input.extend(salt);
702 }
703 let mut account_id = [0_u8; 32];
704 ink_engine::hashing::blake2b_256(&account_input[..], &mut account_id);
705 account_id.to_vec()
706 };
707 let contract_addr = AccountIdMapper::to_address(&account_id_vec[..]);
709
710 let old_callee = self.engine.get_callee();
711 self.engine.set_callee(contract_addr);
712
713 let dispatch = <
714 <
715 <
716 ContractRef
717 as crate::ContractReverseReference
718 >::Type
719 as crate::reflect::ContractConstructorDecoder
720 >::Type
721 as DecodeDispatch
722 >::decode_dispatch(&mut &input[..])
723 .unwrap_or_else(|e| panic!("Failed to decode constructor call: {e:?}"));
724 crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch)
725 .unwrap_or_else(|e| panic!("Constructor call failed: {e:?}"));
726
727 self.engine
728 .database
729 .set_code_hash(&self.engine.get_callee(), code_hash);
730 self.engine.set_contract(callee());
731 self.engine
732 .database
733 .set_balance(&callee(), *endowment);
735
736 self.engine.set_callee(old_callee);
738
739 Ok(Ok(R::ok(<ContractRef as FromAddr>::from_addr(
740 contract_addr,
741 ))))
742 }
743
744 #[cfg(feature = "unstable-hostfn")]
745 fn terminate_contract(&mut self, beneficiary: Address) -> ! {
746 self.engine.terminate(beneficiary)
747 }
748
749 fn transfer<E>(&mut self, destination: Address, value: U256) -> Result<()>
750 where
751 E: Environment,
752 {
753 let enc_value = &scale::Encode::encode(&value)[..];
754 self.engine
755 .transfer(destination, enc_value)
756 .map_err(Into::into)
757 }
758
759 fn weight_to_fee(&mut self, gas: u64) -> U256 {
760 let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
761 self.engine.weight_to_fee(gas, &mut &mut output[..]);
762 scale::Decode::decode(&mut &output[..]).unwrap_or_else(|error| {
763 panic!("could not read `weight_to_fee` property: {error:?}")
764 })
765 }
766
767 fn is_contract(&mut self, account: &Address) -> bool {
768 self.engine.is_contract(account)
769 }
770
771 fn caller_is_origin(&mut self) -> bool {
772 unimplemented!("off-chain environment does not support cross-contract calls")
773 }
774
775 fn caller_is_root(&mut self) -> bool {
776 unimplemented!("off-chain environment does not support `caller_is_root`")
777 }
778
779 fn code_hash(&mut self, addr: &Address) -> core::result::Result<H256, CodeHashErr> {
780 let code_hash = self.engine.database.get_code_hash(addr);
781 if let Some(code_hash) = code_hash {
782 let code_hash = H256::decode(&mut &code_hash[..]).unwrap();
784 Ok(code_hash)
785 } else {
786 Err(CodeHashErr::AddressNotFound)
787 }
788 }
789
790 fn own_code_hash(&mut self) -> H256 {
791 let callee = &self.engine.get_callee();
792 self.engine
793 .database
794 .get_code_hash(callee)
795 .expect("own code hash not found")
796 }
797
798 #[cfg(all(feature = "xcm", feature = "unstable-hostfn"))]
799 fn xcm_execute<E, Call>(&mut self, _msg: &xcm::VersionedXcm<Call>) -> Result<()>
800 where
801 E: Environment,
802 {
803 unimplemented!("off-chain environment does not support `xcm_execute`")
804 }
805
806 #[cfg(all(feature = "xcm", feature = "unstable-hostfn"))]
807 fn xcm_send<E, Call>(
808 &mut self,
809 _dest: &xcm::VersionedLocation,
810 _msg: &xcm::VersionedXcm<Call>,
811 ) -> Result<xcm::v4::XcmHash>
812 where
813 E: Environment,
814 {
815 unimplemented!("off-chain environment does not support `xcm_send`")
816 }
817}