ink_env/engine/off_chain/
impls.rs

1// Copyright (C) Use Ink (UK) Ltd.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use ink_engine::ext::Engine;
16use ink_primitives::{
17    Address,
18    H256,
19    SolEncode,
20    U256,
21    abi::{
22        AbiEncodeWith,
23        Ink,
24        Sol,
25    },
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
78/// The capacity of the static buffer.
79/// This is the same size as the ink! on-chain environment. We chose to use the same size
80/// to be as close to the on-chain behavior as possible.
81const BUFFER_SIZE: usize = crate::BUFFER_SIZE;
82
83/// Proxy function used to simulate code hash and to invoke contract methods.
84fn 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    // TODO: (@davidsemakula) Track return flag and set `did_revert` as appropriate.
135    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    // TODO: (@davidsemakula) Track return flag and set `did_revert` as appropriate.
160    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        // For the engine (simulation of `pallet-revive` in `std`) we skip on the buffer
186        // in the implementation for simplicity.
187        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        // For the engine (simulation of `pallet-revive` in `std`) we skip on the buffer
208        // in the implementation for simplicity.
209        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    /// Returns the contract property value.
315    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> SolEncode<'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)] // todo
454    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        // In most implementations, the v is just 0 or 1 internally, but 27 was added
470        // as an arbitrary number for signing Bitcoin messages and Ethereum adopted that
471        // as well.
472        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        // the context associated with the signing (specific to the sr25519 algorithm)
519        // defaults to "substrate" in substrate, but could be different elsewhere
520        // https://github.com/paritytech/substrate/blob/c32f5ed2ae6746d6f791f08cecbfc22fa188f5f9/primitives/core/src/sr25519.rs#L60
521        let context = b"substrate";
522        // attempt to parse a signature from bytes
523        let signature: Signature = Signature::from_bytes(signature)
524            .map_err(|_| ReturnErrorCode::Sr25519VerifyFailed)?;
525        // attempt to parse a public key from bytes
526        let public_key: PublicKey = PublicKey::from_bytes(pub_key)
527            .map_err(|_| ReturnErrorCode::Sr25519VerifyFailed)?;
528        // verify the signature
529        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 account_id<E: Environment>(&mut self) -> E::AccountId {
564        // todo should not use `Engine::account_id`
565        self.get_property::<E::AccountId>(Engine::address)
566            .unwrap_or_else(|error| {
567                panic!("could not read `account_id` property: {error:?}")
568            })
569    }
570
571    fn address(&mut self) -> Address {
572        self.get_property::<Address>(Engine::address)
573            .unwrap_or_else(|error| {
574                panic!("could not read `account_id` property: {error:?}")
575            })
576    }
577
578    fn balance(&mut self) -> U256 {
579        self.get_property::<U256>(Engine::balance)
580            .unwrap_or_else(|error| {
581                panic!("could not read `balance` property: {error:?}")
582            })
583    }
584
585    fn block_number<E: Environment>(&mut self) -> E::BlockNumber {
586        self.get_property::<E::BlockNumber>(Engine::block_number)
587            .unwrap_or_else(|error| {
588                panic!("could not read `block_number` property: {error:?}")
589            })
590    }
591
592    fn minimum_balance(&mut self) -> U256 {
593        self.get_property::<U256>(Engine::minimum_balance)
594            .unwrap_or_else(|error| {
595                panic!("could not read `minimum_balance` property: {error:?}")
596            })
597    }
598
599    fn emit_event<Evt, Abi>(&mut self, event: &Evt)
600    where
601        Evt: Event<Abi>,
602        Abi: TopicEncoder,
603    {
604        let builder = TopicsBuilder::default();
605        let enc_topics = event.topics(builder.into());
606        let enc_data = event.encode_data();
607        self.engine
608            .deposit_event(&enc_topics[..], enc_data.as_slice());
609    }
610
611    fn invoke_contract<E, Args, R, Abi>(
612        &mut self,
613        params: &CallParams<E, Call, Args, R, Abi>,
614    ) -> Result<ink_primitives::MessageResult<R>>
615    where
616        E: Environment,
617        Args: AbiEncodeWith<Abi>,
618        R: DecodeMessageResult<Abi>,
619    {
620        let call_flags = params.call_flags().bits();
621        let transferred_value = params.transferred_value();
622        let input = params.exec_input().encode();
623        let callee_account = params.callee();
624
625        invoke_contract_impl::<R, Abi>(
626            self,
627            None,
628            call_flags,
629            Some(transferred_value),
630            *callee_account, // todo possibly return no reference from callee()
631            input,
632        )
633    }
634
635    fn invoke_contract_delegate<E, Args, R, Abi>(
636        &mut self,
637        params: &CallParams<E, DelegateCall, Args, R, Abi>,
638    ) -> Result<ink_primitives::MessageResult<R>>
639    where
640        E: Environment,
641        Args: AbiEncodeWith<Abi>,
642        R: DecodeMessageResult<Abi>,
643    {
644        let _addr = params.address(); // todo remove
645        let call_flags = params.call_flags().bits();
646        let input = params.exec_input().encode();
647
648        invoke_contract_impl_delegate::<R, Abi>(
649            self,
650            None,
651            call_flags,
652            None,
653            *params.address(),
654            input,
655        )
656    }
657
658    fn instantiate_contract<E, ContractRef, Args, R, Abi>(
659        &mut self,
660        params: &CreateParams<E, ContractRef, LimitParamsV2, Args, R, Abi>,
661    ) -> Result<
662        ink_primitives::ConstructorResult<
663            <R as ConstructorReturnType<ContractRef, Abi>>::Output,
664        >,
665    >
666    where
667        E: Environment,
668        ContractRef: FromAddr + crate::ContractReverseReference,
669        <ContractRef as crate::ContractReverseReference>::Type:
670            crate::reflect::ContractConstructorDecoder,
671        Args: AbiEncodeWith<Abi>,
672        R: ConstructorReturnType<ContractRef, Abi>,
673    {
674        let endowment = params.endowment();
675        let salt_bytes = params.salt_bytes();
676        let code_hash = params.code_hash();
677
678        let input = params.exec_input().encode();
679
680        // Compute address for instantiated contract.
681        let account_id_vec = {
682            let mut account_input = Vec::<u8>::new();
683            account_input.extend(&b"contract_addr".to_vec());
684            scale::Encode::encode_to(
685                &self.engine.exec_context.caller.as_bytes(),
686                &mut account_input,
687            );
688            account_input.extend(&code_hash.0);
689            account_input.extend(&input);
690            if let Some(salt) = salt_bytes {
691                account_input.extend(salt);
692            }
693            let mut account_id = [0_u8; 32];
694            ink_engine::hashing::blake2b_256(&account_input[..], &mut account_id);
695            account_id.to_vec()
696        };
697        // todo don't convert to vec and back, simplify type
698        let contract_addr = AccountIdMapper::to_address(&account_id_vec[..]);
699
700        let old_callee = self.engine.get_callee();
701        self.engine.set_callee(contract_addr);
702
703        let dispatch = <
704            <
705                <
706                    ContractRef
707                    as crate::ContractReverseReference
708                >::Type
709                as crate::reflect::ContractConstructorDecoder
710            >::Type
711            as DecodeDispatch
712        >::decode_dispatch(&mut &input[..])
713            .unwrap_or_else(|e| panic!("Failed to decode constructor call: {e:?}"));
714        crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch)
715            .unwrap_or_else(|e| panic!("Constructor call failed: {e:?}"));
716
717        self.engine
718            .database
719            .set_code_hash(&self.engine.get_callee(), code_hash);
720        self.engine.set_contract(callee());
721        self.engine
722            .database
723            // todo passing the types instead of refs would be better
724            .set_balance(&callee(), *endowment);
725
726        // todo why?
727        self.engine.set_callee(old_callee);
728
729        Ok(Ok(R::ok(<ContractRef as FromAddr>::from_addr(
730            contract_addr,
731        ))))
732    }
733
734    #[cfg(feature = "unstable-hostfn")]
735    fn terminate_contract(&mut self, beneficiary: Address) -> ! {
736        self.engine.terminate(beneficiary)
737    }
738
739    fn transfer<E>(&mut self, destination: Address, value: U256) -> Result<()>
740    where
741        E: Environment,
742    {
743        let enc_value = &scale::Encode::encode(&value)[..];
744        self.engine
745            .transfer(destination, enc_value)
746            .map_err(Into::into)
747    }
748
749    fn weight_to_fee<E: Environment>(&mut self, gas: u64) -> E::Balance {
750        let mut output: [u8; BUFFER_SIZE] = [0; BUFFER_SIZE];
751        self.engine.weight_to_fee(gas, &mut &mut output[..]);
752        scale::Decode::decode(&mut &output[..]).unwrap_or_else(|error| {
753            panic!("could not read `weight_to_fee` property: {error:?}")
754        })
755    }
756
757    #[cfg(feature = "unstable-hostfn")]
758    fn is_contract(&mut self, account: &Address) -> bool {
759        self.engine.is_contract(account)
760    }
761
762    fn caller_is_origin<E>(&mut self) -> bool
763    where
764        E: Environment,
765    {
766        unimplemented!("off-chain environment does not support cross-contract calls")
767    }
768
769    fn caller_is_root<E>(&mut self) -> bool
770    where
771        E: Environment,
772    {
773        unimplemented!("off-chain environment does not support `caller_is_root`")
774    }
775
776    fn code_hash(&mut self, addr: &Address) -> Result<H256> {
777        let code_hash = self.engine.database.get_code_hash(addr);
778        if let Some(code_hash) = code_hash {
779            // todo
780            let code_hash = H256::decode(&mut &code_hash[..]).unwrap();
781            Ok(code_hash)
782        } else {
783            Err(ReturnErrorCode::KeyNotFound.into())
784        }
785    }
786
787    fn own_code_hash(&mut self) -> Result<H256> {
788        let callee = &self.engine.get_callee();
789        let code_hash = self.engine.database.get_code_hash(callee);
790        if let Some(code_hash) = code_hash {
791            Ok(code_hash)
792        } else {
793            Err(ReturnErrorCode::KeyNotFound.into())
794        }
795    }
796
797    #[cfg(feature = "unstable-hostfn")]
798    fn xcm_execute<E, Call>(&mut self, _msg: &xcm::VersionedXcm<Call>) -> Result<()>
799    where
800        E: Environment,
801    {
802        unimplemented!("off-chain environment does not support `xcm_execute`")
803    }
804
805    #[cfg(feature = "unstable-hostfn")]
806    fn xcm_send<E, Call>(
807        &mut self,
808        _dest: &xcm::VersionedLocation,
809        _msg: &xcm::VersionedXcm<Call>,
810    ) -> Result<xcm::v4::XcmHash>
811    where
812        E: Environment,
813    {
814        unimplemented!("off-chain environment does not support `xcm_send`")
815    }
816}