1use crate::{
5 constants::MAX_LOG_SIZE,
6 v1::{host, trie, EmittedDebugStatement, InstanceState},
7 ExecResult,
8};
9use anyhow::{anyhow, bail, ensure, Context};
10pub use concordium_contracts_common::WasmVersion;
11use concordium_contracts_common::{
12 self as concordium_std, from_bytes, hashes, schema, ContractAddress, Cursor, Deserial, Seek,
13 SeekFrom, Serial,
14};
15use concordium_std::{AccountAddress, Address, HashMap, OwnedEntrypointName, Read, Write};
16use concordium_wasm::{
17 artifact::{Artifact, ArtifactNamedImport, RunnableCode, TryFromImport},
18 machine::{self, NoInterrupt, Value},
19 parse::{parse_custom, parse_skeleton, Skeleton},
20 types::{ExportDescription, Module, Name},
21 utils,
22 validate::{self, ValidationConfig},
23};
24use rand::RngCore;
25use sha2::Digest;
26use std::{collections::BTreeMap, default::Default};
27use thiserror::Error;
28
29pub struct TrapHost;
31
32impl<I> machine::Host<I> for TrapHost {
33 type Interrupt = NoInterrupt;
34
35 fn tick_initial_memory(&mut self, _num_pages: u32) -> machine::RunResult<()> { Ok(()) }
36
37 fn call(
38 &mut self,
39 _f: &I,
40 _memory: &mut [u8],
41 _stack: &mut machine::RuntimeStack,
42 ) -> machine::RunResult<Option<NoInterrupt>> {
43 bail!("TrapHost traps on all host calls.")
44 }
45
46 fn tick_energy(&mut self, _energy: u64) -> machine::RunResult<()> { Ok(()) }
47
48 fn track_call(&mut self) -> machine::RunResult<()> { Ok(()) }
49
50 fn track_return(&mut self) {}
51}
52
53pub struct TestHost<'a, R, BackingStore> {
57 rng: Option<R>,
59 pub rng_used: bool,
61 pub debug_events: Vec<EmittedDebugStatement>,
63 state: InstanceState<'a, BackingStore>,
65 slot_time: Option<u64>,
67 address: Option<ContractAddress>,
69 balance: Option<u64>,
71 parameters: HashMap<u32, Vec<u8>>,
73 events: Vec<Vec<u8>>,
75 init_origin: Option<AccountAddress>,
77 receive_invoker: Option<AccountAddress>,
79 receive_sender: Option<Address>,
81 receive_owner: Option<AccountAddress>,
83 receive_entrypoint: Option<OwnedEntrypointName>,
85}
86
87impl<'a, R: RngCore, BackingStore> TestHost<'a, R, BackingStore> {
88 pub fn new(rng: R, state: InstanceState<'a, BackingStore>) -> Self {
92 TestHost {
93 rng: Some(rng),
94 rng_used: false,
95 debug_events: Vec::new(),
96 state,
97 slot_time: None,
98 address: None,
99 balance: None,
100 parameters: HashMap::default(),
101 events: Vec::new(),
102 init_origin: None,
103 receive_invoker: None,
104 receive_sender: None,
105 receive_owner: None,
106 receive_entrypoint: None,
107 }
108 }
109}
110
111pub struct NoDuplicateImport;
115
116impl validate::ValidateImportExport for NoDuplicateImport {
117 #[cfg_attr(not(feature = "fuzz-coverage"), inline(always))]
119 fn validate_import_function(
120 &self,
121 duplicate: bool,
122 _mod_name: &Name,
123 _item_name: &Name,
124 _ty: &concordium_wasm::types::FunctionType,
125 ) -> bool {
126 !duplicate
127 }
128
129 #[cfg_attr(not(feature = "fuzz-coverage"), inline(always))]
130 fn validate_export_function(
131 &self,
132 _item_name: &Name,
133 _ty: &concordium_wasm::types::FunctionType,
134 ) -> bool {
135 true
136 }
137}
138
139#[derive(Debug, Clone)]
140pub enum ReportError {
144 Reported {
146 filename: String,
147 line: u32,
148 column: u32,
149 msg: String,
150 },
151 Other {
154 msg: String,
155 },
156}
157
158impl std::fmt::Display for ReportError {
159 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160 match self {
161 ReportError::Reported {
162 filename,
163 line,
164 column,
165 msg,
166 } => write!(f, "{}, {}:{}:{}", msg, filename, line, column),
167 ReportError::Other {
168 msg,
169 } => msg.fmt(f),
170 }
171 }
172}
173
174pub(crate) fn extract_debug(
177 memory: &mut [u8],
178 stack: &mut machine::RuntimeStack,
179) -> anyhow::Result<(String, u32, u32, String)> {
180 let column = unsafe { stack.pop_u32() };
181 let line = unsafe { stack.pop_u32() };
182 let filename_length = unsafe { stack.pop_u32() } as usize;
183 let filename_start = unsafe { stack.pop_u32() } as usize;
184 let msg_length = unsafe { stack.pop_u32() } as usize;
185 let msg_start = unsafe { stack.pop_u32() } as usize;
186 ensure!(filename_start + filename_length <= memory.len(), "Illegal memory access.");
187 ensure!(msg_start + msg_length <= memory.len(), "Illegal memory access.");
188 let msg = std::str::from_utf8(&memory[msg_start..msg_start + msg_length])?.to_owned();
189 let filename =
190 std::str::from_utf8(&memory[filename_start..filename_start + filename_length])?.to_owned();
191 Ok((filename, line, column, msg))
192}
193
194#[derive(Error, Debug)]
195enum CallErr {
196 #[error("Unable to read bytes at the given position")]
197 Seek,
198 #[error("No \"{0}\" is set. Make sure to prepare this in the test environment")]
199 Unset(&'static str),
200 #[error("Unable to serialize \"{0}\"")]
201 Serial(&'static str),
202 #[error("Unable to write to given buffer")]
203 Write,
204}
205
206impl<'a, R: RngCore, BackingStore: trie::BackingStoreLoad> machine::Host<ArtifactNamedImport>
207 for TestHost<'a, R, BackingStore>
208{
209 type Interrupt = NoInterrupt;
210
211 fn tick_initial_memory(&mut self, _num_pages: u32) -> machine::RunResult<()> {
212 Ok(())
214 }
215
216 fn call(
217 &mut self,
218 f: &ArtifactNamedImport,
219 memory: &mut [u8],
220 stack: &mut machine::RuntimeStack,
221 ) -> machine::RunResult<Option<NoInterrupt>> {
222 let energy = &mut crate::InterpreterEnergy::new(u64::MAX);
226 let state = &mut self.state;
227
228 ensure!(
229 f.get_mod_name() == "concordium",
230 "Unsupported module in host function call: {:?} {:?}",
231 f.get_mod_name(),
232 f.get_item_name()
233 );
234
235 use host::*;
236 match f.get_item_name() {
237 "report_error" => {
238 let (filename, line, column, msg) = extract_debug(memory, stack)?;
239 bail!(ReportError::Reported {
240 filename,
241 line,
242 column,
243 msg
244 })
245 }
246 "get_random" => {
247 let size = unsafe { stack.pop_u32() } as usize;
248 let dest = unsafe { stack.pop_u32() } as usize;
249 ensure!(dest + size <= memory.len(), "Illegal memory access.");
250 self.rng_used = true;
251 self.rng
252 .as_mut()
253 .context("Expected an initialized RNG.")?
254 .try_fill_bytes(&mut memory[dest..dest + size])?
255 }
256 "debug_print" => {
257 let (filename, line, column, msg) = extract_debug(memory, stack)?;
258 self.debug_events.push(EmittedDebugStatement {
259 filename,
260 line,
261 column,
262 msg,
263 remaining_energy: 0.into(), });
265 }
266 "state_lookup_entry" => state_lookup_entry(memory, stack, energy, state)?,
267 "state_create_entry" => state_create_entry(memory, stack, energy, state)?,
268 "state_delete_entry" => state_delete_entry(memory, stack, energy, state)?,
269 "state_delete_prefix" => state_delete_prefix(memory, stack, energy, state)?,
270 "state_iterate_prefix" => state_iterator(memory, stack, energy, state)?,
271 "state_iterator_next" => state_iterator_next(stack, energy, state)?,
272 "state_iterator_delete" => state_iterator_delete(stack, energy, state)?,
273 "state_iterator_key_size" => state_iterator_key_size(stack, energy, state)?,
274 "state_iterator_key_read" => state_iterator_key_read(memory, stack, energy, state)?,
275 "state_entry_read" => state_entry_read(memory, stack, energy, state)?,
276 "state_entry_write" => state_entry_write(memory, stack, energy, state)?,
277 "state_entry_size" => state_entry_size(stack, energy, state)?,
278 "state_entry_resize" => state_entry_resize(stack, energy, state)?,
279 "set_slot_time" => {
280 let slot_time = unsafe { stack.pop_u64() };
281 self.slot_time = Some(slot_time);
282 }
283 "get_slot_time" => {
284 let slot_time = self.slot_time.ok_or(CallErr::Unset("slot_time"))?;
285 stack.push_value(slot_time);
286 }
287 "set_receive_self_address" => {
288 let addr_ptr = unsafe { stack.pop_u32() };
289
290 let mut cursor = Cursor::new(memory);
291 cursor.seek(SeekFrom::Start(addr_ptr)).map_err(|_| CallErr::Seek)?;
292
293 self.address = Some(ContractAddress::deserial(&mut cursor)?);
294 }
295 "get_receive_self_address" => {
296 let addr_ptr = unsafe { stack.pop_u32() } as usize;
297 self.address
298 .ok_or(CallErr::Unset("self_address"))?
299 .serial(&mut &mut memory[addr_ptr..])
300 .map_err(|_| CallErr::Serial("self_address"))?;
301 }
302 "set_receive_self_balance" => {
303 let balance = unsafe { stack.pop_u64() };
304 self.balance = Some(balance);
305 }
306 "get_receive_self_balance" => {
307 let balance = self.balance.ok_or(CallErr::Unset("self_balance"))?;
308 stack.push_value(balance);
309 }
310 "set_parameter" => {
311 let param_size = unsafe { stack.pop_u32() };
312 let param_ptr = unsafe { stack.pop_u32() };
313 let param_index = unsafe { stack.pop_u32() };
314
315 let mut param = vec![0; param_size as usize];
316
317 let mut cursor = Cursor::new(memory);
318 cursor.seek(SeekFrom::Start(param_ptr)).map_err(|_| CallErr::Seek)?;
319 cursor.read_exact(&mut param)?;
320
321 self.parameters.insert(param_index, param);
322 }
323 "get_parameter_size" => {
324 let param_index = unsafe { stack.pop_u32() };
325
326 if let Some(param) = self.parameters.get(¶m_index) {
327 stack.push_value(param.len() as u64)
328 } else {
329 stack.push_value(-1i32)
330 }
331 }
332 "get_parameter_section" => {
333 let offset = unsafe { stack.pop_u32() } as usize;
334 let length = unsafe { stack.pop_u32() } as usize;
335 let param_bytes = unsafe { stack.pop_u32() } as usize;
336 let param_index = unsafe { stack.pop_u32() };
337
338 if let Some(param) = self.parameters.get(¶m_index) {
339 let self_param = param.get(offset..length + offset).context(format!(
340 "Tried to grab {} bytes of parameter[{}], which has length {}",
341 length,
342 param_index,
343 param.len()
344 ))?;
345
346 let mut mem = &mut memory[param_bytes..];
347 let bytes_written: i32 =
348 mem.write(self_param).map_err(|_| CallErr::Write)?.try_into()?;
349
350 stack.push_value(bytes_written)
351 } else {
352 stack.push_value(-1i32)
353 }
354 }
355 "log_event" => {
356 let event_length = unsafe { stack.pop_u32() };
357 let event_start = unsafe { stack.pop_u32() };
358
359 if event_length > MAX_LOG_SIZE {
360 stack.push_value(-1i32);
361 } else {
362 let mut cursor = Cursor::new(memory);
363 cursor.seek(SeekFrom::Start(event_start)).map_err(|_| CallErr::Seek)?;
364
365 let mut buf = vec![0; event_length as usize];
366 cursor.read(&mut buf).context("Unable to read provided event")?;
367
368 self.events.push(buf);
369
370 stack.push_value(1i32);
371 }
372 }
373 "get_event_size" => {
374 let event_index = unsafe { stack.pop_u32() };
375 let event_opt = self.events.get(event_index as usize);
376
377 if let Some(event) = event_opt {
378 let event_size: i32 = event.len().try_into()?;
379 stack.push_value(event_size)
380 } else {
381 stack.push_value(-1i32);
382 }
383 }
384 "get_event" => {
385 let ret_buf_start = unsafe { stack.pop_u32() } as usize;
386 let event_index = unsafe { stack.pop_u32() };
387 let event_opt = self.events.get(event_index as usize);
388
389 if let Some(event) = event_opt {
390 let mut mem = &mut memory[ret_buf_start..];
391 let bytes_written: i32 =
392 mem.write(event).map_err(|_| CallErr::Write)?.try_into()?;
393
394 stack.push_value(bytes_written)
395 } else {
396 stack.push_value(-1i32);
397 }
398 }
399 "set_init_origin" => {
400 let addr_bytes = unsafe { stack.pop_u32() };
401
402 let mut cursor = Cursor::new(memory);
403 cursor.seek(SeekFrom::Start(addr_bytes)).map_err(|_| CallErr::Seek)?;
404
405 self.init_origin = Some(AccountAddress::deserial(&mut cursor)?);
406 }
407 "get_init_origin" => {
408 let ret_buf_start = unsafe { stack.pop_u32() } as usize;
409
410 let mut mem = &mut memory[ret_buf_start..];
411 self.init_origin
412 .ok_or(CallErr::Unset("init_origin"))?
413 .serial(&mut mem)
414 .map_err(|_| CallErr::Serial("init_origin"))?;
415 }
416 "set_receive_invoker" => {
417 let addr_bytes = unsafe { stack.pop_u32() };
418
419 let mut cursor = Cursor::new(memory);
420 cursor.seek(SeekFrom::Start(addr_bytes)).map_err(|_| CallErr::Seek)?;
421
422 self.receive_invoker = Some(AccountAddress::deserial(&mut cursor)?);
423 }
424 "get_receive_invoker" => {
425 let ret_buf_start = unsafe { stack.pop_u32() } as usize;
426
427 let mut mem = &mut memory[ret_buf_start..];
428 self.receive_invoker
429 .ok_or(CallErr::Unset("receive_invoker"))?
430 .serial(&mut mem)
431 .map_err(|_| CallErr::Serial("receive_invoker"))?;
432 }
433 "set_receive_sender" => {
434 let addr_bytes = unsafe { stack.pop_u32() };
435
436 let mut cursor = Cursor::new(memory);
437 cursor.seek(SeekFrom::Start(addr_bytes)).map_err(|_| CallErr::Seek)?;
438
439 self.receive_sender = Some(Address::deserial(&mut cursor)?);
440 }
441 "get_receive_sender" => {
442 let ret_buf_start = unsafe { stack.pop_u32() } as usize;
443
444 let mut mem = &mut memory[ret_buf_start..];
445 self.receive_sender
446 .ok_or(CallErr::Unset("receive_sender"))?
447 .serial(&mut mem)
448 .map_err(|_| CallErr::Serial("receive_sender"))?;
449 }
450 "set_receive_owner" => {
451 let addr_bytes = unsafe { stack.pop_u32() };
452
453 let mut cursor = Cursor::new(memory);
454 cursor.seek(SeekFrom::Start(addr_bytes)).map_err(|_| CallErr::Seek)?;
455
456 self.receive_owner = Some(AccountAddress::deserial(&mut cursor)?);
457 }
458 "get_receive_owner" => {
459 let ret_buf_start = unsafe { stack.pop_u32() } as usize;
460
461 let mut mem = &mut memory[ret_buf_start..];
462 self.receive_owner
463 .ok_or(CallErr::Unset("receive_owner"))?
464 .serial(&mut mem)
465 .map_err(|_| CallErr::Serial("receive_owner"))?;
466 }
467 "set_receive_entrypoint" => {
468 let addr_bytes = unsafe { stack.pop_u32() };
469
470 let mut cursor = Cursor::new(memory);
471 cursor.seek(SeekFrom::Start(addr_bytes)).map_err(|_| CallErr::Seek)?;
472
473 self.receive_entrypoint = Some(OwnedEntrypointName::deserial(&mut cursor)?);
474 }
475 "get_receive_entrypoint_size" => {
476 let size = self
477 .receive_entrypoint
478 .as_ref()
479 .ok_or(CallErr::Unset("receive_entrypoint"))?
480 .as_entrypoint_name()
481 .size();
482 stack.push_value(size);
483 }
484 "get_receive_entrypoint" => {
485 let ret_buf_start = unsafe { stack.pop_u32() } as usize;
486
487 let bytes = self
488 .receive_entrypoint
489 .clone()
490 .ok_or(CallErr::Unset("receive_entrypoint"))?
491 .to_string()
492 .into_bytes();
493
494 let mut mem = &mut memory[ret_buf_start..];
495 mem.write(&bytes).map_err(|_| CallErr::Write)?;
496 }
497 "verify_ed25519_signature" => {
498 let message_len = unsafe { stack.pop_u32() };
499 let message_ptr = unsafe { stack.pop_u32() };
500 let signature_ptr = unsafe { stack.pop_u32() };
501 let public_key_ptr = unsafe { stack.pop_u32() };
502
503 let mut cursor = Cursor::new(memory);
504
505 cursor.seek(SeekFrom::Start(public_key_ptr)).map_err(|_| CallErr::Seek)?;
506 let mut public_key_bytes = [0; 32];
507 cursor.read(&mut public_key_bytes)?;
508 let z_pk = ed25519_zebra::VerificationKey::try_from(public_key_bytes)?;
509
510 cursor.seek(SeekFrom::Start(signature_ptr)).map_err(|_| CallErr::Seek)?;
511 let mut signature_bytes = [0; 64];
512 cursor.read(&mut signature_bytes)?;
513 let z_sig = ed25519_zebra::Signature::from_bytes(&signature_bytes);
514
515 cursor.seek(SeekFrom::Start(message_ptr)).map_err(|_| CallErr::Seek)?;
516 let mut msg = vec![0; message_len as usize];
517 cursor.read(&mut msg)?;
518
519 let is_verified = z_pk.verify(&z_sig, &msg);
520
521 if is_verified.is_ok() {
522 stack.push_value(1i32)
523 } else {
524 stack.push_value(0i32)
525 }
526 }
527 "verify_ecdsa_secp256k1_signature" => {
528 let message_hash_ptr = unsafe { stack.pop_u32() };
529 let signature_ptr = unsafe { stack.pop_u32() };
530 let public_key_ptr = unsafe { stack.pop_u32() };
531
532 let mut cursor = Cursor::new(memory);
533 let secp = secp256k1::Secp256k1::verification_only();
534
535 cursor.seek(SeekFrom::Start(public_key_ptr)).map_err(|_| CallErr::Seek)?;
536 let mut public_key_bytes = [0; 33];
537 cursor.read(&mut public_key_bytes)?;
538 let pk = secp256k1::PublicKey::from_slice(&public_key_bytes)?;
539
540 cursor.seek(SeekFrom::Start(signature_ptr)).map_err(|_| CallErr::Seek)?;
541 let mut signature_bytes = [0; 64];
542 cursor.read(&mut signature_bytes)?;
543 let sig = secp256k1::ecdsa::Signature::from_compact(&signature_bytes)?;
544
545 cursor.seek(SeekFrom::Start(message_hash_ptr)).map_err(|_| CallErr::Seek)?;
546 let mut message_hash_bytes = [0; 32];
547 cursor.read(&mut message_hash_bytes)?;
548 let msg = secp256k1::Message::from_slice(&message_hash_bytes)?;
549
550 let is_verified = secp.verify_ecdsa(&msg, &sig, &pk);
551 if is_verified.is_ok() {
552 stack.push_value(1i32)
553 } else {
554 stack.push_value(0i32)
555 }
556 }
557 "hash_sha2_256" => {
558 let output_ptr = unsafe { stack.pop_u32() } as usize;
559 let data_len = unsafe { stack.pop_u32() } as usize;
560 let data_ptr = unsafe { stack.pop_u32() } as usize;
561
562 let mut hasher = sha2::Sha256::default();
563 hasher.update(&memory[data_ptr..data_ptr + data_len]);
564 let data_hash = hasher.finalize();
565
566 let mut mem = &mut memory[output_ptr..];
567 mem.write(&data_hash).map_err(|_| CallErr::Write)?;
568 }
569 "hash_sha3_256" => {
570 let output_ptr = unsafe { stack.pop_u32() } as usize;
571 let data_len = unsafe { stack.pop_u32() } as usize;
572 let data_ptr = unsafe { stack.pop_u32() } as usize;
573
574 let mut hasher = sha3::Sha3_256::default();
575 hasher.update(&memory[data_ptr..data_ptr + data_len]);
576 let data_hash = hasher.finalize();
577
578 let mut mem = &mut memory[output_ptr..];
579 mem.write(&data_hash).map_err(|_| CallErr::Write)?;
580 }
581 "hash_keccak_256" => {
582 let output_ptr = unsafe { stack.pop_u32() } as usize;
583 let data_len = unsafe { stack.pop_u32() } as usize;
584 let data_ptr = unsafe { stack.pop_u32() } as usize;
585
586 let mut hasher = sha3::Keccak256::default();
587 hasher.update(&memory[data_ptr..data_ptr + data_len]);
588 let data_hash = hasher.finalize();
589
590 let mut mem = &mut memory[output_ptr..];
591 mem.write(&data_hash).map_err(|_| CallErr::Write)?;
592 }
593 item_name => {
594 bail!("Unsupported host function call: {:?} {:?}", f.get_mod_name(), item_name)
595 }
596 }
597
598 Ok(None)
599 }
600
601 fn tick_energy(&mut self, _energy: u64) -> machine::RunResult<()> { Ok(()) }
602
603 fn track_call(&mut self) -> machine::RunResult<()> { Ok(()) }
604
605 fn track_return(&mut self) {}
606}
607
608pub fn generate_contract_schema_v0(
611 module_bytes: &[u8],
612) -> ExecResult<schema::VersionedModuleSchema> {
613 let artifact = utils::instantiate::<ArtifactNamedImport, _>(
614 ValidationConfig::V0,
615 &NoDuplicateImport,
616 module_bytes,
617 )?
618 .artifact;
619
620 let mut contract_schemas = BTreeMap::new();
621
622 for name in artifact.export.keys() {
623 if let Some(contract_name) = name.as_ref().strip_prefix("concordium_schema_state_") {
624 let schema_type = generate_schema_run(&artifact, name.as_ref())?;
625
626 let contract_schema = contract_schemas
629 .entry(contract_name.to_owned())
630 .or_insert_with(schema::ContractV0::default);
631
632 contract_schema.state = Some(schema_type);
633 } else if let Some(rest) = name.as_ref().strip_prefix("concordium_schema_function_") {
634 if let Some(contract_name) = rest.strip_prefix("init_") {
635 let schema_type = generate_schema_run(&artifact, name.as_ref())?;
636
637 let contract_schema = contract_schemas
638 .entry(contract_name.to_owned())
639 .or_insert_with(schema::ContractV0::default);
640 contract_schema.init = Some(schema_type);
641 } else if rest.contains('.') {
642 let schema_type = generate_schema_run(&artifact, name.as_ref())?;
643
644 let split_name: Vec<_> = rest.splitn(2, '.').collect();
646 let contract_name = split_name[0];
647 let function_name = split_name[1];
648
649 let contract_schema = contract_schemas
650 .entry(contract_name.to_owned())
651 .or_insert_with(schema::ContractV0::default);
652
653 contract_schema.receive.insert(function_name.to_owned(), schema_type);
654 } else {
655 }
658 }
659 }
660
661 Ok(schema::VersionedModuleSchema::V0(schema::ModuleV0 {
662 contracts: contract_schemas,
663 }))
664}
665
666pub fn generate_contract_schema_v1(
669 module_bytes: &[u8],
670) -> ExecResult<schema::VersionedModuleSchema> {
671 let artifact = utils::instantiate::<ArtifactNamedImport, _>(
672 ValidationConfig::V1,
673 &NoDuplicateImport,
674 module_bytes,
675 )?
676 .artifact;
677
678 let mut contract_schemas = BTreeMap::new();
679
680 for name in artifact.export.keys() {
681 if let Some(rest) = name.as_ref().strip_prefix("concordium_schema_function_") {
682 if let Some(contract_name) = rest.strip_prefix("init_") {
683 let function_schema = generate_schema_run(&artifact, name.as_ref())?;
684
685 let contract_schema = contract_schemas
686 .entry(contract_name.to_owned())
687 .or_insert_with(schema::ContractV1::default);
688 contract_schema.init = Some(function_schema);
689 } else if rest.contains('.') {
690 let function_schema = generate_schema_run(&artifact, name.as_ref())?;
691
692 let split_name: Vec<_> = rest.splitn(2, '.').collect();
694 let contract_name = split_name[0];
695 let function_name = split_name[1];
696
697 let contract_schema = contract_schemas
698 .entry(contract_name.to_owned())
699 .or_insert_with(schema::ContractV1::default);
700
701 contract_schema.receive.insert(function_name.to_owned(), function_schema);
702 } else {
703 }
706 }
707 }
708
709 Ok(schema::VersionedModuleSchema::V1(schema::ModuleV1 {
710 contracts: contract_schemas,
711 }))
712}
713
714pub fn generate_contract_schema_v2(
717 module_bytes: &[u8],
718) -> ExecResult<schema::VersionedModuleSchema> {
719 let artifact = utils::instantiate::<ArtifactNamedImport, _>(
720 ValidationConfig::V1,
721 &NoDuplicateImport,
722 module_bytes,
723 )?
724 .artifact;
725
726 let mut contract_schemas = BTreeMap::new();
727
728 for name in artifact.export.keys() {
729 if let Some(rest) = name.as_ref().strip_prefix("concordium_schema_function_") {
730 if let Some(contract_name) = rest.strip_prefix("init_") {
731 let function_schema = generate_schema_run(&artifact, name.as_ref())?;
732
733 let contract_schema = contract_schemas
734 .entry(contract_name.to_owned())
735 .or_insert_with(schema::ContractV2::default);
736 contract_schema.init = Some(function_schema);
737 } else if rest.contains('.') {
738 let function_schema = generate_schema_run(&artifact, name.as_ref())?;
739
740 let split_name: Vec<_> = rest.splitn(2, '.').collect();
742 let contract_name = split_name[0];
743 let function_name = split_name[1];
744
745 let contract_schema = contract_schemas
746 .entry(contract_name.to_owned())
747 .or_insert_with(schema::ContractV2::default);
748
749 contract_schema.receive.insert(function_name.to_owned(), function_schema);
750 } else {
751 }
754 }
755 }
756
757 Ok(schema::VersionedModuleSchema::V2(schema::ModuleV2 {
758 contracts: contract_schemas,
759 }))
760}
761
762pub fn generate_contract_schema_v3(
765 module_bytes: &[u8],
766) -> ExecResult<schema::VersionedModuleSchema> {
767 let artifact = utils::instantiate::<ArtifactNamedImport, _>(
768 ValidationConfig::V1,
769 &NoDuplicateImport,
770 module_bytes,
771 )?
772 .artifact;
773
774 let mut contract_schemas = BTreeMap::new();
775
776 for name in artifact.export.keys() {
777 if let Some(rest) = name.as_ref().strip_prefix("concordium_event_schema_") {
778 if let Some(contract_name) = rest.strip_prefix("init_") {
779 let function_schema_event = generate_schema_run(&artifact, name.as_ref())?;
781
782 let contract_schema = contract_schemas
783 .entry(contract_name.to_owned())
784 .or_insert_with(schema::ContractV3::default);
785 contract_schema.event = Some(function_schema_event);
786 }
787 } else if let Some(rest) = name.as_ref().strip_prefix("concordium_schema_function_") {
792 if let Some(contract_name) = rest.strip_prefix("init_") {
793 let function_schema = generate_schema_run(&artifact, name.as_ref())?;
795
796 let contract_schema = contract_schemas
797 .entry(contract_name.to_owned())
798 .or_insert_with(schema::ContractV3::default);
799 contract_schema.init = Some(function_schema);
800 } else if rest.contains('.') {
801 let function_schema = generate_schema_run(&artifact, name.as_ref())?;
803
804 let split_name: Vec<_> = rest.splitn(2, '.').collect();
805 let contract_name = split_name[0];
806 let function_name = split_name[1];
807
808 let contract_schema = contract_schemas
809 .entry(contract_name.to_owned())
810 .or_insert_with(schema::ContractV3::default);
811
812 contract_schema.receive.insert(function_name.to_owned(), function_schema);
813 } else {
814 }
817 }
818 }
819
820 Ok(schema::VersionedModuleSchema::V3(schema::ModuleV3 {
821 contracts: contract_schemas,
822 }))
823}
824
825fn generate_schema_run<I: TryFromImport, C: RunnableCode, SchemaType: Deserial>(
828 artifact: &Artifact<I, C>,
829 schema_fn_name: &str,
830) -> ExecResult<SchemaType> {
831 let (ptr, memory) = if let machine::ExecutionOutcome::Success {
832 result: Some(Value::I32(ptr)),
833 memory,
834 } = artifact.run(&mut TrapHost, schema_fn_name, &[])?
835 {
836 (ptr as u32 as usize, memory)
837 } else {
838 bail!("Schema derivation function is malformed.")
839 };
840
841 ensure!(ptr + 4 <= memory.len(), "Illegal memory access.");
843 let len = u32::deserial(&mut Cursor::new(&memory[ptr..ptr + 4]))
844 .map_err(|_| anyhow!("Cannot read schema length."))?;
845
846 ensure!(ptr + 4 + len as usize <= memory.len(), "Illegal memory access when reading schema.");
848 let schema_bytes = &memory[ptr + 4..ptr + 4 + len as usize];
849 SchemaType::deserial(&mut Cursor::new(schema_bytes))
850 .map_err(|_| anyhow!("Failed deserialising the schema."))
851}
852
853pub fn get_inits(module: &Module) -> Vec<&Name> {
855 let mut out = Vec::new();
856 for export in module.export.exports.iter() {
857 if export.name.as_ref().starts_with("init_") && !export.name.as_ref().contains('.') {
858 if let ExportDescription::Func {
859 ..
860 } = export.description
861 {
862 out.push(&export.name);
863 }
864 }
865 }
866 out
867}
868
869pub fn get_receives(module: &Module) -> Vec<&Name> {
871 let mut out = Vec::new();
872 for export in module.export.exports.iter() {
873 if export.name.as_ref().contains('.') {
874 if let ExportDescription::Func {
875 ..
876 } = export.description
877 {
878 out.push(&export.name);
879 }
880 }
881 }
882 out
883}
884
885pub fn get_embedded_schema_v0(bytes: &[u8]) -> ExecResult<schema::VersionedModuleSchema> {
891 let skeleton = parse_skeleton(bytes)?;
892 let mut schema_v1_section = None;
893 let mut schema_versioned_section = None;
894 for ucs in skeleton.custom.iter() {
895 let cs = parse_custom(ucs)?;
896
897 if cs.name.as_ref() == "concordium-schema" && schema_versioned_section.is_none() {
898 schema_versioned_section = Some(cs)
899 } else if cs.name.as_ref() == "concordium-schema-v1" && schema_v1_section.is_none() {
900 schema_v1_section = Some(cs)
901 }
902 }
903
904 if let Some(cs) = schema_versioned_section {
905 let module: schema::VersionedModuleSchema =
906 from_bytes(cs.contents).map_err(|_| anyhow!("Failed parsing schema"))?;
907 Ok(module)
908 } else if let Some(cs) = schema_v1_section {
909 let module = from_bytes(cs.contents).map_err(|_| anyhow!("Failed parsing schema"))?;
910 Ok(schema::VersionedModuleSchema::V0(module))
911 } else {
912 bail!("No schema found in the module")
913 }
914}
915
916pub fn get_embedded_schema_v1(bytes: &[u8]) -> ExecResult<schema::VersionedModuleSchema> {
922 let skeleton = parse_skeleton(bytes)?;
923 let mut schema_v2_section = None;
924 let mut schema_versioned_section = None;
925 for ucs in skeleton.custom.iter() {
926 let cs = parse_custom(ucs)?;
927 if cs.name.as_ref() == "concordium-schema" && schema_versioned_section.is_none() {
928 schema_versioned_section = Some(cs)
929 } else if cs.name.as_ref() == "concordium-schema-v2" && schema_v2_section.is_none() {
930 schema_v2_section = Some(cs)
931 }
932 }
933
934 if let Some(cs) = schema_versioned_section {
935 let module: schema::VersionedModuleSchema =
936 from_bytes(cs.contents).map_err(|_| anyhow!("Failed parsing schema"))?;
937 Ok(module)
938 } else if let Some(cs) = schema_v2_section {
939 let module = from_bytes(cs.contents).map_err(|_| anyhow!("Failed parsing schema"))?;
940 Ok(schema::VersionedModuleSchema::V1(module))
941 } else {
942 bail!("No schema found in the module")
943 }
944}
945
946#[derive(Debug, Clone, concordium_contracts_common::Serialize)]
949pub struct BuildInfo {
950 pub archive_hash: hashes::Hash,
954 pub source_link: Option<String>,
956 pub image: String,
958 pub build_command: Vec<String>,
961}
962
963#[derive(Debug, Clone, concordium_contracts_common::Serialize)]
971pub enum VersionedBuildInfo {
972 V0(BuildInfo),
973}
974
975pub const BUILD_INFO_SECTION_NAME: &str = "concordium-build-info";
978
979#[derive(Debug, thiserror::Error)]
980pub enum CustomSectionLookupError {
981 #[error("Custom section with a provided name is not present.")]
982 Missing,
983 #[error("Multiple custom sections with the given name are present.")]
984 Multiple,
985 #[error("Parse error: {0}.")]
986 MalformedData(#[from] anyhow::Error),
987}
988
989impl CustomSectionLookupError {
990 pub fn is_missing(&self) -> bool { matches!(self, Self::Missing) }
992}
993
994pub fn get_build_info(bytes: &[u8]) -> Result<VersionedBuildInfo, CustomSectionLookupError> {
996 let skeleton = parse_skeleton(bytes)?;
997 get_build_info_from_skeleton(&skeleton)
998}
999
1000pub fn get_build_info_from_skeleton(
1002 skeleton: &Skeleton,
1003) -> Result<VersionedBuildInfo, CustomSectionLookupError> {
1004 let mut build_context_section = None;
1005 for ucs in skeleton.custom.iter() {
1006 let cs = parse_custom(ucs)?;
1007 if cs.name.as_ref() == BUILD_INFO_SECTION_NAME
1008 && build_context_section.replace(cs).is_some()
1009 {
1010 return Err(CustomSectionLookupError::Multiple);
1011 }
1012 }
1013 let Some(cs) = build_context_section else {
1014 return Err(CustomSectionLookupError::Missing);
1015 };
1016 let info: VersionedBuildInfo = from_bytes(cs.contents).context("Failed parsing build info")?;
1017 Ok(info)
1018}
1019
1020#[cfg(test)]
1021mod tests {
1023
1024 #[test]
1025 fn test_schema_embeddings() {
1026 let data =
1027 std::fs::read("../testdata/schemas/cis1-wccd-embedded-schema-v0-unversioned.wasm")
1028 .expect("Could not read file.");
1029 if let Err(e) = super::get_embedded_schema_v0(&data) {
1030 panic!("Failed to parse unversioned v0 module schema: {}", e);
1031 }
1032
1033 let data =
1034 std::fs::read("../testdata/schemas/cis2-wccd-embedded-schema-v1-unversioned.wasm.v1")
1035 .expect("Could not read file.");
1036 if let Err(e) = super::get_embedded_schema_v1(&data[8..]) {
1037 panic!("Failed to parse unversioned v1 module schema: {}", e);
1038 }
1039
1040 let data =
1041 std::fs::read("../testdata/schemas/cis1-wccd-embedded-schema-v0-versioned.wasm.v0")
1042 .expect("Could not read file.");
1043 if let Err(e) = super::get_embedded_schema_v0(&data[8..]) {
1044 panic!("Failed to parse versioned v0 module schema: {}", e);
1045 }
1046
1047 let data =
1048 std::fs::read("../testdata/schemas/cis2-wccd-embedded-schema-v1-versioned.wasm.v1")
1049 .expect("Could not read file.");
1050 if let Err(e) = super::get_embedded_schema_v1(&data[8..]) {
1051 panic!("Failed to parse versioned v1 module schema: {}", e);
1052 }
1053 }
1054}