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;
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 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: [0; _],
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: [0; _],
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(
395 &self,
396 transaction: Transaction<'_>,
397 slots: &mut Slots,
398 ) -> Result<(), ContractError> {
399 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
400 return Err(ContractError::BadInput);
401 }
402
403 let env_state = EnvState {
405 shard_index: self.shard_index,
406 padding_0: [0; _],
407 own_address: Address::NULL,
408 context: Address::NULL,
409 caller: Address::NULL,
410 };
411
412 let read_slots_size =
413 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
414 .map_err(|_error| ContractError::BadInput)?;
415 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
416
417 let write_slots_size =
418 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
419 .map_err(|_error| ContractError::BadInput)?;
420 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
421
422 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
423 .map_err(|_error| ContractError::BadInput)?;
424 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
425
426 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
427 .map_err(|_error| ContractError::BadInput)?;
428 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
429
430 {
432 let mut executor_context = NativeExecutorContext::new(
433 self.shard_index,
434 &self.methods_by_code,
435 slots.new_nested_ro(),
436 false,
437 );
438 let env = Env::with_executor_context(env_state, &mut executor_context);
439 env.tx_handler_authorize(
440 transaction.header.contract,
441 transaction.header,
442 &read_slots,
443 &write_slots,
444 &payload,
445 &seal,
446 )?;
447 }
448
449 {
450 let mut executor_context = NativeExecutorContext::new(
451 self.shard_index,
452 &self.methods_by_code,
453 slots.new_nested_rw(),
454 true,
455 );
456 let mut env = Env::with_executor_context(env_state, &mut executor_context);
457 env.tx_handler_execute(
458 MethodContext::Reset,
459 transaction.header.contract,
460 transaction.header,
461 &read_slots,
462 &write_slots,
463 &payload,
464 &seal,
465 )?;
466 }
467
468 Ok(())
469 }
470
471 pub fn transaction_emulate<Calls, T>(
479 &self,
480 contract: Address,
481 slots: &mut Slots,
482 calls: Calls,
483 ) -> T
484 where
485 Calls: FnOnce(&mut Env<'_>) -> T,
486 {
487 let env_state = EnvState {
488 shard_index: self.shard_index,
489 padding_0: [0; _],
490 own_address: contract,
491 context: contract,
492 caller: Address::NULL,
493 };
494
495 let mut executor_context = NativeExecutorContext::new(
496 self.shard_index,
497 &self.methods_by_code,
498 slots.new_nested_rw(),
499 true,
500 );
501 let mut env = Env::with_executor_context(env_state, &mut executor_context);
502 calls(&mut env)
503 }
504
505 #[must_use]
510 pub fn with_env_ro<Callback, T>(&self, slots: &Slots, callback: Callback) -> T
511 where
512 Callback: FnOnce(&Env<'_>) -> T,
513 {
514 let env_state = EnvState {
515 shard_index: self.shard_index,
516 padding_0: [0; _],
517 own_address: Address::NULL,
518 context: Address::NULL,
519 caller: Address::NULL,
520 };
521
522 let mut executor_context = NativeExecutorContext::new(
523 self.shard_index,
524 &self.methods_by_code,
525 slots.new_nested_ro(),
526 false,
527 );
528 let env = Env::with_executor_context(env_state, &mut executor_context);
529 callback(&env)
530 }
531}