ab_executor_native/
lib.rs

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