Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a pseudorandom generator for move tests #7554

Merged
merged 17 commits into from
Jan 24, 2023
Merged
Prev Previous commit
Next Next commit
Misc updates
  • Loading branch information
jonas-lj committed Jan 20, 2023
commit 485ea1aa3d2a39b6447853d6866f555280559770
86 changes: 39 additions & 47 deletions crates/sui-framework/sources/test/random.move
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ module sui::random {
use std::vector;
use sui::bcs;

/// Internally, this pseudorandom generator uses a hash chain over Sha3-256
/// which has an output length of 32 bytes.
// Internally, the pseudorandom generator uses a hash chain over Sha3-256
// which has an output length of 32 bytes.
const DIGEST_LENGTH: u64 = 32;

/// The represents a seeded pseudorandom generator. Note that the generated
Expand All @@ -17,8 +17,9 @@ module sui::random {
state: vector<u8>,
}

/// Update the state of the generator and return DIGEST_LENGTH random bytes.
fun get_next_digest(random: &mut Random): vector<u8> {
/// Update the state of the generator and return a vector holding `DIGEST_LENGTH`
/// random bytes.
fun next_digest(random: &mut Random): vector<u8> {
random.state = hash::sha3_256(random.state);
random.state
}
Expand All @@ -29,26 +30,21 @@ module sui::random {
Random { state }
}

/// Use the given pseudorandom generator to get a vector with l random bytes.
public fun get_next_bytes(random: &mut Random, l: u64): vector<u8> {
assert!(l > 0, 0);

/// Use the given pseudorandom generator to generate a vector with l random bytes.
public fun next_bytes(random: &mut Random, l: u64): vector<u8> {
// We need ceil(l / DIGEST_LENGTH) digests to fill the array
let quotient = l / DIGEST_LENGTH;
let remainder = l - quotient * DIGEST_LENGTH;

let output = vector::empty<u8>();

let i = 0;
let (i, output) = (0, vector::empty<u8>());
while (i < quotient) {
vector::append(&mut output, get_next_digest(random));
vector::append(&mut output, next_digest(random));
i = i + 1;
};

// If quotient is not exact, fill the remaining bytes
if (remainder > 0) {
let i = 0;
let digest = get_next_digest(random);
let (i, digest) = (0, next_digest(random));
while (i < remainder) {
vector::push_back(&mut output, *vector::borrow(&mut digest, i));
i = i + 1;
Expand All @@ -58,30 +54,26 @@ module sui::random {
output
}

/// Use the given pseudorandom generator to get a random `u64` integer.
public fun get_next_u64(random: &mut Random): u64 {
let bytes = get_next_digest(random);
/// Use the given pseudorandom generator to generate a random `u64` integer.
public fun next_u64(random: &mut Random): u64 {
let bytes = next_digest(random);
bcs::peel_u64(&mut bcs::new(bytes))
}

/// Use the given pseudorandom generator to get an `u64` integer in the range [0, ...,
/// 2^bit_length - 1].
fun get_next_u64_with_bit_length(random: &mut Random, bit_length: u8): u64 {
assert!(bit_length > 0 && bit_length < 64, 0);
get_next_u64(random) >> (64 - bit_length)
/// Use the given pseudorandom generator to generate an `u64` integer in the range
/// [0, ..., 2^bit_length - 1].
fun next_u64_with_bit_length(random: &mut Random, bit_length: u8): u64 {
next_u64(random) >> (64 - bit_length)
}

/// Compute the bit length of n.
fun bit_length(n: u64): u8 {

// Use binary search
let mid = 32;
let length = 0;

// Use binary search to find the bit length of n
let (length, mid) = (0, 32);
while (mid > 0) {
let n_mod_mid = n >> mid;
if (n_mod_mid > 0) {
// The bit length of n is strictly larger than mid
// The bit length of n is strictly larger than mid.
length = length + mid;
n = n_mod_mid;
};
Expand All @@ -95,30 +87,30 @@ module sui::random {
length
}

/// Use the given pseudo-random generator to get a random `u64` integer in the range
/// [0, ..., upper_bound - 1].
public fun get_next_u64_in_range(random: &mut Random, upper_bound: u64): u64 {
/// Use the given pseudo-random generator and a non-zero `upper_bound` to generate a
/// random `u64` integer in the range [0, ..., upper_bound - 1].
public fun next_u64_in_range(random: &mut Random, upper_bound: u64): u64 {
assert!(upper_bound > 0, 0);
let bit_length = bit_length(upper_bound);
let candidate = get_next_u64_with_bit_length(random, bit_length);
let candidate = next_u64_with_bit_length(random, bit_length);
while (candidate >= upper_bound) {
candidate = get_next_u64_with_bit_length(random, bit_length);
candidate = next_u64_with_bit_length(random, bit_length);
};
candidate
}

/// Use the given pseudorandom generator to get a random `u8`.
public fun get_next_u8(random: &mut Random): u8 {
*vector::borrow(&get_next_digest(random), 0)
/// Use the given pseudorandom generator to generate a random `u8`.
public fun next_u8(random: &mut Random): u8 {
*vector::borrow(&next_digest(random), 0)
}

/// Use the given pseudorandom generator to get a random `bool`.
public fun get_next_bool(random: &mut Random): bool {
get_next_u8(random) % 2 == 1
/// Use the given pseudorandom generator to generate a random `bool`.
public fun next_bool(random: &mut Random): bool {
next_u8(random) % 2 == 1
}

#[test]
fun test_get_next_bytes() {
fun test_next_bytes() {
let lengths = vector[1, 31, 32, 33, 63, 64, 65];

let i = 0;
Expand All @@ -127,27 +119,27 @@ module sui::random {

// The length should be the requested length
let random1 = new(b"seed");
let bytes = get_next_bytes(&mut random1, length);
let bytes = next_bytes(&mut random1, length);
assert!(vector::length(&bytes) == length, 0);

// Two generators with different seeds should give different outputs
let random1 = new(b"seed 1");
let random2 = new(b"seed 2");
assert!(get_next_bytes(&mut random1, length) !=
get_next_bytes(&mut random2, length), 2);
assert!(next_bytes(&mut random1, length) !=
next_bytes(&mut random2, length), 2);

// Two generators with the same seed should give the same output
let random1 = new(b"seed");
let random2 = new(b"seed");
assert!(get_next_bytes(&mut random1, length) ==
get_next_bytes(&mut random2, length), 3);
assert!(next_bytes(&mut random1, length) ==
next_bytes(&mut random2, length), 3);

i = i + 1;
}
}

#[test]
fun test_get_next_integer_in_range() {
fun test_next_u64_in_range() {
let random = new(b"seed");

let i = 0;
Expand All @@ -157,7 +149,7 @@ module sui::random {
let upper_bound = *vector::borrow(&bounds, i);
let j = 0;
while (j < tests) {
assert!(get_next_u64_in_range(&mut random, upper_bound) < upper_bound, 0);
assert!(next_u64_in_range(&mut random, upper_bound) < upper_bound, 0);
j = j + 1;
};
i = i + 1;
Expand Down