1use std::{borrow::Cow, num::NonZeroU32, sync::Arc};
2
3use bytes::Bytes;
4use casper_executor_wasm_common::{
5 chain_utils,
6 entry_point::{
7 ENTRY_POINT_PAYMENT_CALLER, ENTRY_POINT_PAYMENT_DIRECT_INVOCATION_ONLY,
8 ENTRY_POINT_PAYMENT_SELF_ONWARD,
9 },
10 env_info::EnvInfo,
11 error::{
12 CallError, CALLEE_NOT_CALLABLE, CALLEE_SUCCEEDED, CALLEE_TRAPPED, HOST_ERROR_INVALID_DATA,
13 HOST_ERROR_INVALID_INPUT, HOST_ERROR_MAX_MESSAGES_PER_BLOCK_EXCEEDED,
14 HOST_ERROR_MESSAGE_TOPIC_FULL, HOST_ERROR_NOT_FOUND, HOST_ERROR_PAYLOAD_TOO_LONG,
15 HOST_ERROR_SUCCESS, HOST_ERROR_TOO_MANY_TOPICS, HOST_ERROR_TOPIC_TOO_LONG,
16 },
17 flags::ReturnFlags,
18 keyspace::{Keyspace, KeyspaceTag},
19};
20use casper_executor_wasm_interface::{
21 executor::{ExecuteRequestBuilder, ExecuteResult, ExecutionKind, Executor},
22 u32_from_host_result, Caller, InternalHostError, VMError, VMResult,
23};
24use casper_storage::{
25 global_state::GlobalStateReader,
26 tracking_copy::{TrackingCopyEntityExt, TrackingCopyError, TrackingCopyExt},
27};
28use casper_types::{
29 account::AccountHash,
30 addressable_entity::{ActionThresholds, AssociatedKeys, MessageTopicError, NamedKeyAddr},
31 bytesrepr::ToBytes,
32 contract_messages::{Message, MessageAddr, MessagePayload, MessageTopicSummary},
33 AddressableEntity, BlockGlobalAddr, BlockHash, BlockTime, ByteCode, ByteCodeAddr, ByteCodeHash,
34 ByteCodeKind, CLType, CLValue, ContractRuntimeTag, Digest, EntityAddr, EntityEntryPoint,
35 EntityKind, EntryPointAccess, EntryPointAddr, EntryPointPayment, EntryPointType,
36 EntryPointValue, HashAddr, HostFunctionV2, Key, Package, PackageHash, ProtocolVersion,
37 StoredValue, URef, U512,
38};
39use either::Either;
40use num_derive::FromPrimitive;
41use num_traits::FromPrimitive;
42use tracing::{error, info, warn};
43
44use crate::{
45 abi::{CreateResult, ReadInfo},
46 context::Context,
47 system::{self, MintArgs, MintTransferArgs},
48};
49
50#[derive(Debug, Copy, Clone, FromPrimitive, PartialEq)]
51enum EntityKindTag {
52 Account = 0,
53 Contract = 1,
54}
55
56pub trait FallibleInto<T> {
57 fn try_into_wrapped(self) -> VMResult<T>;
58}
59
60impl<From, To> FallibleInto<To> for From
61where
62 To: TryFrom<From>,
63{
64 fn try_into_wrapped(self) -> VMResult<To> {
65 To::try_from(self).map_err(|_| VMError::Internal(InternalHostError::TypeConversion))
66 }
67}
68
69fn charge_gas_storage<S: GlobalStateReader, E: Executor>(
71 caller: &mut impl Caller<Context = Context<S, E>>,
72 size_bytes: usize,
73) -> VMResult<()> {
74 let storage_costs = &caller.context().storage_costs;
75 let gas_cost = storage_costs.calculate_gas_cost(size_bytes);
76 let value: u64 = gas_cost.value().try_into().map_err(|_| VMError::OutOfGas)?;
77 caller.consume_gas(value)?;
78 Ok(())
79}
80
81fn charge_host_function_call<S, E, const N: usize>(
83 caller: &mut impl Caller<Context = Context<S, E>>,
84 host_function: &HostFunctionV2<[u64; N]>,
85 weights: [u64; N],
86) -> VMResult<()>
87where
88 S: GlobalStateReader,
89 E: Executor,
90{
91 let Some(cost) = host_function.calculate_gas_cost(weights) else {
92 return Err(VMError::OutOfGas);
94 };
95
96 caller.consume_gas(cost.value().as_u64())?;
97 Ok(())
98}
99
100fn metered_write<S: GlobalStateReader, E: Executor>(
102 caller: &mut impl Caller<Context = Context<S, E>>,
103 key: Key,
104 value: StoredValue,
105) -> VMResult<()> {
106 charge_gas_storage(caller, value.serialized_length())?;
107 caller.context_mut().tracking_copy.write(key, value);
108 Ok(())
109}
110
111pub fn casper_write<S: GlobalStateReader, E: Executor>(
113 mut caller: impl Caller<Context = Context<S, E>>,
114 key_space: u64,
115 key_ptr: u32,
116 key_size: u32,
117 value_ptr: u32,
118 value_size: u32,
119) -> VMResult<u32> {
120 let write_cost = caller.context().config.host_function_costs().write;
121 charge_host_function_call(
122 &mut caller,
123 &write_cost,
124 [
125 key_space,
126 u64::from(key_ptr),
127 u64::from(key_size),
128 u64::from(value_ptr),
129 u64::from(value_size),
130 ],
131 )?;
132
133 let keyspace_tag = match KeyspaceTag::from_u64(key_space) {
134 Some(keyspace_tag) => keyspace_tag,
135 None => {
136 return Ok(HOST_ERROR_NOT_FOUND);
138 }
139 };
140
141 let key_payload_bytes = caller.memory_read(key_ptr, key_size.try_into_wrapped()?)?;
142
143 let keyspace = match keyspace_tag {
144 KeyspaceTag::State => Keyspace::State,
145 KeyspaceTag::Context => Keyspace::Context(&key_payload_bytes),
146 KeyspaceTag::NamedKey => {
147 let key_name = match std::str::from_utf8(&key_payload_bytes) {
148 Ok(key_name) => key_name,
149 Err(_) => {
150 return Ok(HOST_ERROR_INVALID_DATA);
152 }
153 };
154
155 Keyspace::NamedKey(key_name)
156 }
157 KeyspaceTag::PaymentInfo => {
158 let key_name = match std::str::from_utf8(&key_payload_bytes) {
159 Ok(key_name) => key_name,
160 Err(_) => {
161 return Ok(HOST_ERROR_INVALID_DATA);
162 }
163 };
164
165 if !caller.has_export(key_name) {
166 return Ok(HOST_ERROR_NOT_FOUND);
168 }
169
170 Keyspace::PaymentInfo(key_name)
171 }
172 };
173
174 let global_state_key = match keyspace_to_global_state_key(caller.context(), keyspace) {
175 Some(global_state_key) => global_state_key,
176 None => {
177 return Ok(HOST_ERROR_NOT_FOUND);
179 }
180 };
181
182 let value = caller.memory_read(value_ptr, value_size.try_into_wrapped()?)?;
183
184 let stored_value = match keyspace {
185 Keyspace::State | Keyspace::Context(_) | Keyspace::NamedKey(_) => {
186 StoredValue::RawBytes(value)
187 }
188 Keyspace::PaymentInfo(_) => {
189 let entry_point_payment = match value.as_slice() {
190 [ENTRY_POINT_PAYMENT_CALLER] => EntryPointPayment::Caller,
191 [ENTRY_POINT_PAYMENT_DIRECT_INVOCATION_ONLY] => {
192 EntryPointPayment::DirectInvocationOnly
193 }
194 [ENTRY_POINT_PAYMENT_SELF_ONWARD] => EntryPointPayment::SelfOnward,
195 _ => {
196 return Ok(HOST_ERROR_INVALID_INPUT);
198 }
199 };
200
201 let entry_point = EntityEntryPoint::new(
202 "_",
203 Vec::new(),
204 CLType::Unit,
205 EntryPointAccess::Public,
206 EntryPointType::Called,
207 entry_point_payment,
208 );
209 let entry_point_value = EntryPointValue::V1CasperVm(entry_point);
210 StoredValue::EntryPoint(entry_point_value)
211 }
212 };
213
214 metered_write(&mut caller, global_state_key, stored_value)?;
215
216 Ok(HOST_ERROR_SUCCESS)
217}
218
219pub fn casper_remove<S: GlobalStateReader, E: Executor>(
228 mut caller: impl Caller<Context = Context<S, E>>,
229 key_space: u64,
230 key_ptr: u32,
231 key_size: u32,
232) -> VMResult<u32> {
233 let write_cost = caller.context().config.host_function_costs().remove;
234 charge_host_function_call(
235 &mut caller,
236 &write_cost,
237 [key_space, u64::from(key_ptr), u64::from(key_size)],
238 )?;
239
240 let keyspace_tag = match KeyspaceTag::from_u64(key_space) {
241 Some(keyspace_tag) => keyspace_tag,
242 None => {
243 return Ok(HOST_ERROR_NOT_FOUND);
245 }
246 };
247
248 let key_payload_bytes = caller.memory_read(key_ptr, key_size.try_into_wrapped()?)?;
249
250 let keyspace = match keyspace_tag {
251 KeyspaceTag::State => Keyspace::State,
252 KeyspaceTag::Context => Keyspace::Context(&key_payload_bytes),
253 KeyspaceTag::NamedKey => {
254 let key_name = match std::str::from_utf8(&key_payload_bytes) {
255 Ok(key_name) => key_name,
256 Err(_) => {
257 return Ok(HOST_ERROR_INVALID_DATA);
259 }
260 };
261
262 Keyspace::NamedKey(key_name)
263 }
264 KeyspaceTag::PaymentInfo => {
265 let key_name = match std::str::from_utf8(&key_payload_bytes) {
266 Ok(key_name) => key_name,
267 Err(_) => {
268 return Ok(HOST_ERROR_INVALID_DATA);
269 }
270 };
271
272 if !caller.has_export(key_name) {
273 return Ok(HOST_ERROR_NOT_FOUND);
275 }
276
277 Keyspace::PaymentInfo(key_name)
278 }
279 };
280
281 let global_state_key = match keyspace_to_global_state_key(caller.context(), keyspace) {
282 Some(global_state_key) => global_state_key,
283 None => {
284 return Ok(HOST_ERROR_NOT_FOUND);
286 }
287 };
288
289 let global_state_read_result = caller.context_mut().tracking_copy.read(&global_state_key);
290 match global_state_read_result {
291 Ok(Some(_stored_value)) => {
292 caller.context_mut().tracking_copy.prune(global_state_key);
294 }
295 Ok(None) => {
296 return Ok(HOST_ERROR_NOT_FOUND);
298 }
299 Err(error) => {
300 error!(
306 ?error,
307 ?global_state_key,
308 "Error while attempting a read before removing value; aborting"
309 );
310 panic!("Error while attempting a read before removing value; aborting key={global_state_key:?} error={error:?}")
311 }
312 }
313
314 Ok(HOST_ERROR_SUCCESS)
315}
316
317pub fn casper_print<S: GlobalStateReader, E: Executor>(
318 mut caller: impl Caller<Context = Context<S, E>>,
319 message_ptr: u32,
320 message_size: u32,
321) -> VMResult<()> {
322 let print_cost = caller.context().config.host_function_costs().print;
323 charge_host_function_call(
324 &mut caller,
325 &print_cost,
326 [u64::from(message_ptr), u64::from(message_size)],
327 )?;
328
329 let vec = caller.memory_read(message_ptr, message_size.try_into_wrapped()?)?;
330 let msg = String::from_utf8_lossy(&vec);
331 eprintln!("⛓️ {msg}");
332 Ok(())
333}
334
335pub fn casper_read<S: GlobalStateReader, E: Executor>(
337 mut caller: impl Caller<Context = Context<S, E>>,
338 key_tag: u64,
339 key_ptr: u32,
340 key_size: u32,
341 info_ptr: u32,
342 cb_alloc: u32,
343 alloc_ctx: u32,
344) -> VMResult<u32> {
345 let read_cost = caller.context().config.host_function_costs().read;
346 charge_host_function_call(
347 &mut caller,
348 &read_cost,
349 [
350 key_tag,
351 u64::from(key_ptr),
352 u64::from(key_size),
353 u64::from(info_ptr),
354 u64::from(cb_alloc),
355 u64::from(alloc_ctx),
356 ],
357 )?;
358
359 let keyspace_tag = match KeyspaceTag::from_u64(key_tag) {
360 Some(keyspace_tag) => keyspace_tag,
361 None => {
362 return Ok(HOST_ERROR_INVALID_INPUT);
364 }
365 };
366
367 let key_payload_bytes = caller.memory_read(key_ptr, key_size.try_into_wrapped()?)?;
370
371 let keyspace = match keyspace_tag {
372 KeyspaceTag::State => Keyspace::State,
373 KeyspaceTag::Context => Keyspace::Context(&key_payload_bytes),
374 KeyspaceTag::NamedKey => {
375 let key_name = match std::str::from_utf8(&key_payload_bytes) {
376 Ok(key_name) => key_name,
377 Err(_) => {
378 return Ok(HOST_ERROR_INVALID_DATA);
379 }
380 };
381
382 Keyspace::NamedKey(key_name)
383 }
384 KeyspaceTag::PaymentInfo => {
385 let key_name = match std::str::from_utf8(&key_payload_bytes) {
386 Ok(key_name) => key_name,
387 Err(_) => {
388 return Ok(HOST_ERROR_INVALID_DATA);
389 }
390 };
391 if !caller.has_export(key_name) {
392 return Ok(HOST_ERROR_NOT_FOUND);
394 }
395 Keyspace::PaymentInfo(key_name)
396 }
397 };
398
399 let global_state_key = match keyspace_to_global_state_key(caller.context(), keyspace) {
400 Some(global_state_key) => global_state_key,
401 None => {
402 return Ok(HOST_ERROR_NOT_FOUND);
404 }
405 };
406 let global_state_read_result = caller.context_mut().tracking_copy.read(&global_state_key);
407
408 let global_state_raw_bytes: Cow<[u8]> = match global_state_read_result {
409 Ok(Some(StoredValue::RawBytes(raw_bytes))) => Cow::Owned(raw_bytes),
410 Ok(Some(StoredValue::EntryPoint(EntryPointValue::V1CasperVm(entry_point)))) => {
411 match entry_point.entry_point_payment() {
412 EntryPointPayment::Caller => Cow::Borrowed(&[ENTRY_POINT_PAYMENT_CALLER]),
413 EntryPointPayment::DirectInvocationOnly => {
414 Cow::Borrowed(&[ENTRY_POINT_PAYMENT_DIRECT_INVOCATION_ONLY])
415 }
416 EntryPointPayment::SelfOnward => Cow::Borrowed(&[ENTRY_POINT_PAYMENT_SELF_ONWARD]),
417 }
418 }
419 Ok(Some(stored_value)) => {
420 todo!("Unsupported {stored_value:?}")
427 }
428 Ok(None) => return Ok(HOST_ERROR_NOT_FOUND), Err(error) => {
430 error!(?error, "Error while reading from storage; aborting");
436 panic!("Error while reading from storage; aborting key={global_state_key:?} error={error:?}")
437 }
438 };
439
440 let out_ptr: u32 = if cb_alloc != 0 {
441 caller.alloc(cb_alloc, global_state_raw_bytes.len(), alloc_ctx)?
442 } else {
443 alloc_ctx
445 };
446
447 let read_info = ReadInfo {
448 data: out_ptr,
449 data_size: global_state_raw_bytes.len().try_into_wrapped()?,
450 };
451
452 let read_info_bytes = safe_transmute::transmute_one_to_bytes(&read_info);
453 caller.memory_write(info_ptr, read_info_bytes)?;
454 if out_ptr != 0 {
455 caller.memory_write(out_ptr, &global_state_raw_bytes)?;
456 }
457 Ok(HOST_ERROR_SUCCESS)
458}
459
460fn keyspace_to_global_state_key<S: GlobalStateReader, E: Executor>(
461 context: &Context<S, E>,
462 keyspace: Keyspace<'_>,
463) -> Option<Key> {
464 let entity_addr = context_to_entity_addr(context);
465
466 match keyspace {
467 Keyspace::State => Some(Key::State(entity_addr)),
468 Keyspace::Context(bytes) => {
469 let digest = Digest::hash(bytes);
470 Some(casper_types::Key::NamedKey(
471 NamedKeyAddr::new_named_key_entry(entity_addr, digest.value()),
472 ))
473 }
474 Keyspace::NamedKey(payload) => {
475 let digest = Digest::hash(payload.as_bytes());
476 Some(casper_types::Key::NamedKey(
477 NamedKeyAddr::new_named_key_entry(entity_addr, digest.value()),
478 ))
479 }
480 Keyspace::PaymentInfo(payload) => {
481 let entry_point_addr =
482 EntryPointAddr::new_v1_entry_point_addr(entity_addr, payload).ok()?;
483 Some(Key::EntryPoint(entry_point_addr))
484 }
485 }
486}
487
488fn context_to_entity_addr<S: GlobalStateReader, E: Executor>(
489 context: &Context<S, E>,
490) -> EntityAddr {
491 match context.callee {
492 Key::Account(account_hash) => EntityAddr::new_account(account_hash.value()),
493 Key::SmartContract(smart_contract_addr) => {
494 EntityAddr::new_smart_contract(smart_contract_addr)
495 }
496 _ => {
497 panic!("Unexpected callee variant: {:?}", context.callee)
499 }
500 }
501}
502
503pub fn casper_copy_input<S: GlobalStateReader, E: Executor>(
504 mut caller: impl Caller<Context = Context<S, E>>,
505 cb_alloc: u32,
506 alloc_ctx: u32,
507) -> VMResult<u32> {
508 let input = caller.context().input.clone();
509
510 let out_ptr: u32 = if cb_alloc != 0 {
511 caller.alloc(cb_alloc, input.len(), alloc_ctx)?
512 } else {
513 alloc_ctx
515 };
516
517 let copy_input_cost = caller.context().config.host_function_costs().copy_input;
518 charge_host_function_call(
519 &mut caller,
520 ©_input_cost,
521 [
522 u64::from(out_ptr),
523 input
524 .len()
525 .try_into()
526 .expect("usize is at least the same size as u64"),
527 ],
528 )?;
529
530 if out_ptr == 0 {
531 Ok(out_ptr)
532 } else {
533 caller.memory_write(out_ptr, &input)?;
534 Ok(out_ptr + (input.len() as u32))
535 }
536}
537
538pub fn casper_return<S: GlobalStateReader, E: Executor>(
540 mut caller: impl Caller<Context = Context<S, E>>,
541 flags: u32,
542 data_ptr: u32,
543 data_len: u32,
544) -> VMResult<()> {
545 let ret_cost = caller.context().config.host_function_costs().ret;
546 charge_host_function_call(
547 &mut caller,
548 &ret_cost,
549 [u64::from(data_ptr), u64::from(data_len)],
550 )?;
551
552 let flags = ReturnFlags::from_bits_retain(flags);
553 let data = if data_ptr == 0 {
554 None
555 } else {
556 let data = caller
557 .memory_read(data_ptr, data_len.try_into_wrapped()?)
558 .map(Bytes::from)?;
559 Some(data)
560 };
561 Err(VMError::Return { flags, data })
562}
563
564#[allow(clippy::too_many_arguments)]
565pub fn casper_create<S: GlobalStateReader + 'static, E: Executor + 'static>(
566 mut caller: impl Caller<Context = Context<S, E>>,
567 code_ptr: u32,
568 code_len: u32,
569 transferred_value: u64,
570 entry_point_ptr: u32,
571 entry_point_len: u32,
572 input_ptr: u32,
573 input_len: u32,
574 seed_ptr: u32,
575 seed_len: u32,
576 result_ptr: u32,
577) -> VMResult<u32> {
578 let create_cost = caller.context().config.host_function_costs().create;
579 charge_host_function_call(
580 &mut caller,
581 &create_cost,
582 [
583 u64::from(code_ptr),
584 u64::from(code_len),
585 transferred_value,
586 u64::from(entry_point_ptr),
587 u64::from(entry_point_len),
588 u64::from(input_ptr),
589 u64::from(input_len),
590 u64::from(seed_ptr),
591 u64::from(seed_len),
592 u64::from(result_ptr),
593 ],
594 )?;
595
596 let code = if code_ptr != 0 {
597 caller
598 .memory_read(code_ptr, code_len as usize)
599 .map(Bytes::from)?
600 } else {
601 caller.bytecode()
602 };
603
604 let seed = if seed_ptr != 0 {
605 if seed_len != 32 {
606 return Ok(CALLEE_NOT_CALLABLE);
607 }
608 let seed_bytes = caller.memory_read(seed_ptr, seed_len as usize)?;
609 let seed_bytes: [u8; 32] = seed_bytes.try_into().unwrap(); Some(seed_bytes)
611 } else {
612 None
613 };
614
615 let constructor_entry_point = {
617 let entry_point_ptr = NonZeroU32::new(entry_point_ptr);
618 match entry_point_ptr {
619 Some(entry_point_ptr) => {
620 let entry_point_bytes =
621 caller.memory_read(entry_point_ptr.get(), entry_point_len as _)?;
622 match String::from_utf8(entry_point_bytes) {
623 Ok(entry_point) => Some(entry_point),
624 Err(utf8_error) => {
625 error!(%utf8_error, "entry point name is not a valid utf-8 string; unable to call");
626 return Ok(CALLEE_NOT_CALLABLE);
627 }
628 }
629 }
630 None => {
631 None
633 }
634 }
635 };
636
637 let input_data: Option<Bytes> = if input_ptr == 0 {
639 None
640 } else {
641 let input_data = caller.memory_read(input_ptr, input_len as _)?.into();
642 Some(input_data)
643 };
644
645 let bytecode_hash = chain_utils::compute_wasm_bytecode_hash(&code);
646
647 let bytecode = ByteCode::new(ByteCodeKind::V2CasperWasm, code.clone().into());
648 let bytecode_addr = ByteCodeAddr::V2CasperWasm(bytecode_hash);
649
650 let mut smart_contract_package = Package::default();
652
653 let protocol_version = ProtocolVersion::V2_0_0;
654 let protocol_version_major = protocol_version.value().major;
655
656 let callee_addr = context_to_entity_addr(caller.context()).value();
657
658 let smart_contract_addr: HashAddr = chain_utils::compute_predictable_address(
659 caller.context().chain_name.as_bytes(),
660 callee_addr,
661 bytecode_hash,
662 seed,
663 );
664
665 smart_contract_package.insert_entity_version(
666 protocol_version_major,
667 EntityAddr::SmartContract(smart_contract_addr),
668 );
669
670 if caller
671 .context_mut()
672 .tracking_copy
673 .read(&Key::SmartContract(smart_contract_addr))
674 .map_err(|_| VMError::Internal(InternalHostError::TrackingCopy))?
675 .is_some()
676 {
677 return VMResult::Err(VMError::Internal(InternalHostError::ContractAlreadyExists));
678 }
679
680 metered_write(
681 &mut caller,
682 Key::SmartContract(smart_contract_addr),
683 StoredValue::SmartContract(smart_contract_package),
684 )?;
685
686 metered_write(
688 &mut caller,
689 Key::ByteCode(bytecode_addr),
690 StoredValue::ByteCode(bytecode),
691 )?;
692
693 let entity_addr = EntityAddr::SmartContract(smart_contract_addr);
696 let addressable_entity_key = Key::AddressableEntity(entity_addr);
697
698 let address_generator = Arc::clone(&caller.context().address_generator);
700 let transaction_hash = caller.context().transaction_hash;
701 let main_purse: URef = match system::mint_mint(
702 &mut caller.context_mut().tracking_copy,
703 transaction_hash,
704 address_generator,
705 MintArgs {
706 initial_balance: U512::zero(),
707 },
708 ) {
709 Ok(uref) => uref,
710 Err(mint_error) => {
711 error!(?mint_error, "Failed to create a purse");
712 return Ok(CALLEE_TRAPPED);
713 }
714 };
715
716 let addressable_entity = AddressableEntity::new(
717 PackageHash::new(smart_contract_addr),
718 ByteCodeHash::new(bytecode_hash),
719 ProtocolVersion::V2_0_0,
720 main_purse,
721 AssociatedKeys::default(),
722 ActionThresholds::default(),
723 EntityKind::SmartContract(ContractRuntimeTag::VmCasperV2),
724 );
725
726 metered_write(
727 &mut caller,
728 addressable_entity_key,
729 StoredValue::AddressableEntity(addressable_entity),
730 )?;
731
732 let _initial_state = match constructor_entry_point {
733 Some(entry_point_name) => {
734 let gas_limit = caller
736 .gas_consumed()
737 .try_into_remaining()
738 .map_err(|_| InternalHostError::TypeConversion)?;
739
740 let execute_request = ExecuteRequestBuilder::default()
741 .with_initiator(caller.context().initiator)
742 .with_caller_key(caller.context().callee)
743 .with_gas_limit(gas_limit)
744 .with_target(ExecutionKind::Stored {
745 address: smart_contract_addr,
746 entry_point: entry_point_name.clone(),
747 })
748 .with_input(input_data.unwrap_or_default())
749 .with_transferred_value(transferred_value)
750 .with_transaction_hash(caller.context().transaction_hash)
751 .with_shared_address_generator(Arc::clone(&caller.context().address_generator))
754 .with_chain_name(caller.context().chain_name.clone())
755 .with_block_time(caller.context().block_time)
756 .with_state_hash(Digest::from_raw([0; 32])) .with_block_height(1) .with_parent_block_hash(BlockHash::new(Digest::from_raw([0; 32]))) .build()
760 .map_err(|_| InternalHostError::ExecuteRequestBuildFailure)?;
761
762 let tracking_copy_for_ctor = caller.context().tracking_copy.fork2();
763
764 match caller
765 .context()
766 .executor
767 .execute(tracking_copy_for_ctor, execute_request)
768 {
769 Ok(ExecuteResult {
770 host_error,
771 output,
772 gas_usage,
773 effects,
774 cache,
775 messages,
776 }) => {
777 caller.consume_gas(gas_usage.gas_spent())?;
779
780 if let Some(host_error) = host_error {
781 return Ok(host_error.into_u32());
782 }
783
784 caller
785 .context_mut()
786 .tracking_copy
787 .apply_changes(effects, cache, messages);
788
789 output
790 }
791 Err(execute_error) => {
792 error!(?execute_error, "Failed to execute constructor entry point");
795 return Err(VMError::Execute(execute_error));
796 }
797 }
798 }
799 None => None,
800 };
801
802 let create_result = CreateResult {
803 package_address: smart_contract_addr,
804 };
805
806 let create_result_bytes = safe_transmute::transmute_one_to_bytes(&create_result);
807
808 debug_assert_eq!(
809 safe_transmute::transmute_one(create_result_bytes),
810 Ok(create_result),
811 "Sanity check", );
813
814 caller.memory_write(result_ptr, create_result_bytes)?;
815
816 Ok(CALLEE_SUCCEEDED)
817}
818
819#[allow(clippy::too_many_arguments)]
820pub fn casper_call<S: GlobalStateReader + 'static, E: Executor + 'static>(
821 mut caller: impl Caller<Context = Context<S, E>>,
822 address_ptr: u32,
823 address_len: u32,
824 transferred_value: u64,
825 entry_point_ptr: u32,
826 entry_point_len: u32,
827 input_ptr: u32,
828 input_len: u32,
829 cb_alloc: u32,
830 cb_ctx: u32,
831) -> VMResult<u32> {
832 let call_cost = caller.context().config.host_function_costs().call;
833 charge_host_function_call(
834 &mut caller,
835 &call_cost,
836 [
837 u64::from(address_ptr),
838 u64::from(address_len),
839 transferred_value,
840 u64::from(entry_point_ptr),
841 u64::from(entry_point_len),
842 u64::from(input_ptr),
843 u64::from(input_len),
844 u64::from(cb_alloc),
845 u64::from(cb_ctx),
846 ],
847 )?;
848
849 let address = caller.memory_read(address_ptr, address_len as _)?;
860 let smart_contract_addr: HashAddr = address.try_into_wrapped()?;
861
862 let input_data: Bytes = caller.memory_read(input_ptr, input_len as _)?.into();
863
864 let entry_point = {
865 let entry_point_bytes = caller.memory_read(entry_point_ptr, entry_point_len as _)?;
866 match String::from_utf8(entry_point_bytes) {
867 Ok(entry_point) => entry_point,
868 Err(utf8_error) => {
869 error!(%utf8_error, "entry point name is not a valid utf-8 string; unable to call");
870 return Ok(CALLEE_NOT_CALLABLE);
871 }
872 }
873 };
874
875 let tracking_copy = caller.context().tracking_copy.fork2();
876
877 let gas_limit = caller
879 .gas_consumed()
880 .try_into_remaining()
881 .map_err(|_| InternalHostError::TypeConversion)?;
882
883 let execute_request = ExecuteRequestBuilder::default()
884 .with_initiator(caller.context().initiator)
885 .with_caller_key(caller.context().callee)
886 .with_gas_limit(gas_limit)
887 .with_target(ExecutionKind::Stored {
888 address: smart_contract_addr,
889 entry_point: entry_point.clone(),
890 })
891 .with_transferred_value(transferred_value)
892 .with_input(input_data)
893 .with_transaction_hash(caller.context().transaction_hash)
894 .with_shared_address_generator(Arc::clone(&caller.context().address_generator))
897 .with_chain_name(caller.context().chain_name.clone())
898 .with_block_time(caller.context().block_time)
899 .with_state_hash(Digest::from_raw([0; 32])) .with_block_height(1) .with_parent_block_hash(BlockHash::new(Digest::from_raw([0; 32]))) .build()
903 .map_err(|_| InternalHostError::ExecuteRequestBuildFailure)?;
904
905 let (gas_usage, host_result) = match caller
906 .context()
907 .executor
908 .execute(tracking_copy, execute_request)
909 {
910 Ok(ExecuteResult {
911 host_error,
912 output,
913 gas_usage,
914 effects,
915 cache,
916 messages,
917 }) => {
918 if let Some(output) = output {
919 let out_ptr: u32 = if cb_alloc != 0 {
920 caller.alloc(cb_alloc, output.len(), cb_ctx)?
921 } else {
922 cb_ctx
924 };
925
926 if out_ptr != 0 {
927 caller.memory_write(out_ptr, &output)?;
928 }
929 }
930
931 let host_result = match host_error {
932 Some(host_error) => Err(host_error),
933 None => {
934 caller
935 .context_mut()
936 .tracking_copy
937 .apply_changes(effects, cache, messages);
938 Ok(())
939 }
940 };
941
942 (gas_usage, host_result)
943 }
944 Err(execute_error) => {
945 error!(
946 ?execute_error,
947 ?smart_contract_addr,
948 ?entry_point,
949 "Failed to execute entry point"
950 );
951 return Err(VMError::Execute(execute_error));
952 }
953 };
954
955 let gas_spent = gas_usage
956 .gas_limit()
957 .checked_sub(gas_usage.remaining_points())
958 .ok_or(InternalHostError::RemainingGasExceedsGasLimit)?;
959
960 caller.consume_gas(gas_spent)?;
961
962 Ok(u32_from_host_result(host_result))
963}
964
965pub fn casper_env_balance<S: GlobalStateReader, E: Executor>(
966 mut caller: impl Caller<Context = Context<S, E>>,
967 entity_kind: u32,
968 entity_addr_ptr: u32,
969 entity_addr_len: u32,
970 output_ptr: u32,
971) -> VMResult<u32> {
972 let balance_cost = caller.context().config.host_function_costs().env_balance;
973 charge_host_function_call(
974 &mut caller,
975 &balance_cost,
976 [
977 u64::from(entity_kind),
978 u64::from(entity_addr_ptr),
979 u64::from(entity_addr_len),
980 u64::from(output_ptr),
981 ],
982 )?;
983
984 let entity_key = match EntityKindTag::from_u32(entity_kind) {
985 Some(EntityKindTag::Account) => {
986 if entity_addr_len != 32 {
987 return Ok(HOST_ERROR_SUCCESS);
988 }
989 let entity_addr = caller.memory_read(entity_addr_ptr, entity_addr_len as usize)?;
990 let account_hash: AccountHash = AccountHash::new(entity_addr.try_into_wrapped()?);
991
992 let account_key = Key::Account(account_hash);
993 match caller.context_mut().tracking_copy.read(&account_key) {
994 Ok(Some(StoredValue::CLValue(clvalue))) => {
995 let addressible_entity_key = clvalue
996 .into_t::<Key>()
997 .map_err(|_| InternalHostError::TypeConversion)?;
998 Either::Right(addressible_entity_key)
999 }
1000 Ok(Some(StoredValue::Account(account))) => Either::Left(account.main_purse()),
1001 Ok(Some(other_entity)) => {
1002 error!("Unexpected entity type: {other_entity:?}");
1003 return Err(InternalHostError::UnexpectedEntityKind.into());
1004 }
1005 Ok(None) => return Ok(HOST_ERROR_SUCCESS),
1006 Err(error) => {
1007 error!("Error while reading from storage; aborting key={account_key:?} error={error:?}");
1008 return Err(InternalHostError::TrackingCopy.into());
1009 }
1010 }
1011 }
1012 Some(EntityKindTag::Contract) => {
1013 if entity_addr_len != 32 {
1014 return Ok(HOST_ERROR_SUCCESS);
1015 }
1016 let hash_bytes = caller.memory_read(entity_addr_ptr, entity_addr_len as usize)?;
1017 let hash_bytes: [u8; 32] = hash_bytes.try_into().unwrap(); let smart_contract_key = Key::SmartContract(hash_bytes);
1020 match caller.context_mut().tracking_copy.read(&smart_contract_key) {
1021 Ok(Some(StoredValue::SmartContract(smart_contract_package))) => {
1022 match smart_contract_package.versions().latest() {
1023 Some(addressible_entity_hash) => {
1024 let key = Key::AddressableEntity(EntityAddr::SmartContract(
1025 addressible_entity_hash.value(),
1026 ));
1027 Either::Right(key)
1028 }
1029 None => {
1030 warn!(
1031 ?smart_contract_key,
1032 "Unable to find latest addressible entity hash for contract"
1033 );
1034 return Ok(HOST_ERROR_SUCCESS);
1035 }
1036 }
1037 }
1038 Ok(Some(_)) => {
1039 return Ok(HOST_ERROR_SUCCESS);
1040 }
1041 Ok(None) => {
1042 return Ok(HOST_ERROR_SUCCESS);
1044 }
1045 Err(error) => {
1046 error!(
1047 hash_bytes = base16::encode_lower(&hash_bytes),
1048 ?error,
1049 "Error while reading from storage; aborting"
1050 );
1051 panic!("Error while reading from storage")
1052 }
1053 }
1054 }
1055 None => return Ok(HOST_ERROR_SUCCESS),
1056 };
1057
1058 let purse = match entity_key {
1059 Either::Left(main_purse) => main_purse,
1060 Either::Right(indirect_entity_key) => {
1061 match caller
1062 .context_mut()
1063 .tracking_copy
1064 .read(&indirect_entity_key)
1065 {
1066 Ok(Some(StoredValue::AddressableEntity(addressable_entity))) => {
1067 addressable_entity.main_purse()
1068 }
1069 Ok(Some(other_entity)) => {
1070 panic!("Unexpected entity type: {other_entity:?}")
1071 }
1072 Ok(None) => panic!("Key not found while checking balance"), Err(error) => {
1074 panic!("Error while reading from storage; aborting key={entity_key:?} error={error:?}")
1075 }
1076 }
1077 }
1078 };
1079
1080 let total_balance = caller
1081 .context_mut()
1082 .tracking_copy
1083 .get_total_balance(Key::URef(purse))
1084 .map_err(|_| InternalHostError::TotalBalanceReadFailure)?;
1085
1086 let total_balance: u64 = total_balance
1087 .value()
1088 .try_into()
1089 .map_err(|_| InternalHostError::TotalBalanceOverflow)?;
1090
1091 caller.memory_write(output_ptr, &total_balance.to_le_bytes())?;
1092 Ok(HOST_ERROR_NOT_FOUND)
1093}
1094
1095pub fn casper_transfer<S: GlobalStateReader + 'static, E: Executor>(
1096 mut caller: impl Caller<Context = Context<S, E>>,
1097 entity_addr_ptr: u32,
1098 entity_addr_len: u32,
1099 amount_ptr: u32,
1100) -> VMResult<u32> {
1101 let transfer_cost = caller.context().config.host_function_costs().transfer;
1102 charge_host_function_call(
1103 &mut caller,
1104 &transfer_cost,
1105 [
1106 u64::from(entity_addr_ptr),
1107 u64::from(entity_addr_len),
1108 u64::from(amount_ptr),
1109 ],
1110 )?;
1111
1112 if entity_addr_len != 32 {
1113 return Ok(u32_from_host_result(Err(CallError::NotCallable)));
1115 }
1116
1117 let amount = {
1118 let mut amount_bytes = [0u8; 8];
1119 caller.memory_read_into(amount_ptr, &mut amount_bytes)?;
1120 u64::from_le_bytes(amount_bytes)
1121 };
1122
1123 let (target_entity_addr, _runtime_footprint) = {
1124 let entity_addr = caller.memory_read(entity_addr_ptr, entity_addr_len as usize)?;
1125 debug_assert_eq!(entity_addr.len(), 32);
1126
1127 let account_hash: AccountHash = AccountHash::new(entity_addr.try_into().unwrap());
1129
1130 let protocol_version = ProtocolVersion::V2_0_0;
1131 let (entity_addr, runtime_footprint) = match caller
1132 .context_mut()
1133 .tracking_copy
1134 .runtime_footprint_by_account_hash(protocol_version, account_hash)
1135 {
1136 Ok((entity_addr, runtime_footprint)) => (entity_addr, runtime_footprint),
1137 Err(TrackingCopyError::KeyNotFound(key)) => {
1138 warn!(?key, "Account not found");
1139 return Ok(u32_from_host_result(Err(CallError::NotCallable)));
1140 }
1141 Err(error) => {
1142 error!(?error, "Error while reading from storage; aborting");
1143 panic!("Error while reading from storage")
1144 }
1145 };
1146 (entity_addr, runtime_footprint)
1147 };
1148
1149 let callee_addressable_entity_key = match caller.context().callee {
1150 callee_account_key @ Key::Account(_account_hash) => {
1151 match caller.context_mut().tracking_copy.read(&callee_account_key) {
1152 Ok(Some(StoredValue::CLValue(indirect))) => {
1153 indirect
1155 .into_t::<Key>()
1156 .map_err(|_| InternalHostError::TypeConversion)?
1157 }
1158 Ok(Some(other)) => panic!("should be cl value but got {other:?}"),
1159 Ok(None) => return Ok(u32_from_host_result(Err(CallError::NotCallable))),
1160 Err(error) => {
1161 error!(
1162 ?error,
1163 ?callee_account_key,
1164 "Error while reading from storage; aborting"
1165 );
1166 panic!("Error while reading from storage")
1167 }
1168 }
1169 }
1170 smart_contract_key @ Key::SmartContract(_) => {
1171 match caller.context_mut().tracking_copy.read(&smart_contract_key) {
1172 Ok(Some(StoredValue::SmartContract(smart_contract_package))) => {
1173 match smart_contract_package.versions().latest() {
1174 Some(addressible_entity_hash) => Key::AddressableEntity(
1175 EntityAddr::SmartContract(addressible_entity_hash.value()),
1176 ),
1177 None => {
1178 warn!(
1179 ?smart_contract_key,
1180 "Unable to find latest addressible entity hash for contract"
1181 );
1182 return Ok(u32_from_host_result(Err(CallError::NotCallable)));
1183 }
1184 }
1185 }
1186 Ok(Some(other)) => panic!("should be smart contract but got {other:?}"),
1187 Ok(None) => return Ok(u32_from_host_result(Err(CallError::NotCallable))),
1188 Err(error) => {
1189 error!(
1190 ?error,
1191 ?smart_contract_key,
1192 "Error while reading from storage; aborting"
1193 );
1194 panic!("Error while reading from storage")
1195 }
1196 }
1197 }
1198 other => panic!("should be account or smart contract but got {other:?}"),
1199 };
1200
1201 let callee_stored_value = caller
1202 .context_mut()
1203 .tracking_copy
1204 .read(&callee_addressable_entity_key)
1205 .map_err(|_| InternalHostError::TrackingCopy)?
1206 .ok_or(InternalHostError::AccountRecordNotFound)?;
1207 let callee_addressable_entity = callee_stored_value
1208 .into_addressable_entity()
1209 .ok_or(InternalHostError::TypeConversion)?;
1210 let callee_purse = callee_addressable_entity.main_purse();
1211
1212 let target_purse = match caller
1213 .context_mut()
1214 .tracking_copy
1215 .runtime_footprint_by_entity_addr(target_entity_addr)
1216 {
1217 Ok(runtime_footprint) => match runtime_footprint.main_purse() {
1218 Some(target_purse) => target_purse,
1219 None => todo!("create a main purse for a contract"),
1220 },
1221 Err(TrackingCopyError::KeyNotFound(key)) => {
1222 warn!(?key, "Transfer recipient not found");
1223 return Ok(u32_from_host_result(Err(CallError::NotCallable)));
1224 }
1225 Err(error) => {
1226 error!(?error, "Error while reading from storage; aborting");
1227 return Err(InternalHostError::TrackingCopy)?;
1228 }
1229 };
1230 let transaction_hash = caller.context().transaction_hash;
1233 let address_generator = Arc::clone(&caller.context().address_generator);
1234 let args = MintTransferArgs {
1235 source: callee_purse,
1236 target: target_purse,
1237 amount: U512::from(amount),
1238 maybe_to: None,
1239 id: None,
1240 };
1241
1242 let result = system::mint_transfer(
1243 &mut caller.context_mut().tracking_copy,
1244 transaction_hash,
1245 address_generator,
1246 args,
1247 );
1248
1249 Ok(u32_from_host_result(result))
1250}
1251
1252pub fn casper_upgrade<S: GlobalStateReader + 'static, E: Executor>(
1253 mut caller: impl Caller<Context = Context<S, E>>,
1254 code_ptr: u32,
1255 code_size: u32,
1256 entry_point_ptr: u32,
1257 entry_point_size: u32,
1258 input_ptr: u32,
1259 input_size: u32,
1260) -> VMResult<u32> {
1261 let upgrade_cost = caller.context().config.host_function_costs().upgrade;
1262 charge_host_function_call(
1263 &mut caller,
1264 &upgrade_cost,
1265 [
1266 u64::from(code_ptr),
1267 u64::from(code_size),
1268 u64::from(entry_point_ptr),
1269 u64::from(entry_point_size),
1270 u64::from(input_ptr),
1271 u64::from(input_size),
1272 ],
1273 )?;
1274
1275 let code = caller
1276 .memory_read(code_ptr, code_size as usize)
1277 .map(Bytes::from)?;
1278
1279 let entry_point = match NonZeroU32::new(entry_point_ptr) {
1280 Some(entry_point_ptr) => {
1281 let entry_point_bytes =
1283 caller.memory_read(entry_point_ptr.get(), entry_point_size as usize)?;
1284 match String::from_utf8(entry_point_bytes) {
1285 Ok(entry_point) => Some(entry_point),
1286 Err(utf8_error) => {
1287 error!(%utf8_error, "entry point name is not a valid utf-8 string; unable to call");
1288 return Ok(CALLEE_NOT_CALLABLE);
1289 }
1290 }
1291 }
1292 None => {
1293 None
1295 }
1296 };
1297
1298 let input_data: Option<Bytes> = if input_ptr == 0 {
1300 None
1301 } else {
1302 let input_data = caller.memory_read(input_ptr, input_size as _)?.into();
1303 Some(input_data)
1304 };
1305
1306 let (smart_contract_addr, callee_addressable_entity_key) = match caller.context().callee {
1307 Key::Account(_account_hash) => {
1308 error!("Account upgrade is not possible");
1309 return Ok(CALLEE_NOT_CALLABLE);
1310 }
1311 addressable_entity_key @ Key::SmartContract(smart_contract_addr) => {
1312 let smart_contract_key = addressable_entity_key;
1313 match caller.context_mut().tracking_copy.read(&smart_contract_key) {
1314 Ok(Some(StoredValue::SmartContract(smart_contract_package))) => {
1315 match smart_contract_package.versions().latest() {
1316 Some(addressible_entity_hash) => {
1317 let key = Key::AddressableEntity(EntityAddr::SmartContract(
1318 addressible_entity_hash.value(),
1319 ));
1320 (smart_contract_addr, key)
1321 }
1322 None => {
1323 warn!(
1324 ?smart_contract_key,
1325 "Unable to find latest addressible entity hash for contract"
1326 );
1327 return Ok(CALLEE_NOT_CALLABLE);
1328 }
1329 }
1330 }
1331 Ok(Some(other)) => panic!("should be smart contract but got {other:?}"),
1332 Ok(None) => return Ok(CALLEE_NOT_CALLABLE),
1333 Err(error) => {
1334 error!(
1335 ?error,
1336 ?smart_contract_key,
1337 "Error while reading from storage; aborting"
1338 );
1339 panic!("Error while reading from storage")
1340 }
1341 }
1342 }
1343 other => panic!("should be account or addressable entity but got {other:?}"),
1344 };
1345
1346 let callee_addressable_entity = match caller
1347 .context_mut()
1348 .tracking_copy
1349 .read(&callee_addressable_entity_key)
1350 {
1351 Ok(Some(StoredValue::AddressableEntity(addressable_entity))) => addressable_entity,
1352 Ok(Some(other_entity)) => {
1353 panic!("Unexpected entity type: {other_entity:?}")
1354 }
1355 Ok(None) => return Ok(CALLEE_NOT_CALLABLE),
1356 Err(error) => {
1357 panic!("Error while reading from storage; aborting key={callee_addressable_entity_key:?} error={error:?}")
1358 }
1359 };
1360
1361 let bytecode_key = Key::ByteCode(ByteCodeAddr::V2CasperWasm(
1368 callee_addressable_entity.byte_code_addr(),
1369 ));
1370 metered_write(
1371 &mut caller,
1372 bytecode_key,
1373 StoredValue::ByteCode(ByteCode::new(
1374 ByteCodeKind::V2CasperWasm,
1375 code.clone().into(),
1376 )),
1377 )?;
1378
1379 if let Some(entry_point_name) = entry_point {
1383 let gas_limit = caller
1385 .gas_consumed()
1386 .try_into_remaining()
1387 .map_err(|_| InternalHostError::TypeConversion)?;
1388
1389 let execute_request = ExecuteRequestBuilder::default()
1390 .with_initiator(caller.context().initiator)
1391 .with_caller_key(caller.context().callee)
1392 .with_gas_limit(gas_limit)
1393 .with_target(ExecutionKind::Stored {
1394 address: smart_contract_addr,
1395 entry_point: entry_point_name.clone(),
1396 })
1397 .with_input(input_data.unwrap_or_default())
1398 .with_transferred_value(0)
1401 .with_transaction_hash(caller.context().transaction_hash)
1402 .with_shared_address_generator(Arc::clone(&caller.context().address_generator))
1405 .with_chain_name(caller.context().chain_name.clone())
1406 .with_block_time(caller.context().block_time)
1407 .with_state_hash(Digest::from_raw([0; 32])) .with_block_height(1) .with_parent_block_hash(BlockHash::new(Digest::from_raw([0; 32]))) .build()
1411 .map_err(|_| InternalHostError::ExecuteRequestBuildFailure)?;
1412
1413 let tracking_copy_for_ctor = caller.context().tracking_copy.fork2();
1414
1415 match caller
1416 .context()
1417 .executor
1418 .execute(tracking_copy_for_ctor, execute_request)
1419 {
1420 Ok(ExecuteResult {
1421 host_error,
1422 output,
1423 gas_usage,
1424 effects,
1425 cache,
1426 messages,
1427 }) => {
1428 caller.consume_gas(gas_usage.gas_spent())?;
1430
1431 if let Some(host_error) = host_error {
1432 return Ok(host_error.into_u32());
1433 }
1434
1435 caller
1436 .context_mut()
1437 .tracking_copy
1438 .apply_changes(effects, cache, messages);
1439
1440 if let Some(output) = output {
1441 info!(
1442 ?entry_point_name,
1443 ?output,
1444 "unexpected output from migration entry point"
1445 );
1446 }
1447 }
1448 Err(execute_error) => {
1449 error!(
1453 ?execute_error,
1454 ?entry_point_name,
1455 smart_contract_addr = base16::encode_lower(&smart_contract_addr),
1456 "Failed to execute upgrade entry point"
1457 );
1458 return Err(VMError::Execute(execute_error));
1459 }
1460 }
1461 }
1462
1463 Ok(CALLEE_SUCCEEDED)
1464}
1465
1466pub fn casper_env_info<S: GlobalStateReader, E: Executor>(
1467 mut caller: impl Caller<Context = Context<S, E>>,
1468 info_ptr: u32,
1469 info_size: u32,
1470) -> VMResult<u32> {
1471 let block_time_cost = caller.context().config.host_function_costs().env_info;
1472 charge_host_function_call(
1473 &mut caller,
1474 &block_time_cost,
1475 [u64::from(info_ptr), u64::from(info_size)],
1476 )?;
1477
1478 let (caller_kind, caller_addr) = match &caller.context().caller {
1479 Key::Account(account_hash) => (EntityKindTag::Account as u32, account_hash.value()),
1480 Key::SmartContract(smart_contract_addr) => {
1481 (EntityKindTag::Contract as u32, *smart_contract_addr)
1482 }
1483 other => panic!("Unexpected caller: {other:?}"),
1484 };
1485
1486 let (callee_kind, callee_addr) = match &caller.context().callee {
1487 Key::Account(initiator_addr) => (EntityKindTag::Account as u32, initiator_addr.value()),
1488 Key::SmartContract(smart_contract_addr) => {
1489 (EntityKindTag::Contract as u32, *smart_contract_addr)
1490 }
1491 other => panic!("Unexpected callee: {other:?}"),
1492 };
1493
1494 let transferred_value = caller.context().transferred_value;
1495
1496 let block_time = caller.context().block_time.value();
1497
1498 let env_info_le = EnvInfo {
1500 caller_addr,
1501 caller_kind: caller_kind.to_le(),
1502 callee_addr,
1503 callee_kind: callee_kind.to_le(),
1504 transferred_value: transferred_value.to_le(),
1505 block_time: block_time.to_le(),
1506 };
1507
1508 let env_info_bytes = safe_transmute::transmute_one_to_bytes(&env_info_le);
1509 let write_len = env_info_bytes.len().min(info_size as usize);
1510 caller.memory_write(info_ptr, &env_info_bytes[..write_len])?;
1511
1512 Ok(HOST_ERROR_SUCCESS)
1513}
1514
1515pub fn casper_emit<S: GlobalStateReader, E: Executor>(
1516 mut caller: impl Caller<Context = Context<S, E>>,
1517 topic_name_ptr: u32,
1518 topic_name_size: u32,
1519 payload_ptr: u32,
1520 payload_size: u32,
1521) -> VMResult<u32> {
1522 let emit_host_function = caller.context().config.host_function_costs().emit;
1524
1525 charge_host_function_call(
1526 &mut caller,
1527 &emit_host_function,
1528 [
1529 u64::from(topic_name_ptr),
1530 u64::from(topic_name_size),
1531 u64::from(payload_ptr),
1532 u64::from(payload_size),
1533 ],
1534 )?;
1535
1536 if topic_name_size > caller.context().message_limits.max_topic_name_size {
1537 return Ok(HOST_ERROR_TOPIC_TOO_LONG);
1538 }
1539
1540 if payload_size > caller.context().message_limits.max_message_size {
1541 return Ok(HOST_ERROR_PAYLOAD_TOO_LONG);
1542 }
1543
1544 let topic_name = {
1545 let topic: Vec<u8> = caller.memory_read(topic_name_ptr, topic_name_size as usize)?;
1546 let Ok(topic) = String::from_utf8(topic) else {
1547 return Ok(HOST_ERROR_INVALID_DATA);
1549 };
1550 topic
1551 };
1552
1553 let payload = caller.memory_read(payload_ptr, payload_size as usize)?;
1554
1555 let entity_addr = context_to_entity_addr(caller.context());
1556
1557 let mut message_topics = caller
1558 .context_mut()
1559 .tracking_copy
1560 .get_message_topics(entity_addr)
1561 .unwrap_or_else(|error| {
1562 panic!("Error while reading from storage; aborting error={error:?}")
1563 });
1564
1565 if message_topics.len() >= caller.context().message_limits.max_topics_per_contract as usize {
1566 return Ok(HOST_ERROR_TOO_MANY_TOPICS);
1567 }
1568
1569 let topic_name_hash = Digest::hash(&topic_name).value().into();
1570
1571 match message_topics.add_topic(&topic_name, topic_name_hash) {
1572 Ok(()) => {
1573 }
1575 Err(MessageTopicError::DuplicateTopic) => {
1576 }
1579 Err(MessageTopicError::MaxTopicsExceeded) => {
1580 return Ok(HOST_ERROR_TOO_MANY_TOPICS);
1582 }
1583 Err(MessageTopicError::TopicNameSizeExceeded) => {
1584 return Ok(HOST_ERROR_TOPIC_TOO_LONG);
1586 }
1587 Err(error) => {
1588 unreachable!("Unexpected error while adding a topic: {:?}", error);
1590 }
1591 };
1592
1593 let current_block_time = caller.context().block_time;
1594 eprintln!("📩 {topic_name}: {payload:?} (at {current_block_time:?})");
1595
1596 let topic_key = Key::Message(MessageAddr::new_topic_addr(entity_addr, topic_name_hash));
1597 let prev_topic_summary = match caller.context_mut().tracking_copy.read(&topic_key) {
1598 Ok(Some(StoredValue::MessageTopic(message_topic_summary))) => message_topic_summary,
1599 Ok(Some(stored_value)) => {
1600 panic!("Unexpected stored value: {stored_value:?}");
1601 }
1602 Ok(None) => {
1603 let message_topic_summary =
1604 MessageTopicSummary::new(0, current_block_time, topic_name.clone());
1605 let summary = StoredValue::MessageTopic(message_topic_summary.clone());
1606 caller.context_mut().tracking_copy.write(topic_key, summary);
1607 message_topic_summary
1608 }
1609 Err(error) => panic!("Error while reading from storage; aborting error={error:?}"),
1610 };
1611
1612 let topic_message_index = if prev_topic_summary.blocktime() != current_block_time {
1613 for index in 1..prev_topic_summary.message_count() {
1614 let message_key = Key::message(entity_addr, topic_name_hash, index);
1615 debug_assert!(
1616 {
1617 caller
1620 .context_mut()
1621 .tracking_copy
1622 .read(&message_key)
1623 .map_err(|_| VMError::Internal(InternalHostError::TrackingCopy))?
1624 .is_some()
1625 },
1626 "Message index is not continuous"
1627 );
1628
1629 caller.context_mut().tracking_copy.prune(message_key);
1631 }
1632 0
1633 } else {
1634 prev_topic_summary.message_count()
1635 };
1636
1637 type MessageCountPair = (BlockTime, u64);
1639
1640 let block_message_index: u64 = match caller
1641 .context_mut()
1642 .tracking_copy
1643 .read(&Key::BlockGlobal(BlockGlobalAddr::MessageCount))
1644 {
1645 Ok(Some(StoredValue::CLValue(value_pair))) => {
1646 let (prev_block_time, prev_count): MessageCountPair =
1647 CLValue::into_t(value_pair).map_err(|_| InternalHostError::TypeConversion)?;
1648 if prev_block_time == current_block_time {
1649 prev_count
1650 } else {
1651 0
1652 }
1653 }
1654 Ok(Some(other)) => panic!("Unexpected stored value: {other:?}"),
1655 Ok(None) => {
1656 0
1658 }
1659 Err(error) => {
1660 panic!("Error while reading from storage; aborting error={error:?}")
1661 }
1662 };
1663
1664 let Some(topic_message_count) = topic_message_index.checked_add(1) else {
1665 return Ok(HOST_ERROR_MESSAGE_TOPIC_FULL);
1666 };
1667
1668 let Some(block_message_count) = block_message_index.checked_add(1) else {
1669 return Ok(HOST_ERROR_MAX_MESSAGES_PER_BLOCK_EXCEEDED);
1670 };
1671
1672 let message_payload = MessagePayload::Bytes(payload.into());
1674
1675 let message = Message::new(
1676 entity_addr,
1677 message_payload,
1678 topic_name,
1679 topic_name_hash,
1680 topic_message_index,
1681 block_message_index,
1682 );
1683 let topic_value = StoredValue::MessageTopic(MessageTopicSummary::new(
1684 topic_message_count,
1685 current_block_time,
1686 message.topic_name().to_owned(),
1687 ));
1688
1689 let message_key = message.message_key();
1690 let message_value = StoredValue::Message(
1691 message
1692 .checksum()
1693 .map_err(|_| InternalHostError::MessageChecksumMissing)?,
1694 );
1695 let message_count_pair: MessageCountPair = (current_block_time, block_message_count);
1696 let block_message_count_value = StoredValue::CLValue(
1697 CLValue::from_t(message_count_pair).map_err(|_| InternalHostError::TypeConversion)?,
1698 );
1699
1700 let bytes_count = topic_value.serialized_length()
1702 + message_value.serialized_length()
1703 + block_message_count_value.serialized_length();
1704 charge_gas_storage(&mut caller, bytes_count)?;
1705
1706 caller.context_mut().tracking_copy.emit_message(
1707 topic_key,
1708 topic_value,
1709 message_key,
1710 message_value,
1711 block_message_count_value,
1712 message,
1713 );
1714
1715 Ok(HOST_ERROR_SUCCESS)
1716}