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