1extern crate alloc;
62
63use alloc::string::String;
64use alloc::vec::Vec;
65use blake3::Hasher;
66
67use crate::codec::Codec32;
68use crate::collections::{StorageBlob, StorageMap, StorageVec};
69
70pub trait Manifest {
84 fn manifest() -> ContractManifest;
86}
87
88pub fn manifest_of<M: Manifest>() -> ContractManifest {
90 M::manifest()
91}
92
93#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
105pub struct StorageKeySpec {
106 pub offset: usize,
108 pub len: usize,
110}
111
112#[derive(Clone, Debug, PartialEq, Eq, Default)]
139pub struct ContractManifest {
140 pub declared_reads: Vec<[u8; 32]>,
142 pub declared_writes: Vec<[u8; 32]>,
144 pub commutative_keys: Vec<[u8; 32]>,
146 pub storage_key_specs: Vec<StorageKeySpec>,
148}
149
150impl ContractManifest {
151 pub fn new() -> Self {
153 Self::default()
154 }
155
156 pub fn add_read_slot(&mut self, slot: [u8; 32]) -> &mut Self {
160 push_unique_slot(&mut self.declared_reads, slot);
161 self
162 }
163
164 pub fn add_write_slot(&mut self, slot: [u8; 32]) -> &mut Self {
168 push_unique_slot(&mut self.declared_writes, slot);
169 self
170 }
171
172 pub fn add_commutative_key(&mut self, slot: [u8; 32]) -> &mut Self {
177 push_unique_slot(&mut self.commutative_keys, slot);
178 self
179 }
180
181 pub fn add_storage_key_spec(&mut self, spec: StorageKeySpec) -> &mut Self {
187 if !self.storage_key_specs.contains(&spec) {
188 self.storage_key_specs.push(spec);
189 }
190 self
191 }
192
193 pub fn normalize(&mut self) {
197 self.declared_reads.sort();
198 self.declared_reads.dedup();
199 self.declared_writes.sort();
200 self.declared_writes.dedup();
201 self.commutative_keys.sort();
202 self.commutative_keys.dedup();
203 self.storage_key_specs.sort();
204 self.storage_key_specs.dedup();
205 }
206
207 pub fn normalized(mut self) -> Self {
211 self.normalize();
212 self
213 }
214
215 pub fn manifest_hash(&self, bytecode: &[u8]) -> [u8; 32] {
232 let canonical = self.clone().normalized();
233 let mut hasher = Hasher::new();
234 hasher.update(bytecode);
235 for slot in &canonical.declared_reads {
236 hasher.update(slot);
237 }
238 for slot in &canonical.declared_writes {
239 hasher.update(slot);
240 }
241 for slot in &canonical.commutative_keys {
242 hasher.update(slot);
243 }
244 *hasher.finalize().as_bytes()
245 }
246
247 pub fn to_json_pretty(&self) -> String {
268 let canonical = self.clone().normalized();
269 let mut out = String::new();
270 out.push_str("{\n");
271 push_slot_list(&mut out, "declared_reads", &canonical.declared_reads, true);
272 push_slot_list(
273 &mut out,
274 "declared_writes",
275 &canonical.declared_writes,
276 true,
277 );
278 push_slot_list(
279 &mut out,
280 "commutative_keys",
281 &canonical.commutative_keys,
282 true,
283 );
284 push_specs_list(&mut out, &canonical.storage_key_specs);
285 out.push_str("}\n");
286 out
287 }
288}
289
290#[derive(Clone, Debug, Default)]
311pub struct ManifestBuilder {
312 manifest: ContractManifest,
313}
314
315impl ManifestBuilder {
316 pub fn new() -> Self {
318 Self::default()
319 }
320
321 pub fn read_slot(mut self, slot: [u8; 32]) -> Self {
323 self.manifest.add_read_slot(slot);
324 self
325 }
326
327 pub fn write_slot(mut self, slot: [u8; 32]) -> Self {
329 self.manifest.add_write_slot(slot);
330 self
331 }
332
333 pub fn commutative_slot(mut self, slot: [u8; 32]) -> Self {
335 self.manifest.add_commutative_key(slot);
336 self
337 }
338
339 pub fn storage_key_spec(mut self, offset: usize, len: usize) -> Self {
341 self.manifest
342 .add_storage_key_spec(StorageKeySpec { offset, len });
343 self
344 }
345
346 pub fn read_map_contains<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
348 self.manifest.add_read_slot(map.exists_slot_for(key));
349 self
350 }
351
352 pub fn read_map_get<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
354 let (exists, value) = map.slots_for_key(key);
355 self.manifest.add_read_slot(exists).add_read_slot(value);
356 self
357 }
358
359 pub fn write_map_set<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
361 let (exists, value) = map.slots_for_key(key);
362 self.manifest.add_write_slot(exists).add_write_slot(value);
363 self
364 }
365
366 pub fn write_map_remove<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
368 let (exists, value) = map.slots_for_key(key);
369 self.manifest.add_write_slot(exists).add_write_slot(value);
370 self
371 }
372
373 pub fn read_vec_index<V: Codec32>(mut self, list: &StorageVec<V>, index: u64) -> Self {
375 self.manifest
376 .add_read_slot(list.len_slot())
377 .add_read_slot(list.slot_for_index(index));
378 self
379 }
380
381 pub fn write_vec_index<V: Codec32>(mut self, list: &StorageVec<V>, index: u64) -> Self {
383 self.manifest
384 .add_read_slot(list.len_slot())
385 .add_write_slot(list.slot_for_index(index));
386 self
387 }
388
389 pub fn write_vec_len<V: Codec32>(mut self, list: &StorageVec<V>) -> Self {
391 self.manifest.add_write_slot(list.len_slot());
392 self
393 }
394
395 pub fn read_blob_chunk(mut self, blob: &StorageBlob, chunk_index: u64) -> Self {
397 self.manifest
398 .add_read_slot(blob.len_slot())
399 .add_read_slot(blob.slot_for_chunk(chunk_index));
400 self
401 }
402
403 pub fn write_blob_chunk(mut self, blob: &StorageBlob, chunk_index: u64) -> Self {
405 self.manifest
406 .add_write_slot(blob.len_slot())
407 .add_write_slot(blob.slot_for_chunk(chunk_index));
408 self
409 }
410
411 pub fn build(mut self) -> ContractManifest {
415 self.manifest.normalize();
416 self.manifest
417 }
418}
419
420fn push_unique_slot(slots: &mut Vec<[u8; 32]>, slot: [u8; 32]) {
422 if !slots.contains(&slot) {
423 slots.push(slot);
424 }
425}
426
427fn push_slot_list(out: &mut String, name: &str, slots: &[[u8; 32]], trailing_comma: bool) {
428 out.push_str(" \"");
429 out.push_str(name);
430 out.push_str("\": [\n");
431 for (idx, slot) in slots.iter().enumerate() {
432 out.push_str(" \"");
433 out.push_str(&hex32(slot));
434 out.push('"');
435 if idx + 1 != slots.len() {
436 out.push(',');
437 }
438 out.push('\n');
439 }
440 out.push_str(" ]");
441 if trailing_comma {
442 out.push(',');
443 }
444 out.push('\n');
445}
446
447fn push_specs_list(out: &mut String, specs: &[StorageKeySpec]) {
448 out.push_str(" \"storage_key_specs\": [\n");
449 for (idx, spec) in specs.iter().enumerate() {
450 out.push_str(" { \"offset\": ");
451 push_usize(out, spec.offset);
452 out.push_str(", \"len\": ");
453 push_usize(out, spec.len);
454 out.push_str(" }");
455 if idx + 1 != specs.len() {
456 out.push(',');
457 }
458 out.push('\n');
459 }
460 out.push_str(" ]\n");
461}
462
463fn push_usize(out: &mut String, value: usize) {
464 let mut buf = [0u8; 20];
466 let mut n = value;
467 let mut cursor = buf.len();
468 if n == 0 {
469 out.push('0');
470 return;
471 }
472 while n > 0 {
473 cursor -= 1;
474 buf[cursor] = b'0' + (n % 10) as u8;
475 n /= 10;
476 }
477 for b in &buf[cursor..] {
478 out.push(*b as char);
479 }
480}
481
482fn hex32(bytes: &[u8; 32]) -> String {
483 const HEX: &[u8; 16] = b"0123456789abcdef";
484 let mut out = String::with_capacity(64);
485 for b in bytes {
486 out.push(HEX[(b >> 4) as usize] as char);
487 out.push(HEX[(b & 0x0f) as usize] as char);
488 }
489 out
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495 use crate::collections::Namespace;
496
497 #[test]
498 fn manifest_builder_dedups_and_hashes() {
499 let map = StorageMap::<u64>::new(Namespace([1u8; 32]));
500 let manifest = ManifestBuilder::new()
501 .read_map_get(&map, b"alice")
502 .read_map_get(&map, b"alice")
503 .write_map_set(&map, b"alice")
504 .storage_key_spec(4, 32)
505 .storage_key_spec(4, 32)
506 .build();
507
508 assert_eq!(manifest.storage_key_specs.len(), 1);
509 assert_eq!(manifest.declared_reads.len(), 2);
510 assert_eq!(manifest.declared_writes.len(), 2);
511
512 let hash_a = manifest.manifest_hash(&[1, 2, 3]);
513 let hash_b = manifest.clone().normalized().manifest_hash(&[1, 2, 3]);
514 assert_eq!(hash_a, hash_b);
515 }
516
517 #[test]
518 fn manifest_json_contains_required_keys() {
519 let manifest = ContractManifest::new();
520 let json = manifest.to_json_pretty();
521 assert!(json.contains("\"declared_reads\""));
522 assert!(json.contains("\"declared_writes\""));
523 assert!(json.contains("\"commutative_keys\""));
524 assert!(json.contains("\"storage_key_specs\""));
525 }
526
527 #[derive(crate::Manifest)]
528 #[manifest(
529 read_derived(namespace = "tests.counter", key = "value"),
530 write_derived(namespace = "tests.counter", key = "value"),
531 key_spec(offset = 4, len = 32)
532 )]
533 struct CounterManifestSpec;
534
535 #[test]
536 fn derive_manifest_builds_contract_manifest() {
537 let manifest = <CounterManifestSpec as Manifest>::manifest();
538 assert_eq!(manifest.declared_reads.len(), 1);
539 assert_eq!(manifest.declared_writes.len(), 1);
540 assert_eq!(manifest.storage_key_specs.len(), 1);
541 }
542
543 const MANUAL_SLOT: [u8; 32] = [0xAA; 32];
544
545 #[derive(crate::Manifest)]
546 #[manifest(
547 read_map(namespace = "tests.map", key = "alice"),
548 write_map(namespace = "tests.map", key = "alice"),
549 read_vec_index(namespace = "tests.vec", index = 3),
550 write_vec_index(namespace = "tests.vec", index = 5),
551 read_blob_chunk(namespace = "tests.blob", chunk = 0),
552 write_blob_chunk(namespace = "tests.blob", chunk = 2),
553 read_slot_expr = "crate::manifest::tests::MANUAL_SLOT",
554 commutative_label = "tests.delta",
555 key_spec(offset = 8, len = 24)
556 )]
557 struct RichManifestSpec;
558
559 #[test]
560 fn derive_manifest_supports_map_vec_blob_helpers() {
561 let manifest = <RichManifestSpec as Manifest>::manifest();
562 assert_eq!(manifest.declared_reads.len(), 7);
563 assert_eq!(manifest.declared_writes.len(), 5);
564 assert_eq!(manifest.commutative_keys.len(), 1);
565 assert_eq!(manifest.storage_key_specs.len(), 1);
566 }
567}