Skip to main content

jam_program_blob_common/
program_blob.rs

1use alloc::{borrow::Cow, string::String, vec::Vec};
2use codec::{Compact, CompactLen, Decode, Encode};
3
4/// Information on a crate, useful for building conventional metadata of type 0.
5#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
6pub struct CrateInfo {
7	pub name: String,
8	pub version: String,
9	pub license: String,
10	pub authors: Vec<String>,
11}
12
13impl core::fmt::Display for CrateInfo {
14	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
15		let name = match self.name.trim() {
16			"" => "<no-name>",
17			other => other,
18		};
19		let version = match self.version.trim() {
20			"" => "<no-version>",
21			other => other,
22		};
23		write!(f, "{name} v{version} by ")?;
24		if self.authors.is_empty() || self.authors.iter().all(|s| s.trim().is_empty()) {
25			write!(f, "<no-authors>")?;
26		} else {
27			let mut iter = self.authors.iter().map(|s| s.trim()).filter(|s| !s.is_empty());
28			if let Some(first) = iter.next() {
29				write!(f, "{first}")?;
30			}
31			for author in iter {
32				write!(f, ", {author}")?;
33			}
34		}
35		Ok(())
36	}
37}
38
39/// Information which, when encoded, could fill a program blob's metadata.
40#[derive(Clone, PartialEq, Eq, Encode, Decode, Debug)]
41pub enum ConventionalMetadata {
42	Info(CrateInfo),
43}
44
45/// Encode and write `ConventionalMetadata` to the `output`.
46pub fn write_metadata(
47	metadata: &ConventionalMetadata,
48	output: &mut Vec<u8>,
49) -> Result<(), &'static str> {
50	let len = metadata.encoded_size();
51	write_var(u32::try_from(len).map_err(|_| "metadata too large")?, output);
52	metadata.encode_to(output);
53	Ok(())
54}
55
56/// Read and decode `ConventionalMetadata`.
57pub fn read_metadata(bytes: &mut &[u8]) -> Result<ConventionalMetadata, codec::Error> {
58	let mut metadata = read_metadata_slice(bytes).ok_or("Failed to parse metadata")?;
59	ConventionalMetadata::decode(&mut metadata)
60}
61
62/// Read `ConventionalMetadata` as bytes.
63pub fn read_metadata_slice<'a>(bytes: &mut &'a [u8]) -> Option<&'a [u8]> {
64	let offset = read_var(bytes)?;
65	let metadata = read_slice(bytes, offset)?;
66	Some(metadata)
67}
68
69/// A JAM-specific program blob.
70pub struct ProgramBlob<'a> {
71	pub metadata: Cow<'a, [u8]>,
72	pub ro_data: Cow<'a, [u8]>,
73	pub rw_data: Cow<'a, [u8]>,
74	pub code_blob: Cow<'a, [u8]>,
75	pub rw_data_padding_pages: u16,
76	pub stack_size: u32,
77}
78
79fn read_u24(bytes: &mut &[u8]) -> Option<u32> {
80	let xs = bytes.get(..3)?;
81	*bytes = &bytes[3..];
82	Some(u32::from_le_bytes([xs[0], xs[1], xs[2], 0]))
83}
84
85fn write_u24(value: u32, output: &mut Vec<u8>) -> Result<(), ()> {
86	if value >= (1 << 24) {
87		return Err(());
88	}
89
90	output.extend_from_slice(&value.to_le_bytes()[0..3]);
91	Ok(())
92}
93
94fn read_u16(bytes: &mut &[u8]) -> Option<u16> {
95	let xs = bytes.get(..2)?;
96	*bytes = &bytes[2..];
97	Some(u16::from_le_bytes([xs[0], xs[1]]))
98}
99
100fn read_u32(bytes: &mut &[u8]) -> Option<u32> {
101	let xs = bytes.get(..4)?;
102	*bytes = &bytes[4..];
103	Some(u32::from_le_bytes([xs[0], xs[1], xs[2], xs[3]]))
104}
105
106fn read_var(bytes: &mut &[u8]) -> Option<u32> {
107	Some(Compact::<u32>::decode(bytes).ok()?.0)
108}
109
110fn write_var(value: u32, output: &mut Vec<u8>) {
111	Compact::<u32>(value).encode_to(output)
112}
113
114fn read_cow<'a>(bytes: &mut &'a [u8], length: u32) -> Option<Cow<'a, [u8]>> {
115	read_slice(bytes, length).map(Cow::Borrowed)
116}
117
118fn read_slice<'a>(bytes: &mut &'a [u8], length: u32) -> Option<&'a [u8]> {
119	let length = length as usize;
120	let slice = bytes.get(..length)?;
121	*bytes = &bytes[length..];
122	Some(slice)
123}
124
125impl<'a> ProgramBlob<'a> {
126	pub fn from_bytes(mut bytes: &'a [u8]) -> Option<Self> {
127		let metadata = Cow::Borrowed(read_metadata_slice(&mut bytes)?);
128		let ro_data_len = read_u24(&mut bytes)?;
129		let rw_data_len = read_u24(&mut bytes)?;
130		let rw_data_padding_pages = read_u16(&mut bytes)?;
131		let stack_size = read_u24(&mut bytes)?;
132		let ro_data = read_cow(&mut bytes, ro_data_len)?;
133		let rw_data = read_cow(&mut bytes, rw_data_len)?;
134		let code_blob_len = read_u32(&mut bytes)?;
135		let code_blob = read_cow(&mut bytes, code_blob_len)?;
136
137		if !bytes.is_empty() {
138			return None;
139		}
140
141		Some(ProgramBlob {
142			metadata,
143			rw_data_padding_pages,
144			stack_size,
145			ro_data,
146			rw_data,
147			code_blob,
148		})
149	}
150
151	pub fn to_vec(&self) -> Result<Vec<u8>, &'static str> {
152		let mut output = Vec::new();
153		write_var(
154			u32::try_from(self.metadata.len()).map_err(|_| "metadata too large")?,
155			&mut output,
156		);
157		output.extend_from_slice(&self.metadata);
158		write_u24(u32::try_from(self.ro_data.len()).map_err(|_| "too large RO data")?, &mut output)
159			.map_err(|_| "too large RO data")?;
160		write_u24(u32::try_from(self.rw_data.len()).map_err(|_| "too large RW data")?, &mut output)
161			.map_err(|_| "too large RW data")?;
162		output.extend_from_slice(&self.rw_data_padding_pages.to_le_bytes());
163		write_u24(self.stack_size, &mut output).map_err(|_| "too large stack size")?;
164		output.extend_from_slice(&self.ro_data);
165		output.extend_from_slice(&self.rw_data);
166		output.extend_from_slice(
167			&u32::try_from(self.code_blob.len()).map_err(|_| "too large code")?.to_le_bytes(),
168		);
169		output.extend_from_slice(&self.code_blob);
170		Ok(output)
171	}
172}
173
174#[cfg(feature = "polkavm")]
175impl From<ProgramBlob<'_>> for polkavm::ProgramParts {
176	fn from(other: ProgramBlob<'_>) -> Self {
177		let mut parts = polkavm::ProgramParts::empty(polkavm::program::InstructionSetKind::JamV1);
178		parts.ro_data_size = other.ro_data.len() as u32;
179		parts.rw_data_size = other.rw_data.len().next_multiple_of(4096) as u32 +
180			other.rw_data_padding_pages as u32 * 4096;
181		parts.stack_size = other.stack_size;
182		parts.ro_data = other.ro_data.into();
183		parts.rw_data = other.rw_data.into();
184		parts.code_and_jump_table = other.code_blob.into();
185		parts
186	}
187}
188
189#[cfg(feature = "polkavm")]
190impl<'a> ProgramBlob<'a> {
191	pub fn from_pvm(parts: &'a polkavm::ProgramParts, metadata: Cow<'a, [u8]>) -> Self {
192		// Pad RO section with zeroes.
193		let mut ro_data = parts.ro_data.to_vec();
194		ro_data.resize(parts.ro_data_size as usize, 0);
195		// Calculate the padding for RW section.
196		let padding = (parts.rw_data_size as usize).next_multiple_of(4096) -
197			parts.rw_data.len().next_multiple_of(4096);
198		let rw_data_padding_pages = padding / 4096;
199		let rw_data_padding_pages =
200			rw_data_padding_pages.try_into().expect("The RW data section is too big");
201		Self {
202			metadata,
203			ro_data: ro_data.into(),
204			rw_data: (&parts.rw_data[..]).into(),
205			code_blob: (&parts.code_and_jump_table[..]).into(),
206			rw_data_padding_pages,
207			stack_size: parts.stack_size,
208		}
209	}
210}
211
212/// A CoreVM-specific program blob.
213pub struct CoreVmProgramBlob<'a, 'b> {
214	/// Serialized conventional metadata.
215	pub metadata: Cow<'a, [u8]>,
216	/// PVM binary blob.
217	pub pvm_blob: Cow<'b, [u8]>,
218}
219
220impl<'a> CoreVmProgramBlob<'a, 'a> {
221	/// Deserialize from bytes.
222	pub fn from_bytes(mut bytes: &'a [u8]) -> Option<Self> {
223		let metadata = Cow::Borrowed(read_metadata_slice(&mut bytes)?);
224		let pvm_blob = Cow::Borrowed(bytes);
225		Some(Self { metadata, pvm_blob })
226	}
227
228	/// Serialize into bytes.
229	pub fn to_vec(&self) -> Result<Vec<u8>, &'static str> {
230		let metadata_len = u32::try_from(self.metadata.len()).map_err(|_| "metadata too large")?;
231		let mut output = Vec::with_capacity(
232			Compact::<u32>::compact_len(&metadata_len) +
233				metadata_len as usize +
234				self.pvm_blob.len(),
235		);
236		write_var(metadata_len, &mut output);
237		output.extend_from_slice(&self.metadata);
238		output.extend_from_slice(&self.pvm_blob);
239		Ok(output)
240	}
241}
242
243#[cfg(test)]
244mod tests {
245	use super::*;
246
247	#[test]
248	fn read_write_u24() {
249		let mut output = Vec::new();
250		write_u24(0x00345678, &mut output).unwrap();
251		assert_eq!(read_u24(&mut &output[..]), Some(0x00345678));
252
253		assert!(write_u24(0x00ffffff, &mut output).is_ok());
254		assert!(write_u24(0x01000000, &mut output).is_err());
255	}
256
257	#[test]
258	fn read_write_var() {
259		let mut output = Vec::new();
260		let vals = [0x00345678, 0x00, 0x01, 0x7f, 0x80, 0xffffffff];
261		for i in vals.into_iter() {
262			write_var(i, &mut output);
263		}
264		let mut cursor = output.as_ref();
265		for i in vals.into_iter() {
266			assert_eq!(read_var(&mut cursor), Some(i));
267		}
268	}
269
270	#[test]
271	fn metadata_read_write() {
272		let info = CrateInfo {
273			name: "x".into(),
274			version: "y".into(),
275			license: "z".into(),
276			authors: vec!["w".into(), "v".into()],
277		};
278		let metadata = ConventionalMetadata::Info(info);
279		let mut output = Vec::new();
280		write_metadata(&metadata, &mut output).unwrap();
281		let actual = read_metadata(&mut &output[..]).unwrap();
282		assert_eq!(metadata, actual);
283	}
284
285	#[test]
286	fn corevm_program_blob_read_write() {
287		let info = CrateInfo {
288			name: "x".into(),
289			version: "y".into(),
290			license: "z".into(),
291			authors: vec!["w".into(), "v".into()],
292		};
293		let metadata = ConventionalMetadata::Info(info).encode().into();
294		// This is just a program composed of a single return.
295		let pvm_blob = vec![0, 0, 2, 50, 0, 1];
296		let corevm_blob = CoreVmProgramBlob { metadata, pvm_blob: pvm_blob.as_slice().into() };
297		let corevm_blob_bytes = corevm_blob.to_vec().unwrap();
298		let actual_blob = CoreVmProgramBlob::from_bytes(&corevm_blob_bytes[..]).unwrap();
299		assert_eq!(corevm_blob.metadata, actual_blob.metadata);
300		assert_eq!(corevm_blob.pvm_blob, actual_blob.pvm_blob);
301		assert_eq!(pvm_blob.as_slice(), actual_blob.pvm_blob.as_ref());
302	}
303}