ab_executor_native/
lib.rs

1#![feature(
2    generic_arg_infer,
3    non_null_from_ref,
4    ptr_as_ref_unchecked,
5    unsafe_cell_access
6)]
7
8mod context;
9
10use crate::context::{MethodDetails, NativeExecutorContext};
11use ab_aligned_buffer::SharedAlignedBuffer;
12use ab_contracts_common::env::{Env, EnvState, MethodContext};
13use ab_contracts_common::metadata::decode::{MetadataDecoder, MetadataDecodingError, MetadataItem};
14use ab_contracts_common::method::MethodFingerprint;
15use ab_contracts_common::{
16    Contract, ContractError, ContractTrait, ContractTraitDefinition, MAX_CODE_SIZE,
17    NativeExecutorContactMethod,
18};
19use ab_contracts_standards::fungible::Fungible;
20use ab_contracts_standards::tx_handler::TxHandlerExt;
21use ab_core_primitives::address::Address;
22use ab_core_primitives::balance::Balance;
23use ab_core_primitives::shard::ShardIndex;
24use ab_core_primitives::transaction::{Transaction, TransactionHeader, TransactionSlot};
25use ab_executor_slots::{Slot, SlotKey, Slots};
26use ab_io_type::variable_bytes::VariableBytes;
27use ab_io_type::variable_elements::VariableElements;
28use ab_system_contract_address_allocator::{AddressAllocator, AddressAllocatorExt};
29use ab_system_contract_block::{Block, BlockExt};
30use ab_system_contract_code::{Code, CodeExt};
31use ab_system_contract_native_token::{NativeToken, NativeTokenExt};
32use ab_system_contract_simple_wallet_base::SimpleWalletBase;
33use ab_system_contract_state::State;
34use halfbrown::HashMap;
35use tracing::error;
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 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 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#[derive(Debug)]
212pub struct NativeExecutor {
213    shard_index: ShardIndex,
214    /// Indexed by contract's code and method fingerprint
215    methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
216}
217
218impl NativeExecutor {
219    /// Create a new [`Slots`] instance with system contracts already deployed
220    pub fn new_storage_slots(&self) -> Result<Slots, ContractError> {
221        // Manually deploy code of system code contract
222        let slots = [Slot::ReadWrite {
223            key: SlotKey {
224                owner: Address::SYSTEM_CODE,
225                contract: Address::SYSTEM_CODE,
226            },
227            buffer: SharedAlignedBuffer::from_bytes(Code::code().get_initialized()),
228        }];
229
230        let address_allocator_address = Address::system_address_allocator(self.shard_index);
231        let mut slots = Slots::new(slots);
232
233        let system_contracts: [(Address, &VariableBytes<{ MAX_CODE_SIZE }>); _] = [
234            (Address::SYSTEM_STATE, &State::code()),
235            (address_allocator_address, &AddressAllocator::code()),
236            (Address::SYSTEM_BLOCK, &Block::code()),
237            (Address::SYSTEM_NATIVE_TOKEN, &NativeToken::code()),
238            (
239                Address::SYSTEM_SIMPLE_WALLET_BASE,
240                &SimpleWalletBase::code(),
241            ),
242        ];
243
244        {
245            let mut nested_slots = slots.new_nested_rw();
246            // Allow deployment of system contracts
247            for (address, _code) in system_contracts {
248                assert!(nested_slots.add_new_contract(address));
249            }
250        }
251
252        // Deploy and initialize other system contacts
253        self.transaction_emulate(Address::NULL, &mut slots, |env| {
254            for (address, code) in system_contracts {
255                env.code_store(MethodContext::Reset, Address::SYSTEM_CODE, &address, code)?;
256            }
257
258            env.address_allocator_new(MethodContext::Reset, address_allocator_address)?;
259            env.block_genesis(MethodContext::Reset, Address::SYSTEM_BLOCK)?;
260            env.native_token_initialize(
261                MethodContext::Reset,
262                Address::SYSTEM_NATIVE_TOKEN,
263                &Address::SYSTEM_NATIVE_TOKEN,
264                // TODO: The balance should not be `max` by default, define rules and use them
265                &Balance::MAX,
266            )
267        })?;
268
269        Ok(slots)
270    }
271
272    /// Builder of native executor for specified shard index
273    #[must_use]
274    pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
275        NativeExecutorBuilder::new(shard_index)
276    }
277
278    /// Verify the provided transaction.
279    ///
280    /// [`Self::transaction_execute()`] can be used for transaction execution if needed.
281    /// [`Self::transaction_verify_execute()`] can be used to verify and execute a transaction with
282    /// a single call.
283    pub fn transaction_verify(
284        &self,
285        transaction: Transaction<'_>,
286        slots: &Slots,
287    ) -> Result<(), ContractError> {
288        if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
289            return Err(ContractError::BadInput);
290        }
291
292        let env_state = EnvState {
293            shard_index: self.shard_index,
294            padding_0: Default::default(),
295            own_address: Address::NULL,
296            context: Address::NULL,
297            caller: Address::NULL,
298        };
299
300        let read_slots_size =
301            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
302                .map_err(|_error| ContractError::BadInput)?;
303        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
304
305        let write_slots_size =
306            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
307                .map_err(|_error| ContractError::BadInput)?;
308        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
309
310        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
311            .map_err(|_error| ContractError::BadInput)?;
312        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
313
314        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
315            .map_err(|_error| ContractError::BadInput)?;
316        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
317
318        let mut executor_context = NativeExecutorContext::new(
319            self.shard_index,
320            &self.methods_by_code,
321            slots.new_nested_ro(),
322            false,
323        );
324        let env = Env::with_executor_context(env_state, &mut executor_context);
325        env.tx_handler_authorize(
326            transaction.header.contract,
327            transaction.header,
328            &read_slots,
329            &write_slots,
330            &payload,
331            &seal,
332        )
333    }
334
335    /// Execute the previously verified transaction.
336    ///
337    /// [`Self::transaction_verify()`] must be used for verification.
338    /// [`Self::transaction_verify_execute()`] can be used to verify and execute a transaction with
339    /// a single call.
340    pub fn transaction_execute(
341        &self,
342        transaction: Transaction<'_>,
343        slots: &mut Slots,
344    ) -> Result<(), ContractError> {
345        if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
346            return Err(ContractError::BadInput);
347        }
348
349        // TODO: This is a pretty large data structure to copy around, try to make it a reference
350        let env_state = EnvState {
351            shard_index: self.shard_index,
352            padding_0: Default::default(),
353            own_address: Address::NULL,
354            context: Address::NULL,
355            caller: Address::NULL,
356        };
357
358        let read_slots_size =
359            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
360                .map_err(|_error| ContractError::BadInput)?;
361        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
362
363        let write_slots_size =
364            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
365                .map_err(|_error| ContractError::BadInput)?;
366        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
367
368        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
369            .map_err(|_error| ContractError::BadInput)?;
370        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
371
372        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
373            .map_err(|_error| ContractError::BadInput)?;
374        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
375
376        let mut executor_context = NativeExecutorContext::new(
377            self.shard_index,
378            &self.methods_by_code,
379            slots.new_nested_rw(),
380            true,
381        );
382
383        let mut env = Env::with_executor_context(env_state, &mut executor_context);
384        env.tx_handler_execute(
385            MethodContext::Reset,
386            transaction.header.contract,
387            transaction.header,
388            &read_slots,
389            &write_slots,
390            &payload,
391            &seal,
392        )
393    }
394
395    /// Verify and execute provided transaction.
396    ///
397    /// A shortcut for [`Self::transaction_verify()`] + [`Self::transaction_execute()`].
398    pub fn transaction_verify_execute(
399        &self,
400        transaction: Transaction<'_>,
401        slots: &mut Slots,
402    ) -> Result<(), ContractError> {
403        if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
404            return Err(ContractError::BadInput);
405        }
406
407        // TODO: This is a pretty large data structure to copy around, try to make it a reference
408        let env_state = EnvState {
409            shard_index: self.shard_index,
410            padding_0: Default::default(),
411            own_address: Address::NULL,
412            context: Address::NULL,
413            caller: Address::NULL,
414        };
415
416        let read_slots_size =
417            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
418                .map_err(|_error| ContractError::BadInput)?;
419        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
420
421        let write_slots_size =
422            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
423                .map_err(|_error| ContractError::BadInput)?;
424        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
425
426        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
427            .map_err(|_error| ContractError::BadInput)?;
428        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
429
430        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
431            .map_err(|_error| ContractError::BadInput)?;
432        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
433
434        // TODO: Make it more efficient by not recreating NativeExecutorContext twice here
435        {
436            let mut executor_context = NativeExecutorContext::new(
437                self.shard_index,
438                &self.methods_by_code,
439                slots.new_nested_ro(),
440                false,
441            );
442            let env = Env::with_executor_context(env_state, &mut executor_context);
443            env.tx_handler_authorize(
444                transaction.header.contract,
445                transaction.header,
446                &read_slots,
447                &write_slots,
448                &payload,
449                &seal,
450            )?;
451        }
452
453        {
454            let mut executor_context = NativeExecutorContext::new(
455                self.shard_index,
456                &self.methods_by_code,
457                slots.new_nested_rw(),
458                true,
459            );
460            let mut env = Env::with_executor_context(env_state, &mut executor_context);
461            env.tx_handler_execute(
462                MethodContext::Reset,
463                transaction.header.contract,
464                transaction.header,
465                &read_slots,
466                &write_slots,
467                &payload,
468                &seal,
469            )?;
470        }
471
472        Ok(())
473    }
474
475    /// Emulate a transaction submitted by `contract` with method calls happening inside `calls`
476    /// without going through `TxHandler`.
477    ///
478    /// NOTE: This is primarily useful for testing environment, usually changes are done in the
479    /// transaction execution using [`Self::transaction_execute()`].
480    ///
481    /// Returns `None` if read-only [`Slots`] instance was given.
482    pub fn transaction_emulate<Calls, T>(
483        &self,
484        contract: Address,
485        slots: &mut Slots,
486        calls: Calls,
487    ) -> T
488    where
489        Calls: FnOnce(&mut Env<'_>) -> T,
490    {
491        let env_state = EnvState {
492            shard_index: self.shard_index,
493            padding_0: Default::default(),
494            own_address: contract,
495            context: contract,
496            caller: Address::NULL,
497        };
498
499        let mut executor_context = NativeExecutorContext::new(
500            self.shard_index,
501            &self.methods_by_code,
502            slots.new_nested_rw(),
503            true,
504        );
505        let mut env = Env::with_executor_context(env_state, &mut executor_context);
506        calls(&mut env)
507    }
508
509    /// Get a read-only `Env` instance for calling `#[view]` methods on it directly.
510    ///
511    /// For stateful methods, execute a transaction using [`Self::transaction_execute()`] or
512    /// emulate one with [`Self::transaction_emulate()`].
513    #[must_use]
514    pub fn with_env_ro<Callback, T>(&self, slots: &Slots, callback: Callback) -> T
515    where
516        Callback: FnOnce(&Env<'_>) -> T,
517    {
518        let env_state = EnvState {
519            shard_index: self.shard_index,
520            padding_0: Default::default(),
521            own_address: Address::NULL,
522            context: Address::NULL,
523            caller: Address::NULL,
524        };
525
526        let mut executor_context = NativeExecutorContext::new(
527            self.shard_index,
528            &self.methods_by_code,
529            slots.new_nested_ro(),
530            false,
531        );
532        let env = Env::with_executor_context(env_state, &mut executor_context);
533        callback(&env)
534    }
535}