runmat_execution/executable/
component.rs1use serde::{Deserialize, Serialize};
2
3use super::ExecutableComponentRevisions;
4use crate::{ContractError, Digest};
5
6pub const EXECUTABLE_COMPONENT_MAX_BYTES: u64 = 64 * 1024 * 1024;
7
8#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
9#[serde(rename_all = "snake_case")]
10pub enum ExecutableComponentKind {
11 Mir,
12 Analysis,
13 Bytecode,
14 VmLayout,
15 FunctionRegistry,
16 SourceMap,
17}
18
19impl ExecutableComponentKind {
20 pub const REQUIRED: [Self; 6] = [
21 Self::Mir,
22 Self::Analysis,
23 Self::Bytecode,
24 Self::VmLayout,
25 Self::FunctionRegistry,
26 Self::SourceMap,
27 ];
28
29 pub(crate) const fn schema(self, revisions: &ExecutableComponentRevisions) -> u16 {
30 match self {
31 Self::Mir => revisions.mir_schema,
32 Self::Analysis => revisions.analysis_schema,
33 Self::Bytecode => revisions.bytecode_schema,
34 Self::VmLayout => revisions.vm_layout_schema,
35 Self::FunctionRegistry => revisions.function_registry_schema,
36 Self::SourceMap => revisions.source_map_schema,
37 }
38 }
39}
40
41#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
42#[serde(deny_unknown_fields)]
43pub struct ExecutableComponentDescriptor {
44 pub kind: ExecutableComponentKind,
45 pub schema_version: u16,
46 pub digest: Digest,
47 pub encoded_length: u64,
48}
49
50impl ExecutableComponentDescriptor {
51 pub fn from_payload(
52 kind: ExecutableComponentKind,
53 schema_version: u16,
54 bytes: &[u8],
55 ) -> Result<Self, ContractError> {
56 let descriptor = Self {
57 kind,
58 schema_version,
59 digest: Digest::sha256(bytes),
60 encoded_length: bytes.len() as u64,
61 };
62 descriptor.validate()?;
63 Ok(descriptor)
64 }
65
66 fn validate(&self) -> Result<(), ContractError> {
67 if self.schema_version == 0 {
68 return Err(ContractError::invalid(
69 "executable.components.schema_version",
70 "version must be non-zero",
71 ));
72 }
73 if self.encoded_length == 0 {
74 return Err(ContractError::invalid(
75 "executable.components.encoded_length",
76 "required component payloads must not be empty",
77 ));
78 }
79 if self.encoded_length > EXECUTABLE_COMPONENT_MAX_BYTES {
80 return Err(ContractError::Limit {
81 field: "executable.components.encoded_length",
82 limit: EXECUTABLE_COMPONENT_MAX_BYTES,
83 });
84 }
85 Ok(())
86 }
87}
88
89#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
90#[serde(deny_unknown_fields)]
91pub struct ExecutableComponentPayload {
92 pub kind: ExecutableComponentKind,
93 pub bytes: Vec<u8>,
94}
95
96impl ExecutableComponentPayload {
97 pub fn new(kind: ExecutableComponentKind, bytes: Vec<u8>) -> Result<Self, ContractError> {
98 if bytes.is_empty() {
99 return Err(ContractError::invalid(
100 "executable.payloads.bytes",
101 "required component payloads must not be empty",
102 ));
103 }
104 if bytes.len() as u64 > EXECUTABLE_COMPONENT_MAX_BYTES {
105 return Err(ContractError::Limit {
106 field: "executable.payloads.bytes",
107 limit: EXECUTABLE_COMPONENT_MAX_BYTES,
108 });
109 }
110 Ok(Self { kind, bytes })
111 }
112}
113
114pub(crate) fn validate_component_descriptors(
115 components: &[ExecutableComponentDescriptor],
116 revisions: &ExecutableComponentRevisions,
117) -> Result<(), ContractError> {
118 if components.len() != ExecutableComponentKind::REQUIRED.len() {
119 return Err(ContractError::invalid(
120 "executable.components",
121 "all required executable components must be present exactly once",
122 ));
123 }
124 for (descriptor, required) in components.iter().zip(ExecutableComponentKind::REQUIRED) {
125 descriptor.validate()?;
126 if descriptor.kind != required {
127 return Err(ContractError::invalid(
128 "executable.components",
129 "components must be complete, unique, and sorted by kind",
130 ));
131 }
132 if descriptor.schema_version != descriptor.kind.schema(revisions) {
133 return Err(ContractError::invalid(
134 "executable.components.schema_version",
135 "component schema does not match executable revisions",
136 ));
137 }
138 }
139 Ok(())
140}