ab_executor_native/
lib.rs

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