ab_contracts_executor/
lib.rs

1#![feature(ptr_as_ref_unchecked)]
2
3mod context;
4
5use crate::context::{MethodDetails, NativeExecutorContext};
6use ab_contracts_common::env::{Env, EnvState, MethodContext, Transaction, TransactionSlot};
7use ab_contracts_common::metadata::decode::{MetadataDecoder, MetadataDecodingError, MetadataItem};
8use ab_contracts_common::method::MethodFingerprint;
9use ab_contracts_common::{
10    Address, Contract, ContractError, ContractTrait, ContractTraitDefinition,
11    NativeExecutorContactMethod, ShardIndex,
12};
13use ab_contracts_io_type::variable_bytes::VariableBytes;
14use ab_contracts_io_type::variable_elements::VariableElements;
15use ab_contracts_slots::aligned_buffer::SharedAlignedBuffer;
16use ab_contracts_slots::slots::{SlotKey, Slots};
17use ab_contracts_standards::tx_handler::TxHandlerExt;
18use ab_system_contract_address_allocator::{AddressAllocator, AddressAllocatorExt};
19use ab_system_contract_code::{Code, CodeExt};
20use ab_system_contract_simple_wallet_base::SimpleWalletBase;
21use ab_system_contract_state::State;
22use halfbrown::HashMap;
23use tracing::error;
24
25/// Native executor errors
26#[derive(Debug, thiserror::Error)]
27pub enum NativeExecutorError {
28    /// Contract metadata not found
29    #[error("Contract metadata not found")]
30    ContractMetadataNotFound,
31    /// Contract metadata decoding error
32    #[error("Contract metadata decoding error: {error}")]
33    ContractMetadataDecodingError {
34        error: MetadataDecodingError<'static>,
35    },
36    /// Expected contract metadata, found trait
37    #[error("Expected contract metadata, found trait")]
38    ExpectedContractMetadataFoundTrait,
39    /// Duplicate method in contract
40    #[error("Duplicate method fingerprint {method_fingerprint} for contract code {contact_code}")]
41    DuplicateMethodInContract {
42        /// Name of the crate in which method was duplicated
43        contact_code: &'static str,
44        /// Method fingerprint
45        method_fingerprint: &'static MethodFingerprint,
46    },
47}
48
49#[derive(Debug, Clone)]
50struct MethodsEntry {
51    contact_code: &'static str,
52    main_contract_metadata: &'static [u8],
53    native_executor_methods: &'static [NativeExecutorContactMethod],
54}
55
56/// Builder for [`NativeExecutor`]
57#[derive(Debug, Clone)]
58pub struct NativeExecutorBuilder {
59    shard_index: ShardIndex,
60    methods: Vec<MethodsEntry>,
61}
62
63impl NativeExecutorBuilder {
64    fn new(shard_index: ShardIndex) -> Self {
65        Self {
66            shard_index,
67            // Start with system contracts
68            methods: vec![
69                MethodsEntry {
70                    contact_code: AddressAllocator::CODE,
71                    main_contract_metadata: AddressAllocator::MAIN_CONTRACT_METADATA,
72                    native_executor_methods: AddressAllocator::NATIVE_EXECUTOR_METHODS,
73                },
74                MethodsEntry {
75                    contact_code: Code::CODE,
76                    main_contract_metadata: Code::MAIN_CONTRACT_METADATA,
77                    native_executor_methods: Code::NATIVE_EXECUTOR_METHODS,
78                },
79                MethodsEntry {
80                    contact_code: State::CODE,
81                    main_contract_metadata: State::MAIN_CONTRACT_METADATA,
82                    native_executor_methods: State::NATIVE_EXECUTOR_METHODS,
83                },
84                MethodsEntry {
85                    contact_code: SimpleWalletBase::CODE,
86                    main_contract_metadata: SimpleWalletBase::MAIN_CONTRACT_METADATA,
87                    native_executor_methods: SimpleWalletBase::NATIVE_EXECUTOR_METHODS,
88                },
89            ],
90        }
91    }
92
93    /// Make the native execution environment aware of the contract specified in generic argument.'
94    ///
95    /// Here `C` is the contract type:
96    /// ```ignore
97    /// # fn foo(mut builder: NativeExecutorBuilder) -> NativeExecutorBuilder {
98    ///     builder.with_contract::<Flipper>()
99    /// # }
100    /// ```
101    ///
102    /// NOTE: System contracts are already included by default.
103    #[must_use]
104    pub fn with_contract<C>(mut self) -> Self
105    where
106        C: Contract,
107    {
108        self.methods.push(MethodsEntry {
109            contact_code: C::CODE,
110            main_contract_metadata: C::MAIN_CONTRACT_METADATA,
111            native_executor_methods: C::NATIVE_EXECUTOR_METHODS,
112        });
113        self
114    }
115
116    /// Make the native execution environment aware of the trait implemented by the contract
117    /// specified in generic argument.
118    ///
119    /// Here `C` is the contract type and `DynCT` is a trait it implements in the form of
120    /// `dyn ContractTrait`:
121    /// ```ignore
122    /// # fn foo(mut builder: NativeExecutorBuilder) -> NativeExecutorBuilder {
123    ///     builder
124    ///         .with_contract::<Token>()
125    ///         .with_contract_trait::<Token, dyn Fungible>()
126    /// # }
127    /// ```
128    #[must_use]
129    pub fn with_contract_trait<C, DynCT>(mut self) -> Self
130    where
131        C: Contract + ContractTrait<DynCT>,
132        DynCT: ContractTraitDefinition + ?Sized,
133    {
134        self.methods.push(MethodsEntry {
135            contact_code: C::CODE,
136            main_contract_metadata: C::MAIN_CONTRACT_METADATA,
137            native_executor_methods: <C as ContractTrait<DynCT>>::NATIVE_EXECUTOR_METHODS,
138        });
139        self
140    }
141
142    /// Build native execution configuration
143    pub fn build(self) -> Result<NativeExecutor, NativeExecutorError> {
144        // 10 is a decent capacity for many typical cases without reallocation
145        let mut methods_by_code = HashMap::with_capacity(10);
146        for methods_entry in self.methods {
147            let MethodsEntry {
148                contact_code,
149                main_contract_metadata,
150                native_executor_methods,
151            } = methods_entry;
152            for &native_executor_method in native_executor_methods {
153                let NativeExecutorContactMethod {
154                    method_fingerprint,
155                    method_metadata,
156                    ffi_fn,
157                } = native_executor_method;
158                let recommended_capacities = match MetadataDecoder::new(main_contract_metadata)
159                    .decode_next()
160                    .ok_or(NativeExecutorError::ContractMetadataNotFound)?
161                    .map_err(|error| NativeExecutorError::ContractMetadataDecodingError { error })?
162                {
163                    MetadataItem::Contract {
164                        state_type_details,
165                        slot_type_details,
166                        tmp_type_details,
167                        ..
168                    } => (
169                        state_type_details.recommended_capacity,
170                        slot_type_details.recommended_capacity,
171                        tmp_type_details.recommended_capacity,
172                    ),
173                    MetadataItem::Trait { .. } => {
174                        return Err(NativeExecutorError::ExpectedContractMetadataFoundTrait);
175                    }
176                };
177                let (
178                    recommended_state_capacity,
179                    recommended_slot_capacity,
180                    recommended_tmp_capacity,
181                ) = recommended_capacities;
182
183                if methods_by_code
184                    .insert(
185                        (contact_code.as_bytes(), method_fingerprint),
186                        MethodDetails {
187                            recommended_state_capacity,
188                            recommended_slot_capacity,
189                            recommended_tmp_capacity,
190                            method_metadata,
191                            ffi_fn,
192                        },
193                    )
194                    .is_some()
195                {
196                    return Err(NativeExecutorError::DuplicateMethodInContract {
197                        contact_code,
198                        method_fingerprint,
199                    });
200                }
201            }
202        }
203
204        Ok(NativeExecutor {
205            shard_index: self.shard_index,
206            methods_by_code,
207        })
208    }
209}
210
211// TODO: Some kind of transaction notion with `#[tmp]` wiped at the end of it
212#[derive(Debug)]
213pub struct NativeExecutor {
214    shard_index: ShardIndex,
215    /// Indexed by contract's code and method fingerprint
216    methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
217}
218
219impl NativeExecutor {
220    /// Create a new [`Slots`] instance with system contracts already deployed
221    pub fn new_storage_slots(&self) -> Result<Slots<'static>, ContractError> {
222        // Manually deploy code of system code contract
223        let slots = [(
224            SlotKey {
225                owner: Address::SYSTEM_CODE,
226                contract: Address::SYSTEM_CODE,
227            },
228            SharedAlignedBuffer::from_bytes(Code::code().get_initialized()),
229        )];
230
231        let address_allocator_address = Address::system_address_allocator(self.shard_index);
232        let mut slots = Slots::new(slots);
233
234        {
235            let mut nested_slots = slots
236                .new_nested_rw()
237                .expect("Just created, hence not read-only; qed");
238            // Allow deployment of system contracts
239            assert!(nested_slots.add_new_contract(address_allocator_address));
240            assert!(nested_slots.add_new_contract(Address::SYSTEM_STATE));
241            assert!(nested_slots.add_new_contract(Address::SYSTEM_SIMPLE_WALLET_BASE));
242        }
243
244        // Deploy and initialize other system contacts
245        let maybe_result = self.transaction_emulate(Address::SYSTEM_CODE, &mut slots, |env| {
246            env.code_store(
247                MethodContext::Keep,
248                Address::SYSTEM_CODE,
249                &Address::SYSTEM_STATE,
250                &State::code(),
251            )?;
252
253            env.code_store(
254                MethodContext::Keep,
255                Address::SYSTEM_CODE,
256                &address_allocator_address,
257                &AddressAllocator::code(),
258            )?;
259            env.address_allocator_new(MethodContext::Keep, address_allocator_address)?;
260
261            env.code_store(
262                MethodContext::Keep,
263                Address::SYSTEM_CODE,
264                &Address::SYSTEM_SIMPLE_WALLET_BASE,
265                &SimpleWalletBase::code(),
266            )
267        });
268        maybe_result.expect("Slots instance is not read-only; qed")?;
269
270        Ok(slots)
271    }
272
273    /// Builder of native executor for specified shard index
274    #[must_use]
275    pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
276        NativeExecutorBuilder::new(shard_index)
277    }
278
279    /// Verify the provided transaction.
280    ///
281    /// [`Self::transaction_execute()`] can be used for transaction execution if needed.
282    /// [`Self::transaction_verify_execute()`] can be used to verify and execute a transaction with
283    /// a single call.
284    pub fn transaction_verify(
285        &self,
286        transaction: Transaction<'_>,
287        slots: &Slots<'_>,
288    ) -> Result<(), ContractError> {
289        let env_state = EnvState {
290            shard_index: self.shard_index,
291            padding_0: Default::default(),
292            own_address: Address::NULL,
293            context: Address::NULL,
294            caller: Address::NULL,
295        };
296
297        let read_slots_size =
298            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
299                .map_err(|_error| ContractError::BadInput)?;
300        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
301
302        let write_slots_size =
303            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
304                .map_err(|_error| ContractError::BadInput)?;
305        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
306
307        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
308            .map_err(|_error| ContractError::BadInput)?;
309        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
310
311        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
312            .map_err(|_error| ContractError::BadInput)?;
313        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
314
315        let mut executor_context = NativeExecutorContext::new(
316            self.shard_index,
317            &self.methods_by_code,
318            slots.new_nested_ro(),
319            false,
320        );
321        let env = Env::with_executor_context(env_state, &mut executor_context);
322        env.tx_handler_authorize(
323            transaction.header.contract,
324            transaction.header,
325            &read_slots,
326            &write_slots,
327            &payload,
328            &seal,
329        )
330    }
331
332    /// Execute the previously verified transaction.
333    ///
334    /// [`Self::transaction_verify()`] must be used for verification.
335    /// [`Self::transaction_verify_execute()`] can be used to verify and execute a transaction with
336    /// a single call.
337    pub fn transaction_execute(
338        &self,
339        transaction: Transaction<'_>,
340        slots: &mut Slots<'_>,
341    ) -> Result<(), ContractError> {
342        // TODO: This is a pretty large data structure to copy around, try to make it a reference
343        let env_state = EnvState {
344            shard_index: self.shard_index,
345            padding_0: Default::default(),
346            own_address: Address::NULL,
347            context: Address::NULL,
348            caller: Address::NULL,
349        };
350
351        let read_slots_size =
352            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
353                .map_err(|_error| ContractError::BadInput)?;
354        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
355
356        let write_slots_size =
357            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
358                .map_err(|_error| ContractError::BadInput)?;
359        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
360
361        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
362            .map_err(|_error| ContractError::BadInput)?;
363        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
364
365        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
366            .map_err(|_error| ContractError::BadInput)?;
367        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
368
369        let mut executor_context = NativeExecutorContext::new(
370            self.shard_index,
371            &self.methods_by_code,
372            slots.new_nested_rw().ok_or_else(|| {
373                error!("Trying to execute a transaction with read-only `Slots` instance #1");
374
375                ContractError::Forbidden
376            })?,
377            true,
378        );
379        let mut env = Env::with_executor_context(env_state, &mut executor_context);
380        env.tx_handler_execute(
381            MethodContext::Reset,
382            transaction.header.contract,
383            transaction.header,
384            &read_slots,
385            &write_slots,
386            &payload,
387            &seal,
388        )
389    }
390
391    /// Verify and execute provided transaction.
392    ///
393    /// A shortcut for [`Self::transaction_verify()`] + [`Self::transaction_execute()`].
394    pub fn transaction_verify_execute(
395        &self,
396        transaction: Transaction<'_>,
397        slots: &mut Slots<'_>,
398    ) -> Result<(), ContractError> {
399        // TODO: This is a pretty large data structure to copy around, try to make it a reference
400        let env_state = EnvState {
401            shard_index: self.shard_index,
402            padding_0: Default::default(),
403            own_address: Address::NULL,
404            context: Address::NULL,
405            caller: Address::NULL,
406        };
407
408        let read_slots_size =
409            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
410                .map_err(|_error| ContractError::BadInput)?;
411        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
412
413        let write_slots_size =
414            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
415                .map_err(|_error| ContractError::BadInput)?;
416        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
417
418        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
419            .map_err(|_error| ContractError::BadInput)?;
420        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
421
422        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
423            .map_err(|_error| ContractError::BadInput)?;
424        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
425
426        // TODO: Make it more efficient by not recreating NativeExecutorContext twice here
427        {
428            let mut executor_context = NativeExecutorContext::new(
429                self.shard_index,
430                &self.methods_by_code,
431                slots.new_nested_ro(),
432                false,
433            );
434            let env = Env::with_executor_context(env_state, &mut executor_context);
435            env.tx_handler_authorize(
436                transaction.header.contract,
437                transaction.header,
438                &read_slots,
439                &write_slots,
440                &payload,
441                &seal,
442            )?;
443        }
444
445        {
446            let mut executor_context = NativeExecutorContext::new(
447                self.shard_index,
448                &self.methods_by_code,
449                slots.new_nested_rw().ok_or_else(|| {
450                    error!("Trying to execute a transaction with read-only `Slots` instance #2");
451
452                    ContractError::Forbidden
453                })?,
454                true,
455            );
456            let mut env = Env::with_executor_context(env_state, &mut executor_context);
457            env.tx_handler_execute(
458                MethodContext::Reset,
459                transaction.header.contract,
460                transaction.header,
461                &read_slots,
462                &write_slots,
463                &payload,
464                &seal,
465            )?;
466        }
467
468        Ok(())
469    }
470
471    /// Emulate a transaction submitted by `contract` with method calls happening inside `calls`
472    /// without going through `TxHandler`.
473    ///
474    /// NOTE: This is primarily useful for testing environment, usually changes are done in the
475    /// transaction execution using [`Self::transaction_execute()`].
476    ///
477    /// Returns `None` if read-only [`Slots`] instance was given.
478    #[must_use]
479    pub fn transaction_emulate<Calls, T>(
480        &self,
481        contract: Address,
482        slots: &mut Slots<'_>,
483        calls: Calls,
484    ) -> Option<T>
485    where
486        Calls: FnOnce(&mut Env<'_>) -> T,
487    {
488        let env_state = EnvState {
489            shard_index: self.shard_index,
490            padding_0: Default::default(),
491            own_address: contract,
492            context: contract,
493            caller: Address::NULL,
494        };
495
496        let mut executor_context = NativeExecutorContext::new(
497            self.shard_index,
498            &self.methods_by_code,
499            slots.new_nested_rw()?,
500            true,
501        );
502        let mut env = Env::with_executor_context(env_state, &mut executor_context);
503        Some(calls(&mut env))
504    }
505
506    /// Get a read-only `Env` instance for calling `#[view]` methods on it directly.
507    ///
508    /// For stateful methods, execute a transaction using [`Self::transaction_execute()`] or
509    /// emulate one with [`Self::transaction_emulate()`].
510    #[must_use]
511    pub fn with_env_ro<Callback, T>(&self, slots: &Slots<'_>, callback: Callback) -> T
512    where
513        Callback: FnOnce(&Env<'_>) -> T,
514    {
515        let env_state = EnvState {
516            shard_index: self.shard_index,
517            padding_0: Default::default(),
518            own_address: Address::NULL,
519            context: Address::NULL,
520            caller: Address::NULL,
521        };
522
523        let mut executor_context = NativeExecutorContext::new(
524            self.shard_index,
525            &self.methods_by_code,
526            slots.new_nested_ro(),
527            false,
528        );
529        let env = Env::with_executor_context(env_state, &mut executor_context);
530        callback(&env)
531    }
532}