ab_executor_native/
lib.rs

1#![feature(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 the 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 the generic
89    /// argument.
90    ///
91    /// Here `C` is the contract type:
92    /// ```ignore
93    /// # fn foo(mut builder: NativeExecutorBuilder) -> NativeExecutorBuilder {
94    ///     builder.with_contract::<Flipper>()
95    /// # }
96    /// ```
97    ///
98    /// NOTE: System contracts are already included by default.
99    #[must_use]
100    pub fn with_contract<C>(mut self) -> Self
101    where
102        C: Contract,
103    {
104        self.methods.push(MethodsEntry {
105            contact_code: C::CODE,
106            main_contract_metadata: C::MAIN_CONTRACT_METADATA,
107            native_executor_methods: C::NATIVE_EXECUTOR_METHODS,
108        });
109        self
110    }
111
112    /// Make the native execution environment aware of the trait implemented by the contract
113    /// specified in the generic argument.
114    ///
115    /// Here `C` is the contract type and `DynCT` is a trait it implements in the form of
116    /// `dyn ContractTrait`:
117    /// ```ignore
118    /// # fn foo(mut builder: NativeExecutorBuilder) -> NativeExecutorBuilder {
119    ///     builder
120    ///         .with_contract::<Token>()
121    ///         .with_contract_trait::<Token, dyn Fungible>()
122    /// # }
123    /// ```
124    #[must_use]
125    pub fn with_contract_trait<C, DynCT>(mut self) -> Self
126    where
127        C: Contract + ContractTrait<DynCT>,
128        DynCT: ContractTraitDefinition + ?Sized,
129    {
130        self.methods.push(MethodsEntry {
131            contact_code: C::CODE,
132            main_contract_metadata: C::MAIN_CONTRACT_METADATA,
133            native_executor_methods: <C as ContractTrait<DynCT>>::NATIVE_EXECUTOR_METHODS,
134        });
135        self
136    }
137
138    /// Build native execution configuration
139    pub fn build(self) -> Result<NativeExecutor, NativeExecutorError> {
140        // 10 is a decent capacity for many typical cases without reallocation
141        let mut methods_by_code = HashMap::with_capacity(10);
142        for methods_entry in self.methods {
143            let MethodsEntry {
144                contact_code,
145                main_contract_metadata,
146                native_executor_methods,
147            } = methods_entry;
148            for &native_executor_method in native_executor_methods {
149                let NativeExecutorContactMethod {
150                    method_fingerprint,
151                    method_metadata,
152                    ffi_fn,
153                } = native_executor_method;
154                let recommended_capacities = match MetadataDecoder::new(main_contract_metadata)
155                    .decode_next()
156                    .ok_or(NativeExecutorError::ContractMetadataNotFound)?
157                    .map_err(|error| NativeExecutorError::ContractMetadataDecodingError { error })?
158                {
159                    MetadataItem::Contract {
160                        state_type_details,
161                        slot_type_details,
162                        tmp_type_details,
163                        ..
164                    } => (
165                        state_type_details.recommended_capacity,
166                        slot_type_details.recommended_capacity,
167                        tmp_type_details.recommended_capacity,
168                    ),
169                    MetadataItem::Trait { .. } => {
170                        return Err(NativeExecutorError::ExpectedContractMetadataFoundTrait);
171                    }
172                };
173                let (
174                    recommended_state_capacity,
175                    recommended_slot_capacity,
176                    recommended_tmp_capacity,
177                ) = recommended_capacities;
178
179                if methods_by_code
180                    .insert(
181                        (contact_code.as_bytes(), method_fingerprint),
182                        MethodDetails {
183                            recommended_state_capacity,
184                            recommended_slot_capacity,
185                            recommended_tmp_capacity,
186                            method_metadata,
187                            ffi_fn,
188                        },
189                    )
190                    .is_some()
191                {
192                    return Err(NativeExecutorError::DuplicateMethodInContract {
193                        contact_code,
194                        method_fingerprint,
195                    });
196                }
197            }
198        }
199
200        Ok(NativeExecutor {
201            shard_index: self.shard_index,
202            methods_by_code,
203        })
204    }
205}
206
207#[derive(Debug)]
208pub struct NativeExecutor {
209    shard_index: ShardIndex,
210    /// Indexed by contract's code and method fingerprint
211    methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
212}
213
214impl NativeExecutor {
215    /// Create a new [`Slots`] instance with system contracts already deployed
216    pub fn new_storage_slots(&self) -> Result<Slots, ContractError> {
217        // Manually deploy code of system code contract
218        let slots = [Slot::ReadWrite {
219            key: SlotKey {
220                owner: Address::SYSTEM_CODE,
221                contract: Address::SYSTEM_CODE,
222            },
223            buffer: SharedAlignedBuffer::from_bytes(Code::code().get_initialized()),
224        }];
225
226        let address_allocator_address = Address::system_address_allocator(self.shard_index);
227        let mut slots = Slots::new(slots);
228
229        let system_contracts: [(Address, &VariableBytes<{ MAX_CODE_SIZE }>); _] = [
230            (Address::SYSTEM_STATE, &State::code()),
231            (address_allocator_address, &AddressAllocator::code()),
232            (Address::SYSTEM_BLOCK, &Block::code()),
233            (Address::SYSTEM_NATIVE_TOKEN, &NativeToken::code()),
234            (
235                Address::SYSTEM_SIMPLE_WALLET_BASE,
236                &SimpleWalletBase::code(),
237            ),
238        ];
239
240        {
241            let mut nested_slots = slots.new_nested_rw();
242            // Allow deployment of system contracts
243            for (address, _code) in system_contracts {
244                assert!(nested_slots.add_new_contract(address));
245            }
246        }
247
248        // Deploy and initialize other system contacts
249        self.transaction_emulate(Address::NULL, &mut slots, |env| {
250            for (address, code) in system_contracts {
251                env.code_store(MethodContext::Reset, Address::SYSTEM_CODE, &address, code)?;
252            }
253
254            env.address_allocator_new(MethodContext::Reset, address_allocator_address)?;
255            env.block_genesis(MethodContext::Reset, Address::SYSTEM_BLOCK)?;
256            env.native_token_initialize(
257                MethodContext::Reset,
258                Address::SYSTEM_NATIVE_TOKEN,
259                &Address::SYSTEM_NATIVE_TOKEN,
260                // TODO: The balance should not be `max` by default, define rules and use them
261                &Balance::MAX,
262            )
263        })?;
264
265        Ok(slots)
266    }
267
268    /// Builder of native executor for specified shard index
269    #[must_use]
270    pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
271        NativeExecutorBuilder::new(shard_index)
272    }
273
274    /// Verify the provided transaction.
275    ///
276    /// [`Self::transaction_execute()`] can be used for transaction execution if needed.
277    /// [`Self::transaction_verify_execute()`] can be used to verify and execute a transaction with
278    /// a single call, primarily for testing purposes.
279    pub fn transaction_verify(
280        &self,
281        transaction: Transaction<'_>,
282        slots: &Slots,
283    ) -> Result<(), ContractError> {
284        if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
285            return Err(ContractError::BadInput);
286        }
287
288        let env_state = EnvState {
289            shard_index: self.shard_index,
290            padding_0: [0; _],
291            own_address: Address::NULL,
292            context: Address::NULL,
293            caller: Address::NULL,
294        };
295
296        let read_slots_size =
297            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
298                .map_err(|_error| ContractError::BadInput)?;
299        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
300
301        let write_slots_size =
302            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
303                .map_err(|_error| ContractError::BadInput)?;
304        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
305
306        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
307            .map_err(|_error| ContractError::BadInput)?;
308        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
309
310        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
311            .map_err(|_error| ContractError::BadInput)?;
312        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
313
314        let mut executor_context = NativeExecutorContext::new(
315            self.shard_index,
316            &self.methods_by_code,
317            slots.new_nested_ro(),
318            false,
319        );
320        let env = Env::with_executor_context(env_state, &mut executor_context);
321        env.tx_handler_authorize(
322            transaction.header.contract,
323            transaction.header,
324            &read_slots,
325            &write_slots,
326            &payload,
327            &seal,
328        )
329    }
330
331    /// Execute the previously verified transaction.
332    ///
333    /// [`Self::transaction_verify()`] must be used for verification.
334    /// [`Self::transaction_verify_execute()`] can be used to verify and execute a transaction with
335    /// a single call, primarily for testing purposes.
336    pub fn transaction_execute(
337        &self,
338        transaction: Transaction<'_>,
339        slots: &mut Slots,
340    ) -> Result<(), ContractError> {
341        if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
342            return Err(ContractError::BadInput);
343        }
344
345        // TODO: This is a pretty large data structure to copy around, try to make it a reference
346        let env_state = EnvState {
347            shard_index: self.shard_index,
348            padding_0: [0; _],
349            own_address: Address::NULL,
350            context: Address::NULL,
351            caller: Address::NULL,
352        };
353
354        let read_slots_size =
355            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
356                .map_err(|_error| ContractError::BadInput)?;
357        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
358
359        let write_slots_size =
360            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
361                .map_err(|_error| ContractError::BadInput)?;
362        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
363
364        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
365            .map_err(|_error| ContractError::BadInput)?;
366        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
367
368        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
369            .map_err(|_error| ContractError::BadInput)?;
370        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
371
372        let mut executor_context = NativeExecutorContext::new(
373            self.shard_index,
374            &self.methods_by_code,
375            slots.new_nested_rw(),
376            true,
377        );
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 the provided transaction, primarily for testing purposes.
392    ///
393    /// A slightly more efficient shortcut for [`Self::transaction_verify()`] +
394    /// [`Self::transaction_execute()`] compared to calling them separately.
395    pub fn transaction_verify_execute(
396        &self,
397        transaction: Transaction<'_>,
398        slots: &mut Slots,
399    ) -> Result<(), ContractError> {
400        if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
401            return Err(ContractError::BadInput);
402        }
403
404        // TODO: This is a pretty large data structure to copy around, try to make it a reference
405        let env_state = EnvState {
406            shard_index: self.shard_index,
407            padding_0: [0; _],
408            own_address: Address::NULL,
409            context: Address::NULL,
410            caller: Address::NULL,
411        };
412
413        let read_slots_size =
414            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
415                .map_err(|_error| ContractError::BadInput)?;
416        let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
417
418        let write_slots_size =
419            u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
420                .map_err(|_error| ContractError::BadInput)?;
421        let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
422
423        let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
424            .map_err(|_error| ContractError::BadInput)?;
425        let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
426
427        let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
428            .map_err(|_error| ContractError::BadInput)?;
429        let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
430
431        // TODO: Make it more efficient by not recreating NativeExecutorContext twice here
432        {
433            let mut executor_context = NativeExecutorContext::new(
434                self.shard_index,
435                &self.methods_by_code,
436                slots.new_nested_ro(),
437                false,
438            );
439            let env = Env::with_executor_context(env_state, &mut executor_context);
440            env.tx_handler_authorize(
441                transaction.header.contract,
442                transaction.header,
443                &read_slots,
444                &write_slots,
445                &payload,
446                &seal,
447            )?;
448        }
449
450        {
451            let mut executor_context = NativeExecutorContext::new(
452                self.shard_index,
453                &self.methods_by_code,
454                slots.new_nested_rw(),
455                true,
456            );
457            let mut env = Env::with_executor_context(env_state, &mut executor_context);
458            env.tx_handler_execute(
459                MethodContext::Reset,
460                transaction.header.contract,
461                transaction.header,
462                &read_slots,
463                &write_slots,
464                &payload,
465                &seal,
466            )?;
467        }
468
469        Ok(())
470    }
471
472    /// Emulate a transaction submitted by `contract` with method calls happening inside `calls`
473    /// without going through `TxHandler`.
474    ///
475    /// NOTE: This is primarily useful for testing environment, usually changes are done in the
476    /// transaction execution using [`Self::transaction_execute()`].
477    ///
478    /// Returns `None` if the read-only [`Slots`] instance was given.
479    pub fn transaction_emulate<Calls, T>(
480        &self,
481        contract: Address,
482        slots: &mut Slots,
483        calls: Calls,
484    ) -> T
485    where
486        Calls: FnOnce(&mut Env<'_>) -> T,
487    {
488        let env_state = EnvState {
489            shard_index: self.shard_index,
490            padding_0: [0; _],
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        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: [0; _],
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}