Skip to main content

moonlit_pdk/
random.rs

1//! Randomness, routed through the host so tests can seed it deterministically.
2
3use crate::context::Host;
4
5/// Randomness handle, created via `ctx.random()`.
6pub struct Random<'a> {
7  host: &'a dyn Host,
8}
9
10impl<'a> Random<'a> {
11  #[must_use]
12  pub(crate) fn new(host: &'a dyn Host) -> Self {
13    Self { host }
14  }
15  /// `n` random bytes from the host (at least `n`).
16  #[must_use]
17  pub fn bytes(&self, n: usize) -> Vec<u8> {
18    self.host.random_bytes(n)
19  }
20  /// A random `UUIDv4` string (`8-4-4-4-12` lowercase hex).
21  #[must_use]
22  pub fn uuid(&self) -> String {
23    let mut b = self.host.random_bytes(16);
24    b.resize(16, 0); // defensive: contract is >= 16, but never index OOB
25    b[6] = (b[6] & 0x0f) | 0x40; // version 4
26    b[8] = (b[8] & 0x3f) | 0x80; // variant 1 (RFC 4122)
27    format!(
28      "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
29      b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7], b[8], b[9], b[10], b[11], b[12], b[13], b[14], b[15]
30    )
31  }
32}
33
34#[cfg(test)]
35mod tests {
36  use crate::Context;
37  use crate::testing::MockHost;
38
39  #[test]
40  fn bytes_passthrough_returns_requested_length() {
41    let host = MockHost::new().with_random(&[1, 2, 3]);
42    let ctx = Context::new(&host, "/w".into(), "s".into());
43    assert_eq!(ctx.random().bytes(5), vec![1, 2, 3, 1, 2]); // cycles the seed
44  }
45
46  #[test]
47  fn uuid_is_v4_formatted_and_deterministic_under_mock() {
48    let host = MockHost::new().with_random(&[0xab]); // every byte 0xab
49    let ctx = Context::new(&host, "/w".into(), "s".into());
50    let u = ctx.random().uuid();
51    // 0xab: b[6]=(0x0b|0x40)=0x4b (version 4); b[8]=(0x2b|0x80)=0xab (variant 1)
52    assert_eq!(u, "abababab-abab-4bab-abab-abababababab");
53    assert_eq!(u, ctx.random().uuid(), "deterministic under a fixed mock seed");
54  }
55}