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