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