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