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#[derive(Debug, thiserror::Error)]
34pub enum NativeExecutorError {
35 #[error("Contract metadata not found")]
37 ContractMetadataNotFound,
38 #[error("Contract metadata decoding error: {error}")]
40 ContractMetadataDecodingError {
41 error: MetadataDecodingError<'static>,
42 },
43 #[error("Expected contract metadata, found trait")]
45 ExpectedContractMetadataFoundTrait,
46 #[error("Duplicate method fingerprint {method_fingerprint} for contract code {contact_code}")]
48 DuplicateMethodInContract {
49 contact_code: &'static str,
51 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#[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 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 #[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 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 methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
211}
212
213impl NativeExecutor {
214 pub fn new_storage_slots(&self) -> Result<Slots, ContractError> {
216 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 for (address, _code) in system_contracts {
243 assert!(nested_slots.add_new_contract(address));
244 }
245 }
246
247 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 &Balance::MAX,
261 )
262 })?;
263
264 Ok(slots)
265 }
266
267 #[must_use]
269 pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
270 NativeExecutorBuilder::new(shard_index)
271 }
272
273 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 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 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 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 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 {
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 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 #[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}