1#![feature(const_convert, const_trait_impl, slice_ptr_get)]
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;
30
31#[derive(Debug, thiserror::Error)]
33pub enum NativeExecutorError {
34 #[error("Contract metadata not found")]
36 ContractMetadataNotFound,
37 #[error("Contract metadata decoding error: {error}")]
39 ContractMetadataDecodingError {
40 error: MetadataDecodingError<'static>,
41 },
42 #[error("Expected contract metadata, found trait")]
44 ExpectedContractMetadataFoundTrait,
45 #[error("Duplicate method fingerprint {method_fingerprint} for contract code {contact_code}")]
47 DuplicateMethodInContract {
48 contact_code: &'static str,
50 method_fingerprint: &'static MethodFingerprint,
52 },
53}
54
55#[derive(Debug, Clone)]
56struct MethodsEntry {
57 contact_code: &'static str,
58 main_contract_metadata: &'static [u8],
59 native_executor_methods: &'static [NativeExecutorContactMethod],
60}
61
62#[derive(Debug, Clone)]
64pub struct NativeExecutorBuilder {
65 shard_index: ShardIndex,
66 methods: Vec<MethodsEntry>,
67}
68
69impl NativeExecutorBuilder {
70 fn new(shard_index: ShardIndex) -> Self {
71 let instance = Self {
72 shard_index,
73 methods: Vec::new(),
74 };
75
76 instance
78 .with_contract::<AddressAllocator>()
79 .with_contract::<Block>()
80 .with_contract::<Code>()
81 .with_contract::<NativeToken>()
82 .with_contract_trait::<NativeToken, dyn Fungible>()
83 .with_contract::<SimpleWalletBase>()
84 .with_contract::<State>()
85 }
86
87 #[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 #[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 pub fn build(self) -> Result<NativeExecutor, NativeExecutorError> {
139 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 #[expect(
154 clippy::rest_pattern_accessible_field,
155 reason = "Do not need other fields"
156 )]
157 let recommended_capacities = match MetadataDecoder::new(main_contract_metadata)
158 .decode_next()
159 .ok_or(NativeExecutorError::ContractMetadataNotFound)?
160 .map_err(|error| NativeExecutorError::ContractMetadataDecodingError { error })?
161 {
162 MetadataItem::Contract {
163 state_type_details,
164 slot_type_details,
165 tmp_type_details,
166 ..
167 } => (
168 state_type_details.recommended_capacity,
169 slot_type_details.recommended_capacity,
170 tmp_type_details.recommended_capacity,
171 ),
172 MetadataItem::Trait { .. } => {
173 return Err(NativeExecutorError::ExpectedContractMetadataFoundTrait);
174 }
175 };
176 let (
177 recommended_state_capacity,
178 recommended_slot_capacity,
179 recommended_tmp_capacity,
180 ) = recommended_capacities;
181
182 if methods_by_code
183 .insert(
184 (contact_code.as_bytes(), method_fingerprint),
185 MethodDetails {
186 recommended_state_capacity,
187 recommended_slot_capacity,
188 recommended_tmp_capacity,
189 method_metadata,
190 ffi_fn,
191 },
192 )
193 .is_some()
194 {
195 return Err(NativeExecutorError::DuplicateMethodInContract {
196 contact_code,
197 method_fingerprint,
198 });
199 }
200 }
201 }
202
203 Ok(NativeExecutor {
204 shard_index: self.shard_index,
205 methods_by_code,
206 })
207 }
208}
209
210#[derive(Debug)]
222pub struct NativeExecutor {
223 shard_index: ShardIndex,
224 methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
226}
227
228impl NativeExecutor {
229 pub fn new_storage_slots(&self) -> Result<Slots, ContractError> {
231 let slots = [Slot::ReadWrite {
233 key: SlotKey {
234 owner: Address::SYSTEM_CODE,
235 contract: Address::SYSTEM_CODE,
236 },
237 buffer: SharedAlignedBuffer::from_bytes(Code::code().get_initialized()),
238 }];
239
240 let address_allocator_address = Address::system_address_allocator(self.shard_index);
241 let mut slots = Slots::new(slots);
242
243 let system_contracts: [(Address, &VariableBytes<'_, { MAX_CODE_SIZE }>); _] = [
244 (Address::SYSTEM_STATE, &State::code()),
245 (address_allocator_address, &AddressAllocator::code()),
246 (Address::SYSTEM_BLOCK, &Block::code()),
247 (Address::SYSTEM_NATIVE_TOKEN, &NativeToken::code()),
248 (
249 Address::SYSTEM_SIMPLE_WALLET_BASE,
250 &SimpleWalletBase::code(),
251 ),
252 ];
253
254 {
255 let mut nested_slots = slots.new_nested_rw();
256 for (address, _code) in system_contracts {
258 let result = nested_slots.add_new_contract(address);
259 debug_assert!(result);
260 }
261 nested_slots.commit();
262 }
263
264 self.transaction_emulate(Address::NULL, &mut slots, |env| {
266 for (address, code) in system_contracts {
267 env.code_store(MethodContext::Reset, Address::SYSTEM_CODE, &address, code)?;
268 }
269
270 env.address_allocator_new(MethodContext::Reset, address_allocator_address)?;
271 env.block_genesis(MethodContext::Reset, Address::SYSTEM_BLOCK)?;
272 env.native_token_initialize(
273 MethodContext::Reset,
274 Address::SYSTEM_NATIVE_TOKEN,
275 &Address::SYSTEM_NATIVE_TOKEN,
276 &Balance::MAX,
278 )
279 })?;
280
281 Ok(slots)
282 }
283
284 #[must_use]
286 pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
287 NativeExecutorBuilder::new(shard_index)
288 }
289
290 pub fn transaction_verify(
296 &self,
297 transaction: Transaction<'_>,
298 slots: &Slots,
299 ) -> Result<(), ContractError> {
300 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
301 return Err(ContractError::BadInput);
302 }
303
304 let env_state = EnvState {
305 shard_index: self.shard_index,
306 padding_0: [0; _],
307 own_address: Address::NULL,
308 context: Address::NULL,
309 caller: Address::NULL,
310 };
311
312 let read_slots_size =
313 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
314 .map_err(|_error| ContractError::BadInput)?;
315 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size)
316 .expect("Size is computed from the buffer above; qed");
317
318 let write_slots_size =
319 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
320 .map_err(|_error| ContractError::BadInput)?;
321 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size)
322 .expect("Size is computed from the buffer above; qed");
323
324 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
325 .map_err(|_error| ContractError::BadInput)?;
326 let payload = VariableElements::from_buffer(transaction.payload, &payload_size)
327 .expect("Size is computed from the buffer above; qed");
328
329 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
330 .map_err(|_error| ContractError::BadInput)?;
331 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size)
332 .expect("Size is computed from the buffer above; qed");
333
334 let executor_context = NativeExecutorContext::new(
335 self.shard_index,
336 &self.methods_by_code,
337 slots.new_nested_ro(),
338 false,
339 );
340 let env = unsafe { Env::with_executor_context(env_state, &executor_context) };
343 env.tx_handler_authorize(
344 transaction.header.contract,
345 transaction.header,
346 &read_slots,
347 &write_slots,
348 &payload,
349 &seal,
350 )
351 }
352
353 pub fn transaction_execute(
359 &self,
360 transaction: Transaction<'_>,
361 slots: &mut Slots,
362 ) -> Result<(), ContractError> {
363 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
364 return Err(ContractError::BadInput);
365 }
366
367 let env_state = EnvState {
369 shard_index: self.shard_index,
370 padding_0: [0; _],
371 own_address: Address::NULL,
372 context: Address::NULL,
373 caller: Address::NULL,
374 };
375
376 let read_slots_size =
377 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
378 .map_err(|_error| ContractError::BadInput)?;
379 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size)
380 .expect("Size is computed from the buffer above; qed");
381
382 let write_slots_size =
383 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
384 .map_err(|_error| ContractError::BadInput)?;
385 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size)
386 .expect("Size is computed from the buffer above; qed");
387
388 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
389 .map_err(|_error| ContractError::BadInput)?;
390 let payload = VariableElements::from_buffer(transaction.payload, &payload_size)
391 .expect("Size is computed from the buffer above; qed");
392
393 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
394 .map_err(|_error| ContractError::BadInput)?;
395 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size)
396 .expect("Size is computed from the buffer above; qed");
397
398 let executor_context = NativeExecutorContext::new(
399 self.shard_index,
400 &self.methods_by_code,
401 slots.new_nested_rw(),
402 true,
403 );
404
405 let mut env = unsafe { Env::with_executor_context(env_state, &executor_context) };
408 env.tx_handler_execute(
409 MethodContext::Reset,
410 transaction.header.contract,
411 transaction.header,
412 &read_slots,
413 &write_slots,
414 &payload,
415 &seal,
416 )?;
417
418 executor_context.into_slots().commit();
419
420 Ok(())
421 }
422
423 pub fn transaction_verify_execute(
428 &self,
429 transaction: Transaction<'_>,
430 slots: &mut Slots,
431 ) -> Result<(), ContractError> {
432 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
433 return Err(ContractError::BadInput);
434 }
435
436 let env_state = EnvState {
438 shard_index: self.shard_index,
439 padding_0: [0; _],
440 own_address: Address::NULL,
441 context: Address::NULL,
442 caller: Address::NULL,
443 };
444
445 let read_slots_size =
446 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
447 .map_err(|_error| ContractError::BadInput)?;
448 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size)
449 .expect("Size is computed from the buffer above; qed");
450
451 let write_slots_size =
452 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
453 .map_err(|_error| ContractError::BadInput)?;
454 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size)
455 .expect("Size is computed from the buffer above; qed");
456
457 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
458 .map_err(|_error| ContractError::BadInput)?;
459 let payload = VariableElements::from_buffer(transaction.payload, &payload_size)
460 .expect("Size is computed from the buffer above; qed");
461
462 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
463 .map_err(|_error| ContractError::BadInput)?;
464 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size)
465 .expect("Size is computed from the buffer above; qed");
466
467 {
469 let executor_context = NativeExecutorContext::new(
470 self.shard_index,
471 &self.methods_by_code,
472 slots.new_nested_ro(),
473 false,
474 );
475 let env = unsafe { Env::with_executor_context(env_state, &executor_context) };
478 env.tx_handler_authorize(
479 transaction.header.contract,
480 transaction.header,
481 &read_slots,
482 &write_slots,
483 &payload,
484 &seal,
485 )?;
486 }
487
488 {
489 let executor_context = NativeExecutorContext::new(
490 self.shard_index,
491 &self.methods_by_code,
492 slots.new_nested_rw(),
493 true,
494 );
495 let mut env = unsafe { Env::with_executor_context(env_state, &executor_context) };
498 env.tx_handler_execute(
499 MethodContext::Reset,
500 transaction.header.contract,
501 transaction.header,
502 &read_slots,
503 &write_slots,
504 &payload,
505 &seal,
506 )?;
507
508 executor_context.into_slots().commit();
509 }
510
511 Ok(())
512 }
513
514 pub fn transaction_emulate<Calls, T>(
522 &self,
523 contract: Address,
524 slots: &mut Slots,
525 calls: Calls,
526 ) -> T
527 where
528 Calls: FnOnce(&mut Env<'_>) -> T,
529 {
530 let env_state = EnvState {
531 shard_index: self.shard_index,
532 padding_0: [0; _],
533 own_address: contract,
534 context: contract,
535 caller: Address::NULL,
536 };
537
538 let executor_context = NativeExecutorContext::new(
539 self.shard_index,
540 &self.methods_by_code,
541 slots.new_nested_rw(),
542 true,
543 );
544 let mut env = unsafe { Env::with_executor_context(env_state, &executor_context) };
548 let result = calls(&mut env);
549
550 executor_context.into_slots().commit();
553
554 result
555 }
556
557 #[must_use]
562 pub fn with_env_ro<Callback, T>(&self, slots: &Slots, callback: Callback) -> T
563 where
564 Callback: FnOnce(&Env<'_>) -> T,
565 {
566 let env_state = EnvState {
567 shard_index: self.shard_index,
568 padding_0: [0; _],
569 own_address: Address::NULL,
570 context: Address::NULL,
571 caller: Address::NULL,
572 };
573
574 let executor_context = NativeExecutorContext::new(
575 self.shard_index,
576 &self.methods_by_code,
577 slots.new_nested_ro(),
578 false,
579 );
580 let env = unsafe { Env::with_executor_context(env_state, &executor_context) };
584 callback(&env)
585 }
586}