extern crate alloc;
use alloc::string::String;
use alloc::vec::Vec;
use blake3::Hasher;
use crate::codec::Codec32;
use crate::collections::{StorageBlob, StorageMap, StorageVec};
pub trait Manifest {
fn manifest() -> ContractManifest;
}
pub fn manifest_of<M: Manifest>() -> ContractManifest {
M::manifest()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StorageKeySpec {
pub offset: usize,
pub len: usize,
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct ContractManifest {
pub declared_reads: Vec<[u8; 32]>,
pub declared_writes: Vec<[u8; 32]>,
pub commutative_keys: Vec<[u8; 32]>,
pub storage_key_specs: Vec<StorageKeySpec>,
}
impl ContractManifest {
pub fn new() -> Self {
Self::default()
}
pub fn add_read_slot(&mut self, slot: [u8; 32]) -> &mut Self {
push_unique_slot(&mut self.declared_reads, slot);
self
}
pub fn add_write_slot(&mut self, slot: [u8; 32]) -> &mut Self {
push_unique_slot(&mut self.declared_writes, slot);
self
}
pub fn add_commutative_key(&mut self, slot: [u8; 32]) -> &mut Self {
push_unique_slot(&mut self.commutative_keys, slot);
self
}
pub fn add_storage_key_spec(&mut self, spec: StorageKeySpec) -> &mut Self {
if !self.storage_key_specs.contains(&spec) {
self.storage_key_specs.push(spec);
}
self
}
pub fn normalize(&mut self) {
self.declared_reads.sort();
self.declared_reads.dedup();
self.declared_writes.sort();
self.declared_writes.dedup();
self.commutative_keys.sort();
self.commutative_keys.dedup();
self.storage_key_specs.sort();
self.storage_key_specs.dedup();
}
pub fn normalized(mut self) -> Self {
self.normalize();
self
}
pub fn manifest_hash(&self, bytecode: &[u8]) -> [u8; 32] {
let canonical = self.clone().normalized();
let mut hasher = Hasher::new();
hasher.update(bytecode);
for slot in &canonical.declared_reads {
hasher.update(slot);
}
for slot in &canonical.declared_writes {
hasher.update(slot);
}
for slot in &canonical.commutative_keys {
hasher.update(slot);
}
*hasher.finalize().as_bytes()
}
pub fn to_json_pretty(&self) -> String {
let canonical = self.clone().normalized();
let mut out = String::new();
out.push_str("{\n");
push_slot_list(&mut out, "declared_reads", &canonical.declared_reads, true);
push_slot_list(
&mut out,
"declared_writes",
&canonical.declared_writes,
true,
);
push_slot_list(
&mut out,
"commutative_keys",
&canonical.commutative_keys,
true,
);
push_specs_list(&mut out, &canonical.storage_key_specs);
out.push_str("}\n");
out
}
}
#[derive(Clone, Debug, Default)]
pub struct ManifestBuilder {
manifest: ContractManifest,
}
impl ManifestBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn read_slot(mut self, slot: [u8; 32]) -> Self {
self.manifest.add_read_slot(slot);
self
}
pub fn write_slot(mut self, slot: [u8; 32]) -> Self {
self.manifest.add_write_slot(slot);
self
}
pub fn commutative_slot(mut self, slot: [u8; 32]) -> Self {
self.manifest.add_commutative_key(slot);
self
}
pub fn storage_key_spec(mut self, offset: usize, len: usize) -> Self {
self.manifest
.add_storage_key_spec(StorageKeySpec { offset, len });
self
}
pub fn read_map_contains<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
self.manifest.add_read_slot(map.exists_slot_for(key));
self
}
pub fn read_map_get<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
let (exists, value) = map.slots_for_key(key);
self.manifest.add_read_slot(exists).add_read_slot(value);
self
}
pub fn write_map_set<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
let (exists, value) = map.slots_for_key(key);
self.manifest.add_write_slot(exists).add_write_slot(value);
self
}
pub fn write_map_remove<V: Codec32>(mut self, map: &StorageMap<V>, key: &[u8]) -> Self {
let (exists, value) = map.slots_for_key(key);
self.manifest.add_write_slot(exists).add_write_slot(value);
self
}
pub fn read_vec_index<V: Codec32>(mut self, list: &StorageVec<V>, index: u64) -> Self {
self.manifest
.add_read_slot(list.len_slot())
.add_read_slot(list.slot_for_index(index));
self
}
pub fn write_vec_index<V: Codec32>(mut self, list: &StorageVec<V>, index: u64) -> Self {
self.manifest
.add_read_slot(list.len_slot())
.add_write_slot(list.slot_for_index(index));
self
}
pub fn write_vec_len<V: Codec32>(mut self, list: &StorageVec<V>) -> Self {
self.manifest.add_write_slot(list.len_slot());
self
}
pub fn read_blob_chunk(mut self, blob: &StorageBlob, chunk_index: u64) -> Self {
self.manifest
.add_read_slot(blob.len_slot())
.add_read_slot(blob.slot_for_chunk(chunk_index));
self
}
pub fn write_blob_chunk(mut self, blob: &StorageBlob, chunk_index: u64) -> Self {
self.manifest
.add_write_slot(blob.len_slot())
.add_write_slot(blob.slot_for_chunk(chunk_index));
self
}
pub fn build(mut self) -> ContractManifest {
self.manifest.normalize();
self.manifest
}
}
fn push_unique_slot(slots: &mut Vec<[u8; 32]>, slot: [u8; 32]) {
if !slots.contains(&slot) {
slots.push(slot);
}
}
fn push_slot_list(out: &mut String, name: &str, slots: &[[u8; 32]], trailing_comma: bool) {
out.push_str(" \"");
out.push_str(name);
out.push_str("\": [\n");
for (idx, slot) in slots.iter().enumerate() {
out.push_str(" \"");
out.push_str(&hex32(slot));
out.push('"');
if idx + 1 != slots.len() {
out.push(',');
}
out.push('\n');
}
out.push_str(" ]");
if trailing_comma {
out.push(',');
}
out.push('\n');
}
fn push_specs_list(out: &mut String, specs: &[StorageKeySpec]) {
out.push_str(" \"storage_key_specs\": [\n");
for (idx, spec) in specs.iter().enumerate() {
out.push_str(" { \"offset\": ");
push_usize(out, spec.offset);
out.push_str(", \"len\": ");
push_usize(out, spec.len);
out.push_str(" }");
if idx + 1 != specs.len() {
out.push(',');
}
out.push('\n');
}
out.push_str(" ]\n");
}
fn push_usize(out: &mut String, value: usize) {
let mut buf = [0u8; 20];
let mut n = value;
let mut cursor = buf.len();
if n == 0 {
out.push('0');
return;
}
while n > 0 {
cursor -= 1;
buf[cursor] = b'0' + (n % 10) as u8;
n /= 10;
}
for b in &buf[cursor..] {
out.push(*b as char);
}
}
fn hex32(bytes: &[u8; 32]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(64);
for b in bytes {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0f) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::collections::Namespace;
#[test]
fn manifest_builder_dedups_and_hashes() {
let map = StorageMap::<u64>::new(Namespace([1u8; 32]));
let manifest = ManifestBuilder::new()
.read_map_get(&map, b"alice")
.read_map_get(&map, b"alice")
.write_map_set(&map, b"alice")
.storage_key_spec(4, 32)
.storage_key_spec(4, 32)
.build();
assert_eq!(manifest.storage_key_specs.len(), 1);
assert_eq!(manifest.declared_reads.len(), 2);
assert_eq!(manifest.declared_writes.len(), 2);
let hash_a = manifest.manifest_hash(&[1, 2, 3]);
let hash_b = manifest.clone().normalized().manifest_hash(&[1, 2, 3]);
assert_eq!(hash_a, hash_b);
}
#[test]
fn manifest_json_contains_required_keys() {
let manifest = ContractManifest::new();
let json = manifest.to_json_pretty();
assert!(json.contains("\"declared_reads\""));
assert!(json.contains("\"declared_writes\""));
assert!(json.contains("\"commutative_keys\""));
assert!(json.contains("\"storage_key_specs\""));
}
#[derive(crate::Manifest)]
#[manifest(
read_derived(namespace = "tests.counter", key = "value"),
write_derived(namespace = "tests.counter", key = "value"),
key_spec(offset = 4, len = 32)
)]
struct CounterManifestSpec;
#[test]
fn derive_manifest_builds_contract_manifest() {
let manifest = <CounterManifestSpec as Manifest>::manifest();
assert_eq!(manifest.declared_reads.len(), 1);
assert_eq!(manifest.declared_writes.len(), 1);
assert_eq!(manifest.storage_key_specs.len(), 1);
}
const MANUAL_SLOT: [u8; 32] = [0xAA; 32];
#[derive(crate::Manifest)]
#[manifest(
read_map(namespace = "tests.map", key = "alice"),
write_map(namespace = "tests.map", key = "alice"),
read_vec_index(namespace = "tests.vec", index = 3),
write_vec_index(namespace = "tests.vec", index = 5),
read_blob_chunk(namespace = "tests.blob", chunk = 0),
write_blob_chunk(namespace = "tests.blob", chunk = 2),
read_slot_expr = "crate::manifest::tests::MANUAL_SLOT",
commutative_label = "tests.delta",
key_spec(offset = 8, len = 24)
)]
struct RichManifestSpec;
#[test]
fn derive_manifest_supports_map_vec_blob_helpers() {
let manifest = <RichManifestSpec as Manifest>::manifest();
assert_eq!(manifest.declared_reads.len(), 7);
assert_eq!(manifest.declared_writes.len(), 5);
assert_eq!(manifest.commutative_keys.len(), 1);
assert_eq!(manifest.storage_key_specs.len(), 1);
}
}