use crate::hasher::Hasher;
use crate::types::StorageKey;
use xdrs::{WriteXDR, cxs::ConstWriter};
#[derive(Debug, Default)]
pub struct Keypath {
hasher: Hasher,
exclusive: bool,
}
impl Clone for Keypath {
fn clone(&self) -> Self {
Self {
hasher: self.hasher.fork(),
exclusive: self.exclusive,
}
}
}
impl Keypath {
#[doc(hidden)]
pub fn root(domain: FramedDomain) -> Self {
let mut kp = Self::default();
kp.hasher.update(&domain.as_bytes());
kp
}
pub fn child<I: WriteXDR + ?Sized>(&self, value: &I) -> Keypath {
let mut child = self.clone();
crate::call::with_scratch(|buf| {
crate::internal::encode_scratch(buf, value).unwrap_or_else(|_| {
crate::exn::library_throw(
crate::exn::LibraryExn::StorageSerialization,
)
});
let bytes: &[u8] = buf.as_slice();
let len: u32 = bytes.len().try_into().unwrap_or_else(|_| {
crate::exn::library_throw(
crate::exn::LibraryExn::StorageKeypathOverflow,
)
});
let mut len_buf = [core::mem::MaybeUninit::<u8>::uninit(); 5];
let written = xdrs::cxs::encode_into(&len, &mut len_buf)
.unwrap_or_else(|_| {
crate::exn::library_throw(
crate::exn::LibraryExn::StorageSerialization,
)
});
let len_bytes: &[u8] =
unsafe { len_buf[..written].assume_init_ref() };
child.hasher.update(&len_bytes);
child.hasher.update(&bytes);
});
child
}
#[doc(hidden)]
pub fn child_framed(&self, frame: &'static FramedComponent) -> Keypath {
let mut child = self.clone();
child.hasher.update(&frame.as_bytes());
child
}
pub fn generate_key(&self) -> StorageKey {
self.hasher.fork().finish().into()
}
pub fn into_key(self) -> StorageKey {
self.hasher.finish().into()
}
pub fn addresses_same_slot(&self, other: &Keypath) -> bool {
self.generate_key() == other.generate_key()
}
pub fn is_exclusive(&self) -> bool {
self.exclusive
}
pub fn set_exclusive(&mut self) {
self.exclusive = true;
}
}
const DOMAIN_FRAME_MAX: usize = 256;
pub const fn framed_domain_len(domain: &str) -> usize {
assert!(
domain.len() + 10 <= DOMAIN_FRAME_MAX,
"storage domain too long; shorten it with #[storage_domain = \"...\"]"
);
let mut inner = ConstWriter::<DOMAIN_FRAME_MAX>::new();
inner.write_opaque_variable(domain.as_bytes());
let inner_len = inner.pos();
let mut outer = ConstWriter::<DOMAIN_FRAME_MAX>::new();
outer.write_u32(inner_len as u32);
outer.pos() + inner_len
}
pub const fn frame_domain<const N: usize>(domain: &str) -> [u8; N] {
let mut probe = ConstWriter::<DOMAIN_FRAME_MAX>::new();
probe.write_opaque_variable(domain.as_bytes());
let inner_len = probe.pos();
let mut w = ConstWriter::<N>::new();
w.write_u32(inner_len as u32);
w.write_opaque_variable(domain.as_bytes());
w.into_bytes()
}
const MAX_FRAMED_U32: usize = 6;
#[derive(Clone, Copy, Debug)]
pub struct FramedComponent {
buf: [u8; MAX_FRAMED_U32],
len: u8,
}
impl FramedComponent {
#[doc(hidden)]
pub const fn from_u32(v: u32) -> Self {
let mut probe = ConstWriter::<5>::new();
probe.write_u32(v);
let inner_len = probe.pos();
let mut w = ConstWriter::<MAX_FRAMED_U32>::new();
w.write_u32(inner_len as u32);
w.write_u32(v);
let len = w.pos() as u8;
while w.pos() < MAX_FRAMED_U32 {
w.write_opaque_fixed(&[0]);
}
Self {
buf: w.into_bytes(),
len,
}
}
fn as_bytes(&self) -> &[u8] {
&self.buf[..self.len as usize]
}
}
#[derive(Clone, Copy, Debug)]
pub struct FramedDomain(&'static [u8]);
impl FramedDomain {
#[doc(hidden)]
pub const fn new(framed: &'static [u8]) -> Self {
Self(framed)
}
pub(crate) const fn as_bytes(&self) -> &'static [u8] {
self.0
}
}
#[cfg(test)]
mod test_keypath {
use super::*;
fn oracle_key(preimage: &[u8]) -> StorageKey {
use sha2::Digest;
let digest: [u8; 32] = sha2::Sha256::digest(preimage).into();
crate::SHA256Hash::new(digest).into()
}
crate::contract_test!(exclusive_flag_propagates_through_clone {
let mut kp = Keypath::default().child("some bytes");
kp.set_exclusive();
assert!(kp.clone().is_exclusive());
});
crate::contract_test!(distinct_paths_distinct_keys {
let root = Keypath::default();
let a = root.child(&[0xAAu8, 0xBB]).child(&[0xCCu8]);
let b = root.child(&[0xAAu8]).child(&[0xBBu8, 0xCC]);
assert!(!a.addresses_same_slot(&b));
assert_eq!(
a.generate_key(),
oracle_key(&[0x02, 0xAA, 0xBB, 0x01, 0xCC])
);
assert_eq!(
b.generate_key(),
oracle_key(&[0x01, 0xAA, 0x02, 0xBB, 0xCC])
);
});
crate::contract_test!(clone_copies_state_and_diverges {
let base = Keypath::default().child(&[1u8, 2, 3]);
let a = base.child(&[0xAAu8, 0xBB]);
let b = base.child(&[0xCCu8]);
assert_eq!(
a.generate_key(),
oracle_key(&[0x03, 1, 2, 3, 0x02, 0xAA, 0xBB])
);
assert_eq!(
b.generate_key(),
oracle_key(&[0x03, 1, 2, 3, 0x01, 0xCC])
);
assert_eq!(base.generate_key(), oracle_key(&[0x03, 1, 2, 3]));
});
crate::contract_test!(addresses_same_slot_matches_preimage_equality {
let root = Keypath::default();
let a = root.child(&3u32).child(&42u64);
let same = root.child(&3u32).child(&42u64);
let different = root.child(&3u32).child(&43u64);
assert!(a.addresses_same_slot(&same));
assert!(!a.addresses_same_slot(&different));
});
crate::contract_test!(addresses_same_slot_ignores_exclusive_flag {
let base = Keypath::default().child(&7u32);
let mut exclusive = base.clone();
exclusive.set_exclusive();
assert!(exclusive.is_exclusive());
assert!(!base.is_exclusive());
assert!(base.addresses_same_slot(&exclusive));
assert!(exclusive.addresses_same_slot(&base));
});
crate::contract_test!(derivation_is_deterministic {
let k1 = Keypath::default().child(&3u32).child(&42u64);
let k2 = Keypath::default().child(&3u32).child(&42u64);
assert_eq!(k1.generate_key(), k2.generate_key());
});
crate::contract_test!(golden_key_vector {
let kp = Keypath::default().child(&1u32).child(&0u32);
let expected: StorageKey = crate::SHA256Hash::new(hex_literal::hex!(
"f896c3a5f9841b6e1f0a22bd35a6a1bc5efb28aaa23b66301ec8098ce57cf99a"
))
.into();
assert_eq!(kp.generate_key(), expected, "golden key changed");
});
crate::contract_test!(domain_frame_is_distinct_and_length_framed {
let root = Keypath::default();
let a = root.child("Token").child(&0u32);
let b = root.child("Vault").child(&0u32);
assert!(!a.addresses_same_slot(&b));
let dom = root.child("AB");
assert_eq!(dom.generate_key(), oracle_key(&[0x03, 0x02, b'A', b'B']));
let expected: StorageKey = crate::SHA256Hash::new(hex_literal::hex!(
"801d3bfb551c298e3b7a2dfef59a178ce0c8d3f41fec0f3d8c308035c678e516"
))
.into();
assert_eq!(dom.generate_key(), expected, "golden domain key changed");
});
crate::contract_test!(const_u32_frame_matches_runtime_child {
macro_rules! check {
($v:expr) => {{
const FRAMED: FramedComponent = FramedComponent::from_u32($v);
let runtime = Keypath::default().child(&$v);
assert_eq!(
runtime.generate_key(),
oracle_key(FRAMED.as_bytes()),
"const u32 framing drifted from runtime child() for {}",
$v
);
}};
}
check!(0u32);
check!(1u32);
check!(62u32);
check!(63u32);
check!(255u32);
check!(256u32);
check!(65535u32);
check!(65536u32);
check!(u32::MAX);
const F1: FramedComponent = FramedComponent::from_u32(1u32);
assert_eq!(F1.as_bytes(), &[0x01, 0x01]);
});
crate::contract_test!(child_framed_matches_child {
const FRAME: FramedComponent = FramedComponent::from_u32(7u32);
let parent = Keypath::default().child("Domain").child(&3u32);
let via_const = parent.child_framed(&FRAME);
let via_runtime = parent.child(&7u32);
assert!(via_const.addresses_same_slot(&via_runtime));
assert!(!via_const.addresses_same_slot(&parent));
});
crate::contract_test!(child_framed_preserves_exclusive_flag {
const FRAME: FramedComponent = FramedComponent::from_u32(0u32);
let mut parent = Keypath::default().child("Domain");
parent.set_exclusive();
assert!(parent.child_framed(&FRAME).is_exclusive());
});
crate::contract_test!(const_frame_matches_runtime_child {
macro_rules! check {
($domain:literal) => {{
const N: usize = framed_domain_len($domain);
const FRAMED: [u8; N] = frame_domain::<N>($domain);
let runtime = Keypath::default().child($domain);
assert_eq!(
runtime.generate_key(),
oracle_key(&FRAMED),
"const framing drifted from runtime child() for {:?}",
$domain
);
}};
}
check!("A");
check!("AB");
check!("ERC20State");
check!("a_rather_long_storage_domain_name_for_framing");
check!(
"this_domain_is_deliberately_longer_than_sixty_three_bytes_to_exercise_the_two_byte_cxs_length_header"
);
check!("日本語ドメイン");
const N_AB: usize = framed_domain_len("AB");
const AB: [u8; N_AB] = frame_domain::<N_AB>("AB");
assert_eq!(&AB[..], &[0x03, 0x02, b'A', b'B']);
});
}
pub use leaf_key::*;
mod leaf_key {
use super::Keypath;
use crate::types::StorageKey;
use core::ops::Deref;
#[derive(Debug)]
pub struct LeafKey {
key: StorageKey,
exclusive: bool,
}
impl From<Keypath> for LeafKey {
fn from(keypath: Keypath) -> Self {
let exclusive = keypath.is_exclusive();
LeafKey {
key: keypath.into_key(),
exclusive,
}
}
}
impl LeafKey {
pub fn get(&self) -> &StorageKey {
&self.key
}
pub fn is_exclusive(&self) -> bool {
self.exclusive
}
}
impl Deref for LeafKey {
type Target = StorageKey;
fn deref(&self) -> &Self::Target {
&self.key
}
}
}