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