1#![feature(
2 generic_arg_infer,
3 non_null_from_ref,
4 ptr_as_ref_unchecked,
5 unsafe_cell_access
6)]
7
8mod context;
9
10use crate::context::{MethodDetails, NativeExecutorContext};
11use ab_aligned_buffer::SharedAlignedBuffer;
12use ab_contracts_common::env::{Env, EnvState, MethodContext};
13use ab_contracts_common::metadata::decode::{MetadataDecoder, MetadataDecodingError, MetadataItem};
14use ab_contracts_common::method::MethodFingerprint;
15use ab_contracts_common::{
16 Contract, ContractError, ContractTrait, ContractTraitDefinition, MAX_CODE_SIZE,
17 NativeExecutorContactMethod,
18};
19use ab_contracts_standards::fungible::Fungible;
20use ab_contracts_standards::tx_handler::TxHandlerExt;
21use ab_core_primitives::address::Address;
22use ab_core_primitives::balance::Balance;
23use ab_core_primitives::shard::ShardIndex;
24use ab_core_primitives::transaction::{Transaction, TransactionHeader, TransactionSlot};
25use ab_executor_slots::{Slot, SlotKey, Slots};
26use ab_io_type::variable_bytes::VariableBytes;
27use ab_io_type::variable_elements::VariableElements;
28use ab_system_contract_address_allocator::{AddressAllocator, AddressAllocatorExt};
29use ab_system_contract_block::{Block, BlockExt};
30use ab_system_contract_code::{Code, CodeExt};
31use ab_system_contract_native_token::{NativeToken, NativeTokenExt};
32use ab_system_contract_simple_wallet_base::SimpleWalletBase;
33use ab_system_contract_state::State;
34use halfbrown::HashMap;
35use tracing::error;
36
37#[derive(Debug, thiserror::Error)]
39pub enum NativeExecutorError {
40 #[error("Contract metadata not found")]
42 ContractMetadataNotFound,
43 #[error("Contract metadata decoding error: {error}")]
45 ContractMetadataDecodingError {
46 error: MetadataDecodingError<'static>,
47 },
48 #[error("Expected contract metadata, found trait")]
50 ExpectedContractMetadataFoundTrait,
51 #[error("Duplicate method fingerprint {method_fingerprint} for contract code {contact_code}")]
53 DuplicateMethodInContract {
54 contact_code: &'static str,
56 method_fingerprint: &'static MethodFingerprint,
58 },
59}
60
61#[derive(Debug, Clone)]
62struct MethodsEntry {
63 contact_code: &'static str,
64 main_contract_metadata: &'static [u8],
65 native_executor_methods: &'static [NativeExecutorContactMethod],
66}
67
68#[derive(Debug, Clone)]
70pub struct NativeExecutorBuilder {
71 shard_index: ShardIndex,
72 methods: Vec<MethodsEntry>,
73}
74
75impl NativeExecutorBuilder {
76 fn new(shard_index: ShardIndex) -> Self {
77 let instance = Self {
78 shard_index,
79 methods: Vec::new(),
80 };
81
82 instance
84 .with_contract::<AddressAllocator>()
85 .with_contract::<Block>()
86 .with_contract::<Code>()
87 .with_contract::<NativeToken>()
88 .with_contract_trait::<NativeToken, dyn Fungible>()
89 .with_contract::<SimpleWalletBase>()
90 .with_contract::<State>()
91 }
92
93 #[must_use]
104 pub fn with_contract<C>(mut self) -> Self
105 where
106 C: Contract,
107 {
108 self.methods.push(MethodsEntry {
109 contact_code: C::CODE,
110 main_contract_metadata: C::MAIN_CONTRACT_METADATA,
111 native_executor_methods: C::NATIVE_EXECUTOR_METHODS,
112 });
113 self
114 }
115
116 #[must_use]
129 pub fn with_contract_trait<C, DynCT>(mut self) -> Self
130 where
131 C: Contract + ContractTrait<DynCT>,
132 DynCT: ContractTraitDefinition + ?Sized,
133 {
134 self.methods.push(MethodsEntry {
135 contact_code: C::CODE,
136 main_contract_metadata: C::MAIN_CONTRACT_METADATA,
137 native_executor_methods: <C as ContractTrait<DynCT>>::NATIVE_EXECUTOR_METHODS,
138 });
139 self
140 }
141
142 pub fn build(self) -> Result<NativeExecutor, NativeExecutorError> {
144 let mut methods_by_code = HashMap::with_capacity(10);
146 for methods_entry in self.methods {
147 let MethodsEntry {
148 contact_code,
149 main_contract_metadata,
150 native_executor_methods,
151 } = methods_entry;
152 for &native_executor_method in native_executor_methods {
153 let NativeExecutorContactMethod {
154 method_fingerprint,
155 method_metadata,
156 ffi_fn,
157 } = native_executor_method;
158 let recommended_capacities = match MetadataDecoder::new(main_contract_metadata)
159 .decode_next()
160 .ok_or(NativeExecutorError::ContractMetadataNotFound)?
161 .map_err(|error| NativeExecutorError::ContractMetadataDecodingError { error })?
162 {
163 MetadataItem::Contract {
164 state_type_details,
165 slot_type_details,
166 tmp_type_details,
167 ..
168 } => (
169 state_type_details.recommended_capacity,
170 slot_type_details.recommended_capacity,
171 tmp_type_details.recommended_capacity,
172 ),
173 MetadataItem::Trait { .. } => {
174 return Err(NativeExecutorError::ExpectedContractMetadataFoundTrait);
175 }
176 };
177 let (
178 recommended_state_capacity,
179 recommended_slot_capacity,
180 recommended_tmp_capacity,
181 ) = recommended_capacities;
182
183 if methods_by_code
184 .insert(
185 (contact_code.as_bytes(), method_fingerprint),
186 MethodDetails {
187 recommended_state_capacity,
188 recommended_slot_capacity,
189 recommended_tmp_capacity,
190 method_metadata,
191 ffi_fn,
192 },
193 )
194 .is_some()
195 {
196 return Err(NativeExecutorError::DuplicateMethodInContract {
197 contact_code,
198 method_fingerprint,
199 });
200 }
201 }
202 }
203
204 Ok(NativeExecutor {
205 shard_index: self.shard_index,
206 methods_by_code,
207 })
208 }
209}
210
211#[derive(Debug)]
212pub struct NativeExecutor {
213 shard_index: ShardIndex,
214 methods_by_code: HashMap<(&'static [u8], &'static MethodFingerprint), MethodDetails>,
216}
217
218impl NativeExecutor {
219 pub fn new_storage_slots(&self) -> Result<Slots, ContractError> {
221 let slots = [Slot::ReadWrite {
223 key: SlotKey {
224 owner: Address::SYSTEM_CODE,
225 contract: Address::SYSTEM_CODE,
226 },
227 buffer: SharedAlignedBuffer::from_bytes(Code::code().get_initialized()),
228 }];
229
230 let address_allocator_address = Address::system_address_allocator(self.shard_index);
231 let mut slots = Slots::new(slots);
232
233 let system_contracts: [(Address, &VariableBytes<{ MAX_CODE_SIZE }>); _] = [
234 (Address::SYSTEM_STATE, &State::code()),
235 (address_allocator_address, &AddressAllocator::code()),
236 (Address::SYSTEM_BLOCK, &Block::code()),
237 (Address::SYSTEM_NATIVE_TOKEN, &NativeToken::code()),
238 (
239 Address::SYSTEM_SIMPLE_WALLET_BASE,
240 &SimpleWalletBase::code(),
241 ),
242 ];
243
244 {
245 let mut nested_slots = slots.new_nested_rw();
246 for (address, _code) in system_contracts {
248 assert!(nested_slots.add_new_contract(address));
249 }
250 }
251
252 self.transaction_emulate(Address::NULL, &mut slots, |env| {
254 for (address, code) in system_contracts {
255 env.code_store(MethodContext::Reset, Address::SYSTEM_CODE, &address, code)?;
256 }
257
258 env.address_allocator_new(MethodContext::Reset, address_allocator_address)?;
259 env.block_genesis(MethodContext::Reset, Address::SYSTEM_BLOCK)?;
260 env.native_token_initialize(
261 MethodContext::Reset,
262 Address::SYSTEM_NATIVE_TOKEN,
263 &Address::SYSTEM_NATIVE_TOKEN,
264 &Balance::MAX,
266 )
267 })?;
268
269 Ok(slots)
270 }
271
272 #[must_use]
274 pub fn builder(shard_index: ShardIndex) -> NativeExecutorBuilder {
275 NativeExecutorBuilder::new(shard_index)
276 }
277
278 pub fn transaction_verify(
284 &self,
285 transaction: Transaction<'_>,
286 slots: &Slots,
287 ) -> Result<(), ContractError> {
288 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
289 return Err(ContractError::BadInput);
290 }
291
292 let env_state = EnvState {
293 shard_index: self.shard_index,
294 padding_0: Default::default(),
295 own_address: Address::NULL,
296 context: Address::NULL,
297 caller: Address::NULL,
298 };
299
300 let read_slots_size =
301 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
302 .map_err(|_error| ContractError::BadInput)?;
303 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
304
305 let write_slots_size =
306 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
307 .map_err(|_error| ContractError::BadInput)?;
308 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
309
310 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
311 .map_err(|_error| ContractError::BadInput)?;
312 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
313
314 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
315 .map_err(|_error| ContractError::BadInput)?;
316 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
317
318 let mut executor_context = NativeExecutorContext::new(
319 self.shard_index,
320 &self.methods_by_code,
321 slots.new_nested_ro(),
322 false,
323 );
324 let env = Env::with_executor_context(env_state, &mut executor_context);
325 env.tx_handler_authorize(
326 transaction.header.contract,
327 transaction.header,
328 &read_slots,
329 &write_slots,
330 &payload,
331 &seal,
332 )
333 }
334
335 pub fn transaction_execute(
341 &self,
342 transaction: Transaction<'_>,
343 slots: &mut Slots,
344 ) -> Result<(), ContractError> {
345 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
346 return Err(ContractError::BadInput);
347 }
348
349 let env_state = EnvState {
351 shard_index: self.shard_index,
352 padding_0: Default::default(),
353 own_address: Address::NULL,
354 context: Address::NULL,
355 caller: Address::NULL,
356 };
357
358 let read_slots_size =
359 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
360 .map_err(|_error| ContractError::BadInput)?;
361 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
362
363 let write_slots_size =
364 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
365 .map_err(|_error| ContractError::BadInput)?;
366 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
367
368 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
369 .map_err(|_error| ContractError::BadInput)?;
370 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
371
372 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
373 .map_err(|_error| ContractError::BadInput)?;
374 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
375
376 let mut executor_context = NativeExecutorContext::new(
377 self.shard_index,
378 &self.methods_by_code,
379 slots.new_nested_rw(),
380 true,
381 );
382
383 let mut env = Env::with_executor_context(env_state, &mut executor_context);
384 env.tx_handler_execute(
385 MethodContext::Reset,
386 transaction.header.contract,
387 transaction.header,
388 &read_slots,
389 &write_slots,
390 &payload,
391 &seal,
392 )
393 }
394
395 pub fn transaction_verify_execute(
399 &self,
400 transaction: Transaction<'_>,
401 slots: &mut Slots,
402 ) -> Result<(), ContractError> {
403 if transaction.header.version != TransactionHeader::TRANSACTION_VERSION {
404 return Err(ContractError::BadInput);
405 }
406
407 let env_state = EnvState {
409 shard_index: self.shard_index,
410 padding_0: Default::default(),
411 own_address: Address::NULL,
412 context: Address::NULL,
413 caller: Address::NULL,
414 };
415
416 let read_slots_size =
417 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
418 .map_err(|_error| ContractError::BadInput)?;
419 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
420
421 let write_slots_size =
422 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
423 .map_err(|_error| ContractError::BadInput)?;
424 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
425
426 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
427 .map_err(|_error| ContractError::BadInput)?;
428 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
429
430 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
431 .map_err(|_error| ContractError::BadInput)?;
432 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
433
434 {
436 let mut executor_context = NativeExecutorContext::new(
437 self.shard_index,
438 &self.methods_by_code,
439 slots.new_nested_ro(),
440 false,
441 );
442 let env = Env::with_executor_context(env_state, &mut executor_context);
443 env.tx_handler_authorize(
444 transaction.header.contract,
445 transaction.header,
446 &read_slots,
447 &write_slots,
448 &payload,
449 &seal,
450 )?;
451 }
452
453 {
454 let mut executor_context = NativeExecutorContext::new(
455 self.shard_index,
456 &self.methods_by_code,
457 slots.new_nested_rw(),
458 true,
459 );
460 let mut env = Env::with_executor_context(env_state, &mut executor_context);
461 env.tx_handler_execute(
462 MethodContext::Reset,
463 transaction.header.contract,
464 transaction.header,
465 &read_slots,
466 &write_slots,
467 &payload,
468 &seal,
469 )?;
470 }
471
472 Ok(())
473 }
474
475 pub fn transaction_emulate<Calls, T>(
483 &self,
484 contract: Address,
485 slots: &mut Slots,
486 calls: Calls,
487 ) -> T
488 where
489 Calls: FnOnce(&mut Env<'_>) -> T,
490 {
491 let env_state = EnvState {
492 shard_index: self.shard_index,
493 padding_0: Default::default(),
494 own_address: contract,
495 context: contract,
496 caller: Address::NULL,
497 };
498
499 let mut executor_context = NativeExecutorContext::new(
500 self.shard_index,
501 &self.methods_by_code,
502 slots.new_nested_rw(),
503 true,
504 );
505 let mut env = Env::with_executor_context(env_state, &mut executor_context);
506 calls(&mut env)
507 }
508
509 #[must_use]
514 pub fn with_env_ro<Callback, T>(&self, slots: &Slots, callback: Callback) -> T
515 where
516 Callback: FnOnce(&Env<'_>) -> T,
517 {
518 let env_state = EnvState {
519 shard_index: self.shard_index,
520 padding_0: Default::default(),
521 own_address: Address::NULL,
522 context: Address::NULL,
523 caller: Address::NULL,
524 };
525
526 let mut executor_context = NativeExecutorContext::new(
527 self.shard_index,
528 &self.methods_by_code,
529 slots.new_nested_ro(),
530 false,
531 );
532 let env = Env::with_executor_context(env_state, &mut executor_context);
533 callback(&env)
534 }
535}