1#![feature(ptr_as_ref_unchecked)]
2
3mod context;
4
5use crate::context::{MethodDetails, NativeExecutorContext};
6use ab_contracts_common::env::{Env, EnvState, MethodContext, Transaction, TransactionSlot};
7use ab_contracts_common::metadata::decode::{MetadataDecoder, MetadataDecodingError, MetadataItem};
8use ab_contracts_common::method::MethodFingerprint;
9use ab_contracts_common::{
10 Address, Contract, ContractError, ContractTrait, ContractTraitDefinition,
11 NativeExecutorContactMethod, ShardIndex,
12};
13use ab_contracts_io_type::variable_bytes::VariableBytes;
14use ab_contracts_io_type::variable_elements::VariableElements;
15use ab_contracts_slots::aligned_buffer::SharedAlignedBuffer;
16use ab_contracts_slots::slots::{SlotKey, Slots};
17use ab_contracts_standards::tx_handler::TxHandlerExt;
18use ab_system_contract_address_allocator::{AddressAllocator, AddressAllocatorExt};
19use ab_system_contract_code::{Code, CodeExt};
20use ab_system_contract_simple_wallet_base::SimpleWalletBase;
21use ab_system_contract_state::State;
22use halfbrown::HashMap;
23use tracing::error;
24
25#[derive(Debug, thiserror::Error)]
27pub enum NativeExecutorError {
28 #[error("Contract metadata not found")]
30 ContractMetadataNotFound,
31 #[error("Contract metadata decoding error: {error}")]
33 ContractMetadataDecodingError {
34 error: MetadataDecodingError<'static>,
35 },
36 #[error("Expected contract metadata, found trait")]
38 ExpectedContractMetadataFoundTrait,
39 #[error("Duplicate method fingerprint {method_fingerprint} for contract code {contact_code}")]
41 DuplicateMethodInContract {
42 contact_code: &'static str,
44 method_fingerprint: &'static MethodFingerprint,
46 },
47}
48
49#[derive(Debug, Clone)]
50struct MethodsEntry {
51 contact_code: &'static str,
52 main_contract_metadata: &'static [u8],
53 native_executor_methods: &'static [NativeExecutorContactMethod],
54}
55
56#[derive(Debug, Clone)]
58pub struct NativeExecutorBuilder {
59 shard_index: ShardIndex,
60 methods: Vec<MethodsEntry>,
61}
62
63impl NativeExecutorBuilder {
64 fn new(shard_index: ShardIndex) -> Self {
65 Self {
66 shard_index,
67 methods: vec![
69 MethodsEntry {
70 contact_code: AddressAllocator::CODE,
71 main_contract_metadata: AddressAllocator::MAIN_CONTRACT_METADATA,
72 native_executor_methods: AddressAllocator::NATIVE_EXECUTOR_METHODS,
73 },
74 MethodsEntry {
75 contact_code: Code::CODE,
76 main_contract_metadata: Code::MAIN_CONTRACT_METADATA,
77 native_executor_methods: Code::NATIVE_EXECUTOR_METHODS,
78 },
79 MethodsEntry {
80 contact_code: State::CODE,
81 main_contract_metadata: State::MAIN_CONTRACT_METADATA,
82 native_executor_methods: State::NATIVE_EXECUTOR_METHODS,
83 },
84 MethodsEntry {
85 contact_code: SimpleWalletBase::CODE,
86 main_contract_metadata: SimpleWalletBase::MAIN_CONTRACT_METADATA,
87 native_executor_methods: SimpleWalletBase::NATIVE_EXECUTOR_METHODS,
88 },
89 ],
90 }
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)]
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<'static>, ContractError> {
222 let slots = [(
224 SlotKey {
225 owner: Address::SYSTEM_CODE,
226 contract: Address::SYSTEM_CODE,
227 },
228 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 {
235 let mut nested_slots = slots
236 .new_nested_rw()
237 .expect("Just created, hence not read-only; qed");
238 assert!(nested_slots.add_new_contract(address_allocator_address));
240 assert!(nested_slots.add_new_contract(Address::SYSTEM_STATE));
241 assert!(nested_slots.add_new_contract(Address::SYSTEM_SIMPLE_WALLET_BASE));
242 }
243
244 let maybe_result = self.transaction_emulate(Address::SYSTEM_CODE, &mut slots, |env| {
246 env.code_store(
247 MethodContext::Keep,
248 Address::SYSTEM_CODE,
249 &Address::SYSTEM_STATE,
250 &State::code(),
251 )?;
252
253 env.code_store(
254 MethodContext::Keep,
255 Address::SYSTEM_CODE,
256 &address_allocator_address,
257 &AddressAllocator::code(),
258 )?;
259 env.address_allocator_new(MethodContext::Keep, address_allocator_address)?;
260
261 env.code_store(
262 MethodContext::Keep,
263 Address::SYSTEM_CODE,
264 &Address::SYSTEM_SIMPLE_WALLET_BASE,
265 &SimpleWalletBase::code(),
266 )
267 });
268 maybe_result.expect("Slots instance is not read-only; qed")?;
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 let env_state = EnvState {
290 shard_index: self.shard_index,
291 padding_0: Default::default(),
292 own_address: Address::NULL,
293 context: Address::NULL,
294 caller: Address::NULL,
295 };
296
297 let read_slots_size =
298 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
299 .map_err(|_error| ContractError::BadInput)?;
300 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
301
302 let write_slots_size =
303 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
304 .map_err(|_error| ContractError::BadInput)?;
305 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
306
307 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
308 .map_err(|_error| ContractError::BadInput)?;
309 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
310
311 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
312 .map_err(|_error| ContractError::BadInput)?;
313 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
314
315 let mut executor_context = NativeExecutorContext::new(
316 self.shard_index,
317 &self.methods_by_code,
318 slots.new_nested_ro(),
319 false,
320 );
321 let env = Env::with_executor_context(env_state, &mut executor_context);
322 env.tx_handler_authorize(
323 transaction.header.contract,
324 transaction.header,
325 &read_slots,
326 &write_slots,
327 &payload,
328 &seal,
329 )
330 }
331
332 pub fn transaction_execute(
338 &self,
339 transaction: Transaction<'_>,
340 slots: &mut Slots<'_>,
341 ) -> Result<(), ContractError> {
342 let env_state = EnvState {
344 shard_index: self.shard_index,
345 padding_0: Default::default(),
346 own_address: Address::NULL,
347 context: Address::NULL,
348 caller: Address::NULL,
349 };
350
351 let read_slots_size =
352 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
353 .map_err(|_error| ContractError::BadInput)?;
354 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
355
356 let write_slots_size =
357 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
358 .map_err(|_error| ContractError::BadInput)?;
359 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
360
361 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
362 .map_err(|_error| ContractError::BadInput)?;
363 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
364
365 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
366 .map_err(|_error| ContractError::BadInput)?;
367 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
368
369 let mut executor_context = NativeExecutorContext::new(
370 self.shard_index,
371 &self.methods_by_code,
372 slots.new_nested_rw().ok_or_else(|| {
373 error!("Trying to execute a transaction with read-only `Slots` instance #1");
374
375 ContractError::Forbidden
376 })?,
377 true,
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(
395 &self,
396 transaction: Transaction<'_>,
397 slots: &mut Slots<'_>,
398 ) -> Result<(), ContractError> {
399 let env_state = EnvState {
401 shard_index: self.shard_index,
402 padding_0: Default::default(),
403 own_address: Address::NULL,
404 context: Address::NULL,
405 caller: Address::NULL,
406 };
407
408 let read_slots_size =
409 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.read_slots))
410 .map_err(|_error| ContractError::BadInput)?;
411 let read_slots = VariableElements::from_buffer(transaction.read_slots, &read_slots_size);
412
413 let write_slots_size =
414 u32::try_from(size_of_val::<[TransactionSlot]>(transaction.write_slots))
415 .map_err(|_error| ContractError::BadInput)?;
416 let write_slots = VariableElements::from_buffer(transaction.write_slots, &write_slots_size);
417
418 let payload_size = u32::try_from(size_of_val::<[u128]>(transaction.payload))
419 .map_err(|_error| ContractError::BadInput)?;
420 let payload = VariableElements::from_buffer(transaction.payload, &payload_size);
421
422 let seal_size = u32::try_from(size_of_val::<[u8]>(transaction.seal))
423 .map_err(|_error| ContractError::BadInput)?;
424 let seal = VariableBytes::from_buffer(transaction.seal, &seal_size);
425
426 {
428 let mut executor_context = NativeExecutorContext::new(
429 self.shard_index,
430 &self.methods_by_code,
431 slots.new_nested_ro(),
432 false,
433 );
434 let env = Env::with_executor_context(env_state, &mut executor_context);
435 env.tx_handler_authorize(
436 transaction.header.contract,
437 transaction.header,
438 &read_slots,
439 &write_slots,
440 &payload,
441 &seal,
442 )?;
443 }
444
445 {
446 let mut executor_context = NativeExecutorContext::new(
447 self.shard_index,
448 &self.methods_by_code,
449 slots.new_nested_rw().ok_or_else(|| {
450 error!("Trying to execute a transaction with read-only `Slots` instance #2");
451
452 ContractError::Forbidden
453 })?,
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 #[must_use]
479 pub fn transaction_emulate<Calls, T>(
480 &self,
481 contract: Address,
482 slots: &mut Slots<'_>,
483 calls: Calls,
484 ) -> Option<T>
485 where
486 Calls: FnOnce(&mut Env<'_>) -> T,
487 {
488 let env_state = EnvState {
489 shard_index: self.shard_index,
490 padding_0: Default::default(),
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 Some(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: Default::default(),
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}