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#[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]
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 #[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 pub fn build(self) -> Result<NativeExecutor, NativeExecutorError> {
140 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 methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
212}
213
214impl NativeExecutor {
215 pub fn new_storage_slots(&self) -> Result<Slots, ContractError> {
217 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 for (address, _code) in system_contracts {
244 assert!(nested_slots.add_new_contract(address));
245 }
246 }
247
248 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 &Balance::MAX,
262 )
263 })?;
264
265 Ok(slots)
266 }
267
268 #[must_use]
270 pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
271 NativeExecutorBuilder::new(shard_index)
272 }
273
274 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 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 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 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 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 {
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 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 #[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}