From 4dedd048a71c2d793e28ff84540a79e88727c3cb Mon Sep 17 00:00:00 2001 From: benr-ml Date: Sun, 4 Feb 2024 18:24:55 +0200 Subject: [PATCH 01/17] all changes --- .../dependencies/sui-framework/hmac.md | 35 ++ .../dependencies/sui-framework/hmac.md | 35 ++ .../sui-framework/sources/random.move | 158 ++++- .../sui-framework/tests/random_tests.move | 543 +++++++++++++++++- .../examples/games/sources/raffle.move | 124 ++++ .../examples/games/sources/slot_machine.move | 92 +++ .../examples/games/tests/raffle_tests.move | 103 ++++ .../games/tests/slot_machine_tests.move | 98 ++++ 8 files changed, 1165 insertions(+), 23 deletions(-) create mode 100644 crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md create mode 100644 crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md create mode 100644 sui_programmability/examples/games/sources/raffle.move create mode 100644 sui_programmability/examples/games/sources/slot_machine.move create mode 100644 sui_programmability/examples/games/tests/raffle_tests.move create mode 100644 sui_programmability/examples/games/tests/slot_machine_tests.move diff --git a/crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md b/crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md new file mode 100644 index 0000000000000..d9cefc6191cd4 --- /dev/null +++ b/crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md @@ -0,0 +1,35 @@ + + + +# Module `0x2::hmac` + + + +- [Function `hmac_sha3_256`](#0x2_hmac_hmac_sha3_256) + + +
+ + + + + +## Function `hmac_sha3_256` + + + +
public fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>
+
+ + + +
+Implementation + + +
public native fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>;
+
+ + + +
diff --git a/crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md b/crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md new file mode 100644 index 0000000000000..d9cefc6191cd4 --- /dev/null +++ b/crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md @@ -0,0 +1,35 @@ + + + +# Module `0x2::hmac` + + + +- [Function `hmac_sha3_256`](#0x2_hmac_hmac_sha3_256) + + +
+ + + + + +## Function `hmac_sha3_256` + + + +
public fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>
+
+ + + +
+Implementation + + +
public native fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>;
+
+ + + +
diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index bb5feb8d227b4..e95981611bd3a 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -1,34 +1,37 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -#[allow(unused_use)] -// This module provides functionality for generating and using secure randomness. -// -// Randomness is currently write-only, until user-facing API is implemented. +/// This module provides functionality for generating secure randomness. module sui::random { + use std::bcs; use std::vector; + use sui::address::to_bytes; + use sui::hmac::hmac_sha3_256; use sui::object::{Self, UID}; use sui::transfer; - use sui::tx_context::{Self, TxContext}; + use sui::tx_context::{Self, TxContext, fresh_object_address}; use sui::versioned::{Self, Versioned}; // Sender is not @0x0 the system address. const ENotSystemAddress: u64 = 0; const EWrongInnerVersion: u64 = 1; const EInvalidRandomnessUpdate: u64 = 2; + const EInvalidRange: u64 = 3; const CURRENT_VERSION: u64 = 1; + const RAND_OUTPUT_LEN: u16 = 32; /// Singleton shared object which stores the global randomness state. /// The actual state is stored in a versioned inner field. public struct Random has key { id: UID, + // The inner object must never be accessed outside this module as it could be used for accessing global + // randomness via deserialization of RandomInner. inner: Versioned, } public struct RandomInner has store { version: u64, - epoch: u64, randomness_round: u64, random_bytes: vector, @@ -74,7 +77,6 @@ module sui::random { inner } - #[allow(unused_function)] // TODO: remove annotation after implementing user-facing API fun load_inner( self: &Random, ): &RandomInner { @@ -131,4 +133,146 @@ module sui::random { ) { update_randomness_state(self, new_round, new_bytes, ctx); } + + + /// Unique randomness generator, derived from the global randomness. + struct RandomGenerator has drop { + seed: vector, + counter: u16, + buffer: vector, + } + + /// Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. + public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator { + let inner = load_inner(r); + let seed = hmac_sha3_256(&inner.random_bytes,&to_bytes(fresh_object_address(ctx))); + RandomGenerator { seed, counter: 0, buffer: vector[] } + } + + // Get the next block of random bytes. + fun derive_next_block(g: &mut RandomGenerator): vector { + g.counter = g.counter + 1; + hmac_sha3_256(&g.seed, &bcs::to_bytes(&g.counter)) + } + + // Fill the generator's buffer with 32 random bytes. + fun fill_buffer(g: &mut RandomGenerator) { + let next_block = derive_next_block(g); + vector::append(&mut g.buffer, next_block); + } + + /// Generate n random bytes. + public fun bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector { + let result = vector[]; + // Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer. + let num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN; + while (num_of_blocks > 0) { + vector::append(&mut result, derive_next_block(g)); + num_of_blocks = num_of_blocks - 1; + }; + // Take remaining bytes from the generator's buffer. + if (vector::length(&g.buffer) < ((num_of_bytes as u64) - vector::length(&result))) { + fill_buffer(g); + }; + while (vector::length(&result) < (num_of_bytes as u64)) { + vector::push_back(&mut result, vector::pop_back(&mut g.buffer)); + }; + result + } + + // Helper function that extracts the given number of bytes from the random generator and returns it as u256. + // Assumes that the caller has already checked that num_of_bytes is valid. + fun u256_from_bytes(g: &mut RandomGenerator, num_of_bytes: u8): u256 { + if (vector::length(&g.buffer) < (num_of_bytes as u64)) { + fill_buffer(g); + }; + let result: u256 = 0; + let i = 0; + while (i < num_of_bytes) { + let byte = vector::pop_back(&mut g.buffer); + result = (result << 8) + (byte as u256); + i = i + 1; + }; + result + } + + /// Generate a u256. + public fun generate_u256(g: &mut RandomGenerator): u256 { + u256_from_bytes(g, 32) + } + + /// Generate a u128. + public fun generate_u128(g: &mut RandomGenerator): u128 { + (u256_from_bytes(g, 16) as u128) + } + + /// Generate a u64. + public fun generate_u64(g: &mut RandomGenerator): u64 { + (u256_from_bytes(g, 8) as u64) + } + + /// Generate a u32. + public fun generate_u32(g: &mut RandomGenerator): u32 { + (u256_from_bytes(g, 4) as u32) + } + + /// Generate a u16. + public fun generate_u16(g: &mut RandomGenerator): u16 { + (u256_from_bytes(g, 2) as u16) + } + + /// Generate a u8. + public fun generate_u8(g: &mut RandomGenerator): u8 { + (u256_from_bytes(g, 1) as u8) + } + + // Helper function to generate a random u128 in [min, max] using a random number with num_of_bytes bytes. + // Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias. + fun u128_in_range(g: &mut RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128 { + assert!(min < max, EInvalidRange); + let diff = ((max - min) as u256) + 1; + let rand = u256_from_bytes(g, num_of_bytes); + min + ((rand % diff) as u128) + } + + /// Generate a random u128 in [min, max] (with a bias of 2^{-64}). + public fun generate_u128_in_range(g: &mut RandomGenerator, min: u128, max: u128): u128 { + u128_in_range(g, min, max, 24) + } + + //// Generate a random u64 in [min, max] (with a bias of 2^{-64}). + public fun generate_u64_in_range(g: &mut RandomGenerator, min: u64, max: u64): u64 { + (u128_in_range(g, (min as u128), (max as u128), 16) as u64) + } + + /// Generate a random u32 in [min, max] (with a bias of 2^{-64}). + public fun generate_u32_in_range(g: &mut RandomGenerator, min: u32, max: u32): u32 { + (u128_in_range(g, (min as u128), (max as u128), 12) as u32) + } + + /// Generate a random u16 in [min, max] (with a bias of 2^{-64}). + public fun generate_u16_in_range(g: &mut RandomGenerator, min: u16, max: u16): u16 { + (u128_in_range(g, (min as u128), (max as u128), 10) as u16) + } + + /// Generate a random u8 in [min, max] (with a bias of 2^{-64}). + public fun generate_u8_in_range(g: &mut RandomGenerator, min: u8, max: u8): u8 { + (u128_in_range(g, (min as u128), (max as u128), 9) as u8) + } + + #[test_only] + public fun generator_seed(r: &RandomGenerator): &vector { + &r.seed + } + + #[test_only] + public fun generator_counter(r: &RandomGenerator): u16 { + r.counter + } + + #[test_only] + public fun generator_buffer(r: &RandomGenerator): &vector { + &r.buffer + } + } diff --git a/crates/sui-framework/packages/sui-framework/tests/random_tests.move b/crates/sui-framework/packages/sui-framework/tests/random_tests.move index 62a961d56ad08..b324a483c3264 100644 --- a/crates/sui-framework/packages/sui-framework/tests/random_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/random_tests.move @@ -6,31 +6,542 @@ #[allow(unused_use)] module sui::random_tests { use std::vector; - + use sui::bcs; use sui::test_scenario; use sui::random::{ Self, Random, - update_randomness_state_for_testing, + update_randomness_state_for_testing, new_generator, generator_seed, generator_counter, generator_buffer, bytes, + generate_u256, generate_u128, generate_u64, generate_u32, generate_u16, generate_u8, generate_u128_in_range, + generate_u64_in_range, generate_u32_in_range, generate_u16_in_range, generate_u8_in_range, }; + // TODO: add a test from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf ? + #[test] - fun random_tests_basic() { - let mut scenario_val = test_scenario::begin(@0x0); + fun random_test_basic_flow() { + let scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let mut random_state = test_scenario::take_shared(scenario); + let random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, - vector[0, 1, 2, 3], + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let _o256 = generate_u256(&mut gen); + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + fun test_new_generator() { + let global_random1 = x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F"; + let global_random2 = x"2F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1A"; + + // Create Random + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::end(scenario_val); + + // Set random to global_random1 + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + global_random1, + test_scenario::ctx(scenario) + ); + test_scenario::next_tx(scenario, @0x0); + let gen1= new_generator(&random_state, test_scenario::ctx(scenario)); + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + + // Set random again to global_random1 + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 1, + global_random1, + test_scenario::ctx(scenario) + ); + test_scenario::next_tx(scenario, @0x0); + let gen2= new_generator(&random_state, test_scenario::ctx(scenario)); + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + + // Set random to global_random2 + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 2, + global_random2, + test_scenario::ctx(scenario) + ); + test_scenario::next_tx(scenario, @0x0); + let gen3= new_generator(&random_state, test_scenario::ctx(scenario)); + let gen4= new_generator(&random_state, test_scenario::ctx(scenario)); + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + + assert!(generator_counter(&gen1) == 0, 0); + assert!(*generator_buffer(&gen1) == vector::empty(), 0); + assert!(generator_seed(&gen1) == generator_seed(&gen2), 0); + assert!(generator_seed(&gen1) != generator_seed(&gen3), 0); + assert!(generator_seed(&gen3) != generator_seed(&gen4), 0); + } + + #[test] + fun random_tests_regression() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // Regression (not critical for security, but still an indication that something is wrong). + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let o256 = generate_u256(&mut gen); + assert!(o256 == 85985798878417437391783029796051418802193098452099584085821130568389745847195, 0); + let o128 = generate_u128(&mut gen); + assert!(o128 == 332057125240408555349883177059479920214, 0); + let o64 = generate_u64(&mut gen); + assert!(o64 == 13202990749492462163, 0); + let o32 = generate_u32(&mut gen); + assert!(o32 == 3316307786, 0); + let o16 = generate_u16(&mut gen); + assert!(o16 == 5961, 0); + let o8 = generate_u8(&mut gen); + assert!(o8 == 222, 0); + let output = generate_u128_in_range(&mut gen, 51, 123456789); + assert!(output == 99859235, 0); + let output = generate_u64_in_range(&mut gen, 51, 123456789); + assert!(output == 87557915, 0); + let output = generate_u32_in_range(&mut gen, 51, 123456789); + assert!(output == 57096277, 0); + let output = generate_u16_in_range(&mut gen, 51, 1234); + assert!(output == 349, 0); + let output = generate_u8_in_range(&mut gen, 51, 123); + assert!(output == 60, 0); + let output = bytes(&mut gen, 11); + assert!(output == x"252cfdbb59205fcc509c9e", 0); + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + fun test_bytes() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + + // Check the output size & internal generator state + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output = bytes(&mut gen, 1); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 31, 0); + assert!(vector::length(&output) == 1, 0); + let output = bytes(&mut gen, 2); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 29, 0); + assert!(vector::length(&output) == 2, 0); + let output = bytes(&mut gen, 29); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 0, 0); + assert!(vector::length(&output) == 29, 0); + let output = bytes(&mut gen, 11); + assert!(generator_counter(&gen) == 2, 0); + assert!(vector::length(generator_buffer(&gen)) == 21, 0); + assert!(vector::length(&output) == 11, 0); + let output = bytes(&mut gen, 32 * 2); + assert!(generator_counter(&gen) == 4, 0); + assert!(vector::length(generator_buffer(&gen)) == 21, 0); + assert!(vector::length(&output) == 32 * 2, 0); + let output = bytes(&mut gen, 32 * 5 + 5); + assert!(generator_counter(&gen) == 9, 0); + assert!(vector::length(generator_buffer(&gen)) == 16, 0); + assert!(vector::length(&output) == 32 * 5 + 5, 0); + + // Sanity check that the output is not all zeros. + let output = bytes(&mut gen, 10); + let i = 0; + while (true) { // should break before the overflow + if (*vector::borrow(&output, i) != 0u8) + break; + i = i + 1; + }; + + // Sanity check that 2 different outputs are different. + let output1 = bytes(&mut gen, 10); + let output2 = bytes(&mut gen, 10); + i = 0; + while (true) { // should break before the overflow + if (vector::borrow(&output1, i) != vector::borrow(&output2, i)) + break; + i = i + 1; + }; + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + fun random_tests_uints() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // u256 + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_u256(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 0, 0); + let output2 = generate_u256(&mut gen); + assert!(generator_counter(&gen) == 2, 0); + assert!(vector::length(generator_buffer(&gen)) == 0, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u8(&mut gen); + let _output4 = generate_u256(&mut gen); + assert!(generator_counter(&gen) == 4, 0); + assert!(vector::length(generator_buffer(&gen)) == 31, 0); + // Check that we indeed generate all bytes as random + let i = 0; + while (i < 32) { + let x = generate_u256(&mut gen); + let x_bytes = bcs::to_bytes(&x); + if (*vector::borrow(&x_bytes, i) != 0u8) + i = i + 1; + }; + + // u128 + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_u128(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 16, 0); + let output2 = generate_u128(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 0, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u8(&mut gen); + let _output4 = generate_u128(&mut gen); + assert!(generator_counter(&gen) == 2, 0); + assert!(vector::length(generator_buffer(&gen)) == 15, 0); + let i = 0; + while (i < 16) { + let x = generate_u128(&mut gen); + let x_bytes = bcs::to_bytes(&x); + if (*vector::borrow(&x_bytes, i) != 0u8) + i = i + 1; + }; + + // u64 + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_u64(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 24, 0); + let output2 = generate_u64(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 16, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u8(&mut gen); + let _output4 = generate_u64(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 7, 0); + let i = 0; + while (i < 8) { + let x = generate_u64(&mut gen); + let x_bytes = bcs::to_bytes(&x); + if (*vector::borrow(&x_bytes, i) != 0u8) + i = i + 1; + }; + + // u32 + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_u32(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 28, 0); + let output2 = generate_u32(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 24, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u8(&mut gen); + let _output4 = generate_u32(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 19, 0); + let i = 0; + while (i < 4) { + let x = generate_u32(&mut gen); + let x_bytes = bcs::to_bytes(&x); + if (*vector::borrow(&x_bytes, i) != 0u8) + i = i + 1; + }; + + // u16 + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_u16(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 30, 0); + let output2 = generate_u16(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 28, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u8(&mut gen); + let _output4 = generate_u16(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 25, 0); + let i = 0; + while (i < 2) { + let x = generate_u16(&mut gen); + let x_bytes = bcs::to_bytes(&x); + if (*vector::borrow(&x_bytes, i) != 0u8) + i = i + 1; + }; + + // u8 + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_u8(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 31, 0); + let output2 = generate_u8(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 30, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u128(&mut gen); + let _output4 = generate_u8(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 13, 0); + while (true) { + let x = generate_u8(&mut gen); + if (x != 0u8) + break + }; + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + fun random_tests_in_range() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // generate_u128_in_range + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let output1 = generate_u128_in_range(&mut gen, 11, 123454321); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 8, 0); + let output2 = generate_u128_in_range(&mut gen, 11, 123454321); + assert!(generator_counter(&gen) == 2, 0); + assert!(vector::length(generator_buffer(&gen)) == 16, 0); + assert!(output1 != output2, 0); + let output = generate_u128_in_range(&mut gen, 123454321, 123454321 + 1); + assert!((output == 123454321) || (output == 123454321 + 1), 0); + // test the edge case of u128_in_range (covers also the other in_range functions) + let _output = generate_u128_in_range(&mut gen, 0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); + let i = 0; + while (i < 50) { + let min = generate_u128(&mut gen); + let max = min + (generate_u64(&mut gen) as u128); + let output = generate_u128_in_range(&mut gen, min, max); + assert!(output >= min, 0); + assert!(output <= max, 0); + i = i + 1; + }; + + // generate_u64_in_range + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let output1 = generate_u64_in_range(&mut gen, 11, 123454321); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 16, 0); + let output2 = generate_u64_in_range(&mut gen, 11, 123454321); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 0, 0); + assert!(output1 != output2, 0); + let output = generate_u64_in_range(&mut gen, 123454321, 123454321 + 1); + assert!((output == 123454321) || (output == 123454321 + 1), 0); + let i = 0; + while (i < 50) { + let min = generate_u64(&mut gen); + let max = min + (generate_u32(&mut gen) as u64); + let output = generate_u64_in_range(&mut gen, min, max); + assert!(output >= min, 0); + assert!(output <= max, 0); + i = i + 1; + }; + + // generate_u32_in_range + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let output1 = generate_u32_in_range(&mut gen, 11, 123454321); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 20, 0); + let output2 = generate_u32_in_range(&mut gen, 11, 123454321); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 8, 0); + assert!(output1 != output2, 0); + let output = generate_u32_in_range(&mut gen, 123454321, 123454321 + 1); + assert!((output == 123454321) || (output == 123454321 + 1), 0); + let i = 0; + while (i < 50) { + let min = generate_u32(&mut gen); + let max = min + (generate_u16(&mut gen) as u32); + let output = generate_u32_in_range(&mut gen, min, max); + assert!(output >= min, 0); + assert!(output <= max, 0); + i = i + 1; + }; + + // generate_u16_in_range + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let output1 = generate_u16_in_range(&mut gen, 11, 12345); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 22, 0); + let output2 = generate_u16_in_range(&mut gen, 11, 12345); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 12, 0); + assert!(output1 != output2, 0); + let output = generate_u16_in_range(&mut gen, 12345, 12345 + 1); + assert!((output == 12345) || (output == 12345 + 1), 0); + let i = 0; + while (i < 50) { + let min = generate_u16(&mut gen); + let max = min + (generate_u8(&mut gen) as u16); + let output = generate_u16_in_range(&mut gen, min, max); + assert!(output >= min, 0); + assert!(output <= max, 0); + i = i + 1; + }; + + // generate_u8_in_range + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let output1 = generate_u8_in_range(&mut gen, 11, 123); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 23, 0); + let output2 = generate_u8_in_range(&mut gen, 11, 123); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 14, 0); + assert!(output1 != output2, 0); + let output = generate_u8_in_range(&mut gen, 123, 123 + 1); + assert!((output == 123) || (output == 123 + 1), 0); + let i = 0; + while (i < 50) { + let (min, max) = (generate_u8(&mut gen), generate_u8(&mut gen)); + let (min, max) = if (min < max) { (min, max) } else { (max, min) }; + let (min, max) = if (min == max) { (min, max + 1) } else { (min, max) }; + let output = generate_u8_in_range(&mut gen, min, max); + assert!(output >= min, 0); + assert!(output <= max, 0); + i = i + 1; + }; + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + #[expected_failure(abort_code = random::EInvalidRange)] + fun random_tests_invalid_range() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let _output = generate_u128_in_range(&mut gen, 511, 500); + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + #[expected_failure(abort_code = random::EInvalidRange)] + fun random_tests_invalid_range_same() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", test_scenario::ctx(scenario) ); - // TODO: Add more once user-facing API is implemented. + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let _output = generate_u32_in_range(&mut gen, 123, 123); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); @@ -38,13 +549,13 @@ module sui::random_tests { #[test] fun random_tests_update_after_epoch_change() { - let mut scenario_val = test_scenario::begin(@0x0); + let scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let mut random_state = test_scenario::take_shared(scenario); + let random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -74,22 +585,22 @@ module sui::random_tests { #[test] #[expected_failure(abort_code = random::EInvalidRandomnessUpdate)] fun random_tests_duplicate() { - let mut scenario_val = test_scenario::begin(@0x0); + let scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let mut random_state = test_scenario::take_shared(scenario); + let random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, - 0, + 1, vector[0, 1, 2, 3], test_scenario::ctx(scenario) ); update_randomness_state_for_testing( &mut random_state, - 0, + 1, vector[0, 1, 2, 3], test_scenario::ctx(scenario) ); @@ -101,16 +612,16 @@ module sui::random_tests { #[test] #[expected_failure(abort_code = random::EInvalidRandomnessUpdate)] fun random_tests_out_of_order() { - let mut scenario_val = test_scenario::begin(@0x0); + let scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let mut random_state = test_scenario::take_shared(scenario); + let random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, - 0, + 1, vector[0, 1, 2, 3], test_scenario::ctx(scenario) ); diff --git a/sui_programmability/examples/games/sources/raffle.move b/sui_programmability/examples/games/sources/raffle.move new file mode 100644 index 0000000000000..e0e4ce6321782 --- /dev/null +++ b/sui_programmability/examples/games/sources/raffle.move @@ -0,0 +1,124 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A basic raffle game that depends on Sui randomness. +/// +/// Anyone can create a new lottery game with an end time and a cost per ticket. After the end time, anyone can trigger +/// a function to determine the winner, and the owner of the winning ticket can redeem the entire balance of the game. +/// +module games::raffle { + use std::option::{Self, Option}; + use sui::balance; + use sui::balance::Balance; + use sui::clock; + use sui::clock::Clock; + use sui::coin; + use sui::coin::Coin; + use sui::object::{Self, ID, UID}; + use sui::random; + use sui::random::{Random, new_generator}; + use sui::sui::SUI; + use sui::transfer; + use sui::tx_context::TxContext; + + /// Error codes + const EGameInProgress: u64 = 0; + const EGameAlreadyCompleted: u64 = 1; + const EInvalidAmount: u64 = 2; + const EGameMistmatch: u64 = 3; + const EWrongGameWinner: u64 = 4; + + /// Game represents a set of parameters of a single game. + struct Game has key { + id: UID, + cost_in_sui: u64, + participants: u32, + end_time: u64, + winner: Option, + balance: Balance, + } + + /// Ticket represents a participant in a single game. + struct Ticket has key { + id: UID, + game_id: ID, + participant_index: u32, + } + + /// Create a shared-object Game. + public fun create(end_time: u64, cost_in_sui: u64, ctx: &mut TxContext) { + let game = Game { + id: object::new(ctx), + cost_in_sui, + participants: 0, + end_time, + winner: option::none(), + balance: balance::zero(), + }; + transfer::share_object(game); + } + + /// Anyone can determine a winner. + entry fun determine_winner(game: &mut Game, r: &Random, clock: &Clock, ctx: &mut TxContext) { + assert!(game.end_time <= clock::timestamp_ms(clock), EGameInProgress); + assert!(option::is_none(&game.winner), EGameAlreadyCompleted); + + let generator = new_generator(r, ctx); + let winner = random::generate_u32_in_range(&mut generator, 1, game.participants); + game.winner = option::some(winner); + } + + /// Anyone can play and receive a ticket. + public fun play(game: &mut Game, coin: Coin, clock: &Clock, ctx: &mut TxContext): Ticket { + assert!(game.end_time > clock::timestamp_ms(clock), EGameAlreadyCompleted); + assert!(coin::value(&coin) == game.cost_in_sui, EInvalidAmount); + + game.participants = game.participants + 1; + coin::put(&mut game.balance, coin); + + Ticket { + id: object::new(ctx), + game_id: object::id(game), + participant_index: game.participants, + } + } + + /// The winner can take the prize. + public fun redeem(ticket: Ticket, game: &mut Game, ctx: &mut TxContext): Coin { + assert!(object::id(game) == ticket.game_id, EGameMistmatch); + assert!(option::contains(&game.winner, &ticket.participant_index), EWrongGameWinner); + destroy_ticket(ticket); + let full_balance = balance::value(&game.balance); + coin::take(&mut game.balance, full_balance, ctx) + } + + public fun destroy_ticket(ticket: Ticket) { + let Ticket { id, game_id: _, participant_index: _} = ticket; + object::delete(id); + } + + #[test_only] + public fun get_cost_in_sui(game: &Game): u64 { + game.cost_in_sui + } + + #[test_only] + public fun get_end_time(game: &Game): u64 { + game.end_time + } + + #[test_only] + public fun get_participants(game: &Game): u32 { + game.participants + } + + #[test_only] + public fun get_winner(game: &Game): Option { + game.winner + } + + #[test_only] + public fun get_balance(game: &Game): u64 { + balance::value(&game.balance) + } +} diff --git a/sui_programmability/examples/games/sources/slot_machine.move b/sui_programmability/examples/games/sources/slot_machine.move new file mode 100644 index 0000000000000..70b63275866e3 --- /dev/null +++ b/sui_programmability/examples/games/sources/slot_machine.move @@ -0,0 +1,92 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A betting game that depends on Sui randomness. +/// +/// Anyone can create a new game for the current epoch by depositing SUIs as the initial balance. The creator can +/// withdraw the remaining balance after the epoch is over. +/// +/// Anyone can play the game by betting on X SUIs. They win X with probability 49% and loss the X SUIs otherwise. +/// +module games::slot_machine { + use sui::balance::{Self, Balance}; + use sui::coin::{Self, Coin}; + use sui::math; + use sui::object::{Self, UID}; + use sui::random::{Self, Random, new_generator}; + use sui::sui::SUI; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + /// Error codes + const EInvalidAmount: u64 = 0; + const EInvalidSender: u64 = 1; + const EInvalidEpoch: u64 = 2; + + /// Game for a specific epoch. + struct Game has key, store { + id: UID, + creator: address, + epoch: u64, + balance: Balance, + } + + /// Create a new game with a given initial reward for the current epoch. + public fun create( + reward: Coin, + ctx: &mut TxContext + ) { + let amount = coin::value(&reward); + assert!(amount > 0, EInvalidAmount); + transfer::public_share_object(Game { + id: object::new(ctx), + creator: tx_context::sender(ctx), + epoch: tx_context::epoch(ctx), + balance: coin::into_balance(reward), + }); + } + + /// Creator can withdraw remaining balance if the game is over. + public fun close(game: &mut Game, ctx: &mut TxContext): Coin { + assert!(tx_context::epoch(ctx) > game.epoch, EInvalidEpoch); + assert!(tx_context::sender(ctx) == game.creator, EInvalidSender); + let full_balance = balance::value(&game.balance); + coin::take(&mut game.balance, full_balance, ctx) + } + + /// Play one turn of the game. + /// + /// The function follows the same steps whether the user won or lost to make sure the gas consumption + /// is the same. + // TODO: confirm the above statement. + entry fun play(game: &mut Game, r: &Random, coin: Coin, ctx: &mut TxContext) { + assert!(tx_context::epoch(ctx) == game.epoch, EInvalidEpoch); + assert!(coin::value(&coin) > 0, EInvalidAmount); + + let bet = math::min(coin::value(&coin), balance::value(&game.balance)); + // If lost, return the rest to the user. + let amount_lost = coin::value(&coin) - bet; + // If won, return entire input balance and the reward to the user. + let amount_won = coin::value(&coin) + bet; + coin::put(&mut game.balance, coin); + + let generator = new_generator(r, ctx); + let bet = random::generate_u8_in_range(&mut generator, 1, 100); + let won = bet <= 49; + + let amount = if (won) { amount_won } else { amount_lost }; + let to_user_coin = coin::take(&mut game.balance, amount, ctx); + transfer::public_transfer(to_user_coin, tx_context::sender(ctx)); + } + + #[test_only] + public fun get_balance(game: &Game): u64 { + balance::value(&game.balance) + } + + #[test_only] + public fun get_epoch(game: &Game): u64 { + game.epoch + } + +} diff --git a/sui_programmability/examples/games/tests/raffle_tests.move b/sui_programmability/examples/games/tests/raffle_tests.move new file mode 100644 index 0000000000000..d2b2ffe18ed9e --- /dev/null +++ b/sui_programmability/examples/games/tests/raffle_tests.move @@ -0,0 +1,103 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module games::raffle_tests { + + use std::option; + use sui::clock; + use sui::coin::{Self, Coin}; + use sui::random::{Self, update_randomness_state_for_testing, Random}; + use sui::sui::SUI; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer; + + use games::raffle; + + fun mint(addr: address, amount: u64, scenario: &mut Scenario) { + transfer::public_transfer(coin::mint_for_testing(amount, test_scenario::ctx(scenario)), addr); + test_scenario::next_tx(scenario, addr); + } + + #[test] + fun test_game() { + let user1 = @0x0; + let user2 = @0x1; + let user3 = @0x2; + let user4 = @0x3; + + let scenario_val = test_scenario::begin(user1); + let scenario = &mut scenario_val; + + // Setup randomness + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // Create the game and get back the output objects. + mint(user1, 1000, scenario); + let end_time = 100; + raffle::create(end_time, 10, test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let game = test_scenario::take_shared(scenario); + assert!(raffle::get_cost_in_sui(&game) == 10, 1); + assert!(raffle::get_participants(&game) == 0, 1); + assert!(raffle::get_end_time(&game) == end_time, 1); + assert!(raffle::get_winner(&game) == option::none(), 1); + assert!(raffle::get_balance(&game) == 0, 1); + + let clock = clock::create_for_testing(test_scenario::ctx(scenario)); + clock::set_for_testing(&mut clock, 10); + + // Play with 4 users (everything here is deterministic) + test_scenario::next_tx(scenario, user1); + mint(user1, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t1 = raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle::get_participants(&game) == 1, 1); + raffle::destroy_ticket(t1); // loser + + test_scenario::next_tx(scenario, user2); + mint(user2, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t2 = raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle::get_participants(&game) == 2, 1); + raffle::destroy_ticket(t2); // loser + + test_scenario::next_tx(scenario, user3); + mint(user3, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t3 = raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle::get_participants(&game) == 3, 1); + raffle::destroy_ticket(t3); // loser + + test_scenario::next_tx(scenario, user4); + mint(user4, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t4 = raffle::play( &mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle::get_participants(&game) == 4, 1); + // this is the winner + + // Determine the winner (-> user3) + clock::set_for_testing(&mut clock, 101); + raffle::determine_winner(&mut game, &random_state, &clock, test_scenario::ctx(scenario)); + assert!(raffle::get_winner(&game) == option::some(4), 1); + assert!(raffle::get_balance(&game) == 40, 1); + + // Take the reward + let coin = raffle::redeem(t4, &mut game, test_scenario::ctx(scenario)); + assert!(coin::value(&coin) == 40, 1); + coin::burn_for_testing(coin); + + clock::destroy_for_testing(clock); + test_scenario::return_shared(game); + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } +} diff --git a/sui_programmability/examples/games/tests/slot_machine_tests.move b/sui_programmability/examples/games/tests/slot_machine_tests.move new file mode 100644 index 0000000000000..1f26ea87a7052 --- /dev/null +++ b/sui_programmability/examples/games/tests/slot_machine_tests.move @@ -0,0 +1,98 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module games::slot_machine_tests { + use sui::coin::{Self, Coin}; + use sui::random::{Self, update_randomness_state_for_testing, Random}; + use sui::sui::SUI; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer; + + use games::slot_machine; + + fun mint(addr: address, amount: u64, scenario: &mut Scenario) { + transfer::public_transfer(coin::mint_for_testing(amount, test_scenario::ctx(scenario)), addr); + test_scenario::next_tx(scenario, addr); + } + + #[test] + fun test_game() { + let user1 = @0x0; + let user2 = @0x1; + let scenario_val = test_scenario::begin(user1); + let scenario = &mut scenario_val; + + // Setup randomness + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // Create the game and get back the output objects. + mint(user1, 1000, scenario); + let coin = test_scenario::take_from_sender>(scenario); + slot_machine::create(coin, test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let game = test_scenario::take_shared(scenario); + assert!(slot_machine::get_balance(&game) == 1000, 1); + assert!(slot_machine::get_epoch(&game) == 0, 1); + + // Play 4 turns (everything here is deterministic) + test_scenario::next_tx(scenario, user2); + mint(user2, 100, scenario); + let coin = test_scenario::take_from_sender>(scenario); + slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); + assert!(slot_machine::get_balance(&game) == 1100, 1); // lost 100 + + test_scenario::next_tx(scenario, user2); + mint(user2, 200, scenario); + let coin = test_scenario::take_from_sender>(scenario); + slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); + assert!(slot_machine::get_balance(&game) == 900, 1); // won 200 + // check that received the right amount + test_scenario::next_tx(scenario, user2); + let coin = test_scenario::take_from_sender>(scenario); + assert!(coin::value(&coin) == 400, 1); + test_scenario::return_to_sender(scenario, coin); + + test_scenario::next_tx(scenario, user2); + mint(user2, 2000, scenario); + let coin = test_scenario::take_from_sender>(scenario); + slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); + assert!(slot_machine::get_balance(&game) == 1800, 1); // lost 900 + // check that received the remaining amount + test_scenario::next_tx(scenario, user2); + let coin = test_scenario::take_from_sender>(scenario); + assert!(coin::value(&coin) == 1100, 1); + test_scenario::return_to_sender(scenario, coin); + + test_scenario::next_tx(scenario, user2); + mint(user2, 200, scenario); + let coin = test_scenario::take_from_sender>(scenario); + slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); + assert!(slot_machine::get_balance(&game) == 1600, 1); // won 200 + // check that received the right amount + test_scenario::next_tx(scenario, user2); + let coin = test_scenario::take_from_sender>(scenario); + assert!(coin::value(&coin) == 400, 1); + test_scenario::return_to_sender(scenario, coin); + + // TODO: test also that the last coin is taken + + // Take remaining balance + test_scenario::next_epoch(scenario, user1); + let coin = slot_machine::close(&mut game, test_scenario::ctx(scenario)); + assert!(coin::value(&coin) == 1600, 1); + coin::burn_for_testing(coin); + + test_scenario::return_shared(game); + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } +} From 412b3f92ca5dcee2f7abf8e77cd96712c7067b41 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Sun, 4 Feb 2024 21:51:55 +0200 Subject: [PATCH 02/17] typo --- crates/sui-framework/packages/sui-framework/sources/random.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index e95981611bd3a..c10dc2c235818 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -145,7 +145,7 @@ module sui::random { /// Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator { let inner = load_inner(r); - let seed = hmac_sha3_256(&inner.random_bytes,&to_bytes(fresh_object_address(ctx))); + let seed = hmac_sha3_256(&inner.random_bytes, &to_bytes(fresh_object_address(ctx))); RandomGenerator { seed, counter: 0, buffer: vector[] } } From 689f6e27c00c9afabbc6581d78c5e3de1901311d Mon Sep 17 00:00:00 2001 From: benr-ml Date: Sun, 4 Feb 2024 21:52:05 +0200 Subject: [PATCH 03/17] update play --- .../examples/games/sources/slot_machine.move | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/sui_programmability/examples/games/sources/slot_machine.move b/sui_programmability/examples/games/sources/slot_machine.move index 70b63275866e3..7c4ed8b35ae03 100644 --- a/sui_programmability/examples/games/sources/slot_machine.move +++ b/sui_programmability/examples/games/sources/slot_machine.move @@ -56,25 +56,22 @@ module games::slot_machine { /// Play one turn of the game. /// - /// The function follows the same steps whether the user won or lost to make sure the gas consumption - /// is the same. - // TODO: confirm the above statement. + /// The function consumes more gas in the "winning" case than the "losing" case, thus gas consumption attacks are + /// not possible. entry fun play(game: &mut Game, r: &Random, coin: Coin, ctx: &mut TxContext) { assert!(tx_context::epoch(ctx) == game.epoch, EInvalidEpoch); assert!(coin::value(&coin) > 0, EInvalidAmount); - let bet = math::min(coin::value(&coin), balance::value(&game.balance)); - // If lost, return the rest to the user. - let amount_lost = coin::value(&coin) - bet; - // If won, return entire input balance and the reward to the user. - let amount_won = coin::value(&coin) + bet; + let coin_value = coin::value(&coin); + let bet_amount = math::min(coin_value, balance::value(&game.balance)); coin::put(&mut game.balance, coin); let generator = new_generator(r, ctx); let bet = random::generate_u8_in_range(&mut generator, 1, 100); let won = bet <= 49; - let amount = if (won) { amount_won } else { amount_lost }; + let amount = coin_value - bet_amount; + if (won) { amount = amount + 2 * bet_amount; }; let to_user_coin = coin::take(&mut game.balance, amount, ctx); transfer::public_transfer(to_user_coin, tx_context::sender(ctx)); } From 120be910cc30f6153922313a2d363aa6c2f44d94 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Tue, 6 Feb 2024 20:50:33 +0200 Subject: [PATCH 04/17] update play to use &mut coin --- .../examples/games/sources/slot_machine.move | 28 ++++++++++--------- .../games/tests/slot_machine_tests.move | 28 ++++++++----------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/sui_programmability/examples/games/sources/slot_machine.move b/sui_programmability/examples/games/sources/slot_machine.move index 7c4ed8b35ae03..8724d677966bf 100644 --- a/sui_programmability/examples/games/sources/slot_machine.move +++ b/sui_programmability/examples/games/sources/slot_machine.move @@ -56,24 +56,26 @@ module games::slot_machine { /// Play one turn of the game. /// - /// The function consumes more gas in the "winning" case than the "losing" case, thus gas consumption attacks are - /// not possible. - entry fun play(game: &mut Game, r: &Random, coin: Coin, ctx: &mut TxContext) { + /// The function consumes the same amount of gas independently of the random outcome. + entry fun play(game: &mut Game, r: &Random, coin: &mut Coin, ctx: &mut TxContext) { assert!(tx_context::epoch(ctx) == game.epoch, EInvalidEpoch); - assert!(coin::value(&coin) > 0, EInvalidAmount); - - let coin_value = coin::value(&coin); - let bet_amount = math::min(coin_value, balance::value(&game.balance)); - coin::put(&mut game.balance, coin); + assert!(coin::value(coin) > 0, EInvalidAmount); + // play the game let generator = new_generator(r, ctx); let bet = random::generate_u8_in_range(&mut generator, 1, 100); - let won = bet <= 49; + let won = 1 - bet / 50; // equals 1 with probability 49% and 0 otherwise + + // move the bet amount from the user's coin to the game's balance + let coin_value = coin::value(coin); + let bet_amount = math::min(coin_value, balance::value(&game.balance)); + coin::put(&mut game.balance, coin::split(coin, bet_amount, ctx)); - let amount = coin_value - bet_amount; - if (won) { amount = amount + 2 * bet_amount; }; - let to_user_coin = coin::take(&mut game.balance, amount, ctx); - transfer::public_transfer(to_user_coin, tx_context::sender(ctx)); + // move the reward to the user's coin + let reward = 2 * (won as u64) * bet_amount; + // the assumption here is that the next line does not consumes more gas when called with zero reward than with + // non-zero reward + coin::join(coin, coin::take(&mut game.balance, reward, ctx)); } #[test_only] diff --git a/sui_programmability/examples/games/tests/slot_machine_tests.move b/sui_programmability/examples/games/tests/slot_machine_tests.move index 1f26ea87a7052..ee23769a21723 100644 --- a/sui_programmability/examples/games/tests/slot_machine_tests.move +++ b/sui_programmability/examples/games/tests/slot_machine_tests.move @@ -47,40 +47,36 @@ module games::slot_machine_tests { test_scenario::next_tx(scenario, user2); mint(user2, 100, scenario); let coin = test_scenario::take_from_sender>(scenario); - slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); + slot_machine::play(&mut game, &random_state, &mut coin, test_scenario::ctx(scenario)); assert!(slot_machine::get_balance(&game) == 1100, 1); // lost 100 + assert!(coin::value(&coin) == 0, 1); + test_scenario::return_to_sender(scenario, coin); test_scenario::next_tx(scenario, user2); mint(user2, 200, scenario); let coin = test_scenario::take_from_sender>(scenario); - slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); + slot_machine::play(&mut game, &random_state, &mut coin, test_scenario::ctx(scenario)); assert!(slot_machine::get_balance(&game) == 900, 1); // won 200 // check that received the right amount - test_scenario::next_tx(scenario, user2); - let coin = test_scenario::take_from_sender>(scenario); assert!(coin::value(&coin) == 400, 1); test_scenario::return_to_sender(scenario, coin); test_scenario::next_tx(scenario, user2); - mint(user2, 2000, scenario); + mint(user2, 300, scenario); let coin = test_scenario::take_from_sender>(scenario); - slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); - assert!(slot_machine::get_balance(&game) == 1800, 1); // lost 900 + slot_machine::play(&mut game, &random_state, &mut coin, test_scenario::ctx(scenario)); + assert!(slot_machine::get_balance(&game) == 600, 1); // won 300 // check that received the remaining amount - test_scenario::next_tx(scenario, user2); - let coin = test_scenario::take_from_sender>(scenario); - assert!(coin::value(&coin) == 1100, 1); + assert!(coin::value(&coin) == 600, 1); test_scenario::return_to_sender(scenario, coin); test_scenario::next_tx(scenario, user2); mint(user2, 200, scenario); let coin = test_scenario::take_from_sender>(scenario); - slot_machine::play(&mut game, &random_state, coin, test_scenario::ctx(scenario)); - assert!(slot_machine::get_balance(&game) == 1600, 1); // won 200 + slot_machine::play(&mut game, &random_state, &mut coin, test_scenario::ctx(scenario)); + assert!(slot_machine::get_balance(&game) == 800, 1); // lost 200 // check that received the right amount - test_scenario::next_tx(scenario, user2); - let coin = test_scenario::take_from_sender>(scenario); - assert!(coin::value(&coin) == 400, 1); + assert!(coin::value(&coin) == 0, 1); test_scenario::return_to_sender(scenario, coin); // TODO: test also that the last coin is taken @@ -88,7 +84,7 @@ module games::slot_machine_tests { // Take remaining balance test_scenario::next_epoch(scenario, user1); let coin = slot_machine::close(&mut game, test_scenario::ctx(scenario)); - assert!(coin::value(&coin) == 1600, 1); + assert!(coin::value(&coin) == 800, 1); coin::burn_for_testing(coin); test_scenario::return_shared(game); From 8343f4d52ea25fb84c07be06f7ce335bd9974afd Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 7 Feb 2024 16:00:21 +0200 Subject: [PATCH 05/17] address PR comments --- .../sui-framework/sources/random.move | 36 ++++- .../sui-framework/tests/random_tests.move | 128 +++++++++++++----- .../examples/games/sources/raffle.move | 37 +++-- .../examples/games/sources/slot_machine.move | 17 +-- .../examples/games/tests/raffle_tests.move | 13 +- .../games/tests/slot_machine_tests.move | 3 +- 6 files changed, 164 insertions(+), 70 deletions(-) diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index c10dc2c235818..95b15e0b69bf7 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -145,7 +145,10 @@ module sui::random { /// Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator { let inner = load_inner(r); - let seed = hmac_sha3_256(&inner.random_bytes, &to_bytes(fresh_object_address(ctx))); + let seed = hmac_sha3_256( + &inner.random_bytes, + &to_bytes(fresh_object_address(ctx)) + ); RandomGenerator { seed, counter: 0, buffer: vector[] } } @@ -162,7 +165,7 @@ module sui::random { } /// Generate n random bytes. - public fun bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector { + public fun generate_bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector { let result = vector[]; // Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer. let num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN; @@ -171,10 +174,11 @@ module sui::random { num_of_blocks = num_of_blocks - 1; }; // Take remaining bytes from the generator's buffer. - if (vector::length(&g.buffer) < ((num_of_bytes as u64) - vector::length(&result))) { + let num_of_bytes = (num_of_bytes as u64); + if (vector::length(&g.buffer) < (num_of_bytes - vector::length(&result))) { fill_buffer(g); }; - while (vector::length(&result) < (num_of_bytes as u64)) { + while (vector::length(&result) < num_of_bytes) { vector::push_back(&mut result, vector::pop_back(&mut g.buffer)); }; result @@ -226,10 +230,18 @@ module sui::random { (u256_from_bytes(g, 1) as u8) } + /// Generate a boolean. + public fun generate_bool(g: &mut RandomGenerator): bool { + (u256_from_bytes(g, 1) & 1) == 1 + } + // Helper function to generate a random u128 in [min, max] using a random number with num_of_bytes bytes. // Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias. fun u128_in_range(g: &mut RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128 { - assert!(min < max, EInvalidRange); + assert!(min <= max, EInvalidRange); + if (min == max) { + return min + }; let diff = ((max - min) as u256) + 1; let rand = u256_from_bytes(g, num_of_bytes); min + ((rand % diff) as u128) @@ -260,6 +272,20 @@ module sui::random { (u128_in_range(g, (min as u128), (max as u128), 9) as u8) } + /// Shuffle a vector using the random generator. + public fun shuffle(g: &mut RandomGenerator, v: &mut vector) { + let n = (vector::length(v) as u32); + if (n == 0) { + return + }; + let i: u32 = 0; + while (i < (n - 1)) { + let j = generate_u32_in_range(g, i, n-1); + vector::swap(v, (i as u64), (j as u64)); + i = i + 1; + }; + } + #[test_only] public fun generator_seed(r: &RandomGenerator): &vector { &r.seed diff --git a/crates/sui-framework/packages/sui-framework/tests/random_tests.move b/crates/sui-framework/packages/sui-framework/tests/random_tests.move index b324a483c3264..21177a02ce74d 100644 --- a/crates/sui-framework/packages/sui-framework/tests/random_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/random_tests.move @@ -6,14 +6,17 @@ #[allow(unused_use)] module sui::random_tests { use std::vector; + use sui::test_utils::assert_eq; use sui::bcs; use sui::test_scenario; use sui::random::{ Self, Random, - update_randomness_state_for_testing, new_generator, generator_seed, generator_counter, generator_buffer, bytes, + update_randomness_state_for_testing, new_generator, generator_seed, generator_counter, generator_buffer, + generate_bytes, generate_u256, generate_u128, generate_u64, generate_u32, generate_u16, generate_u8, generate_u128_in_range, - generate_u64_in_range, generate_u32_in_range, generate_u16_in_range, generate_u8_in_range, + generate_u64_in_range, generate_u32_in_range, generate_u16_in_range, generate_u8_in_range, generate_bool, + shuffle, }; // TODO: add a test from https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-22r1a.pdf ? @@ -145,8 +148,10 @@ module sui::random_tests { assert!(output == 349, 0); let output = generate_u8_in_range(&mut gen, 51, 123); assert!(output == 60, 0); - let output = bytes(&mut gen, 11); + let output = generate_bytes(&mut gen, 11); assert!(output == x"252cfdbb59205fcc509c9e", 0); + let output = generate_bool(&mut gen); + assert!(output == true, 0); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); @@ -172,33 +177,33 @@ module sui::random_tests { // Check the output size & internal generator state assert!(*generator_buffer(&gen) == vector::empty(), 0); - let output = bytes(&mut gen, 1); + let output = generate_bytes(&mut gen, 1); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 31, 0); assert!(vector::length(&output) == 1, 0); - let output = bytes(&mut gen, 2); + let output = generate_bytes(&mut gen, 2); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 29, 0); assert!(vector::length(&output) == 2, 0); - let output = bytes(&mut gen, 29); + let output = generate_bytes(&mut gen, 29); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 0, 0); assert!(vector::length(&output) == 29, 0); - let output = bytes(&mut gen, 11); + let output = generate_bytes(&mut gen, 11); assert!(generator_counter(&gen) == 2, 0); assert!(vector::length(generator_buffer(&gen)) == 21, 0); assert!(vector::length(&output) == 11, 0); - let output = bytes(&mut gen, 32 * 2); + let output = generate_bytes(&mut gen, 32 * 2); assert!(generator_counter(&gen) == 4, 0); assert!(vector::length(generator_buffer(&gen)) == 21, 0); assert!(vector::length(&output) == 32 * 2, 0); - let output = bytes(&mut gen, 32 * 5 + 5); + let output = generate_bytes(&mut gen, 32 * 5 + 5); assert!(generator_counter(&gen) == 9, 0); assert!(vector::length(generator_buffer(&gen)) == 16, 0); assert!(vector::length(&output) == 32 * 5 + 5, 0); // Sanity check that the output is not all zeros. - let output = bytes(&mut gen, 10); + let output = generate_bytes(&mut gen, 10); let i = 0; while (true) { // should break before the overflow if (*vector::borrow(&output, i) != 0u8) @@ -207,8 +212,8 @@ module sui::random_tests { }; // Sanity check that 2 different outputs are different. - let output1 = bytes(&mut gen, 10); - let output2 = bytes(&mut gen, 10); + let output1 = generate_bytes(&mut gen, 10); + let output2 = generate_bytes(&mut gen, 10); i = 0; while (true) { // should break before the overflow if (vector::borrow(&output1, i) != vector::borrow(&output2, i)) @@ -367,6 +372,78 @@ module sui::random_tests { break }; + // bool + gen = new_generator(&random_state, test_scenario::ctx(scenario)); + assert!(*generator_buffer(&gen) == vector::empty(), 0); + let output1 = generate_bool(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 31, 0); + let output2 = generate_bool(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 30, 0); + assert!(output1 != output2, 0); + let _output3 = generate_u128(&mut gen); + let _output4 = generate_u8(&mut gen); + assert!(generator_counter(&gen) == 1, 0); + assert!(vector::length(generator_buffer(&gen)) == 13, 0); + let saw_false = false; + while (true) { + let x = generate_bool(&mut gen); + if (!x) saw_false = true; + if (x && saw_false) break; + }; + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + fun test_shuffle() { + let scenario_val = test_scenario::begin(@0x0); + let scenario = &mut scenario_val; + + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, @0x0); + + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let v: vector = vector[0, 1, 2, 3, 4,]; + shuffle(&mut gen, &mut v); + assert!(vector::length(&v) == 5, 0); + let i: u16 = 0; + while (i < 5) { + assert!(vector::contains(&v, &i), 0); + i = i + 1; + }; + + // check that numbers indeed eventaually move to all positions + while (true) { + shuffle(&mut gen, &mut v); + if ((*vector::borrow(&v, 4) == 1u16)) + break; + }; + while (true) { + shuffle(&mut gen, &mut v); + if ((*vector::borrow(&v, 0) == 2u16)) + break; + }; + + let v: vector = vector[]; + shuffle(&mut gen, &mut v); + assert!(vector::length(&v) == 0, 0); + + let v: vector = vector[321]; + shuffle(&mut gen, &mut v); + assert!(vector::length(&v) == 1, 0); + assert!(*vector::borrow(&v, 0) == 321u32, 0); + test_scenario::return_shared(random_state); test_scenario::end(scenario_val); } @@ -495,6 +572,9 @@ module sui::random_tests { i = i + 1; }; + // in range with min=max should return min + assert_eq(generate_u32_in_range(&mut gen, 123, 123), 123); + test_scenario::return_shared(random_state); test_scenario::end(scenario_val); } @@ -523,30 +603,6 @@ module sui::random_tests { test_scenario::end(scenario_val); } - #[test] - #[expected_failure(abort_code = random::EInvalidRange)] - fun random_tests_invalid_range_same() { - let scenario_val = test_scenario::begin(@0x0); - let scenario = &mut scenario_val; - - random::create_for_testing(test_scenario::ctx(scenario)); - test_scenario::next_tx(scenario, @0x0); - - let random_state = test_scenario::take_shared(scenario); - update_randomness_state_for_testing( - &mut random_state, - 0, - x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) - ); - - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); - let _output = generate_u32_in_range(&mut gen, 123, 123); - - test_scenario::return_shared(random_state); - test_scenario::end(scenario_val); - } - #[test] fun random_tests_update_after_epoch_change() { let scenario_val = test_scenario::begin(@0x0); diff --git a/sui_programmability/examples/games/sources/raffle.move b/sui_programmability/examples/games/sources/raffle.move index e0e4ce6321782..9eae90a9eba1e 100644 --- a/sui_programmability/examples/games/sources/raffle.move +++ b/sui_programmability/examples/games/sources/raffle.move @@ -25,8 +25,9 @@ module games::raffle { const EGameInProgress: u64 = 0; const EGameAlreadyCompleted: u64 = 1; const EInvalidAmount: u64 = 2; - const EGameMistmatch: u64 = 3; - const EWrongGameWinner: u64 = 4; + const EGameMismatch: u64 = 3; + const ENotWinner: u64 = 4; + const ENoParticipants: u64 = 4; /// Game represents a set of parameters of a single game. struct Game has key { @@ -59,17 +60,26 @@ module games::raffle { } /// Anyone can determine a winner. + /// + /// The function is defined as private entry to prevent calls from other Move functions. (If calls from other + /// functions are allowed, the calling function might abort the transaction depending on the winner.) + /// Gas based attacks are not possible since the gas cost of this function is independent of the winner. entry fun determine_winner(game: &mut Game, r: &Random, clock: &Clock, ctx: &mut TxContext) { assert!(game.end_time <= clock::timestamp_ms(clock), EGameInProgress); assert!(option::is_none(&game.winner), EGameAlreadyCompleted); - - let generator = new_generator(r, ctx); - let winner = random::generate_u32_in_range(&mut generator, 1, game.participants); - game.winner = option::some(winner); + assert!(game.participants > 0, ENoParticipants); + + if (game.participants == 1) { + game.winner = option::some(1); + } else { + let generator = new_generator(r, ctx); + let winner = random::generate_u32_in_range(&mut generator, 1, game.participants); + game.winner = option::some(winner); + } } /// Anyone can play and receive a ticket. - public fun play(game: &mut Game, coin: Coin, clock: &Clock, ctx: &mut TxContext): Ticket { + public fun buy_ticket(game: &mut Game, coin: Coin, clock: &Clock, ctx: &mut TxContext): Ticket { assert!(game.end_time > clock::timestamp_ms(clock), EGameAlreadyCompleted); assert!(coin::value(&coin) == game.cost_in_sui, EInvalidAmount); @@ -84,12 +94,15 @@ module games::raffle { } /// The winner can take the prize. - public fun redeem(ticket: Ticket, game: &mut Game, ctx: &mut TxContext): Coin { - assert!(object::id(game) == ticket.game_id, EGameMistmatch); - assert!(option::contains(&game.winner, &ticket.participant_index), EWrongGameWinner); + public fun redeem(ticket: Ticket, game: Game, ctx: &mut TxContext): Coin { + assert!(object::id(&game) == ticket.game_id, EGameMismatch); + assert!(option::contains(&game.winner, &ticket.participant_index), ENotWinner); destroy_ticket(ticket); - let full_balance = balance::value(&game.balance); - coin::take(&mut game.balance, full_balance, ctx) + + let Game { id, cost_in_sui: _, participants: _, end_time: _, winner: _, balance } = game; + object::delete(id); + let reward = coin::from_balance(balance, ctx); + reward } public fun destroy_ticket(ticket: Ticket) { diff --git a/sui_programmability/examples/games/sources/slot_machine.move b/sui_programmability/examples/games/sources/slot_machine.move index 8724d677966bf..69776f09f6620 100644 --- a/sui_programmability/examples/games/sources/slot_machine.move +++ b/sui_programmability/examples/games/sources/slot_machine.move @@ -3,10 +3,10 @@ /// A betting game that depends on Sui randomness. /// -/// Anyone can create a new game for the current epoch by depositing SUIs as the initial balance. The creator can +/// Anyone can create a new game for the current epoch by depositing SUI as the initial balance. The creator can /// withdraw the remaining balance after the epoch is over. /// -/// Anyone can play the game by betting on X SUIs. They win X with probability 49% and loss the X SUIs otherwise. +/// Anyone can play the game by betting on X SUI. They win X with probability 49% and lose the X SUI otherwise. /// module games::slot_machine { use sui::balance::{Self, Balance}; @@ -24,7 +24,7 @@ module games::slot_machine { const EInvalidEpoch: u64 = 2; /// Game for a specific epoch. - struct Game has key, store { + struct Game has key { id: UID, creator: address, epoch: u64, @@ -38,7 +38,7 @@ module games::slot_machine { ) { let amount = coin::value(&reward); assert!(amount > 0, EInvalidAmount); - transfer::public_share_object(Game { + transfer::share_object(Game { id: object::new(ctx), creator: tx_context::sender(ctx), epoch: tx_context::epoch(ctx), @@ -47,11 +47,12 @@ module games::slot_machine { } /// Creator can withdraw remaining balance if the game is over. - public fun close(game: &mut Game, ctx: &mut TxContext): Coin { + public fun close(game: Game, ctx: &mut TxContext): Coin { assert!(tx_context::epoch(ctx) > game.epoch, EInvalidEpoch); assert!(tx_context::sender(ctx) == game.creator, EInvalidSender); - let full_balance = balance::value(&game.balance); - coin::take(&mut game.balance, full_balance, ctx) + let Game { id, creator: _, epoch: _, balance } = game; + object::delete(id); + coin::from_balance(balance, ctx) } /// Play one turn of the game. @@ -64,7 +65,7 @@ module games::slot_machine { // play the game let generator = new_generator(r, ctx); let bet = random::generate_u8_in_range(&mut generator, 1, 100); - let won = 1 - bet / 50; // equals 1 with probability 49% and 0 otherwise + let won = 1 - (bet / 50); // equals 1 with probability 49% and 0 otherwise // move the bet amount from the user's coin to the game's balance let coin_value = coin::value(coin); diff --git a/sui_programmability/examples/games/tests/raffle_tests.move b/sui_programmability/examples/games/tests/raffle_tests.move index d2b2ffe18ed9e..5ee8a867596ed 100644 --- a/sui_programmability/examples/games/tests/raffle_tests.move +++ b/sui_programmability/examples/games/tests/raffle_tests.move @@ -59,28 +59,28 @@ module games::raffle_tests { test_scenario::next_tx(scenario, user1); mint(user1, 10, scenario); let coin = test_scenario::take_from_sender>(scenario); - let t1 = raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + let t1 = raffle::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); assert!(raffle::get_participants(&game) == 1, 1); raffle::destroy_ticket(t1); // loser test_scenario::next_tx(scenario, user2); mint(user2, 10, scenario); let coin = test_scenario::take_from_sender>(scenario); - let t2 = raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + let t2 = raffle::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); assert!(raffle::get_participants(&game) == 2, 1); raffle::destroy_ticket(t2); // loser test_scenario::next_tx(scenario, user3); mint(user3, 10, scenario); let coin = test_scenario::take_from_sender>(scenario); - let t3 = raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + let t3 = raffle::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); assert!(raffle::get_participants(&game) == 3, 1); raffle::destroy_ticket(t3); // loser test_scenario::next_tx(scenario, user4); mint(user4, 10, scenario); let coin = test_scenario::take_from_sender>(scenario); - let t4 = raffle::play( &mut game, coin, &clock, test_scenario::ctx(scenario)); + let t4 = raffle::buy_ticket( &mut game, coin, &clock, test_scenario::ctx(scenario)); assert!(raffle::get_participants(&game) == 4, 1); // this is the winner @@ -89,14 +89,13 @@ module games::raffle_tests { raffle::determine_winner(&mut game, &random_state, &clock, test_scenario::ctx(scenario)); assert!(raffle::get_winner(&game) == option::some(4), 1); assert!(raffle::get_balance(&game) == 40, 1); + clock::destroy_for_testing(clock); // Take the reward - let coin = raffle::redeem(t4, &mut game, test_scenario::ctx(scenario)); + let coin = raffle::redeem(t4, game, test_scenario::ctx(scenario)); assert!(coin::value(&coin) == 40, 1); coin::burn_for_testing(coin); - clock::destroy_for_testing(clock); - test_scenario::return_shared(game); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); } diff --git a/sui_programmability/examples/games/tests/slot_machine_tests.move b/sui_programmability/examples/games/tests/slot_machine_tests.move index ee23769a21723..b29ea893ebe80 100644 --- a/sui_programmability/examples/games/tests/slot_machine_tests.move +++ b/sui_programmability/examples/games/tests/slot_machine_tests.move @@ -83,11 +83,10 @@ module games::slot_machine_tests { // Take remaining balance test_scenario::next_epoch(scenario, user1); - let coin = slot_machine::close(&mut game, test_scenario::ctx(scenario)); + let coin = slot_machine::close(game, test_scenario::ctx(scenario)); assert!(coin::value(&coin) == 800, 1); coin::burn_for_testing(coin); - test_scenario::return_shared(game); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); } From 610416dee718054aa50046787c7b8205ed020dba Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 7 Feb 2024 16:56:40 +0200 Subject: [PATCH 06/17] add another variant of a raffle --- .../sources/{raffle.move => raffles.move} | 115 +++++++++-- .../examples/games/tests/raffle_tests.move | 102 ---------- .../examples/games/tests/raffles_tests.move | 178 ++++++++++++++++++ 3 files changed, 281 insertions(+), 114 deletions(-) rename sui_programmability/examples/games/sources/{raffle.move => raffles.move} (52%) delete mode 100644 sui_programmability/examples/games/tests/raffle_tests.move create mode 100644 sui_programmability/examples/games/tests/raffles_tests.move diff --git a/sui_programmability/examples/games/sources/raffle.move b/sui_programmability/examples/games/sources/raffles.move similarity index 52% rename from sui_programmability/examples/games/sources/raffle.move rename to sui_programmability/examples/games/sources/raffles.move index 9eae90a9eba1e..c835d70449007 100644 --- a/sui_programmability/examples/games/sources/raffle.move +++ b/sui_programmability/examples/games/sources/raffles.move @@ -1,12 +1,15 @@ // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 -/// A basic raffle game that depends on Sui randomness. +/// Basic raffles games that depends on Sui randomness. /// -/// Anyone can create a new lottery game with an end time and a cost per ticket. After the end time, anyone can trigger -/// a function to determine the winner, and the owner of the winning ticket can redeem the entire balance of the game. +/// Anyone can create a new lottery game with an end time and a price. After the end time, anyone can trigger +/// a function to determine the winner, and the winner gets the entire balance of the game. /// -module games::raffle { +/// - raffle_with_tickets uses tickets which could be transferred to other accounts, used as NFTs, etc. +/// - small_raffle uses a simpler approach with no tickets. + +module games::raffle_with_tickets { use std::option::{Self, Option}; use sui::balance; use sui::balance::Balance; @@ -68,14 +71,9 @@ module games::raffle { assert!(game.end_time <= clock::timestamp_ms(clock), EGameInProgress); assert!(option::is_none(&game.winner), EGameAlreadyCompleted); assert!(game.participants > 0, ENoParticipants); - - if (game.participants == 1) { - game.winner = option::some(1); - } else { - let generator = new_generator(r, ctx); - let winner = random::generate_u32_in_range(&mut generator, 1, game.participants); - game.winner = option::some(winner); - } + let generator = new_generator(r, ctx); + let winner = random::generate_u32_in_range(&mut generator, 1, game.participants); + game.winner = option::some(winner); } /// Anyone can play and receive a ticket. @@ -135,3 +133,96 @@ module games::raffle { balance::value(&game.balance) } } + + + +module games::small_raffle { + use sui::balance::{Self, Balance}; + use sui::clock::{Self, Clock}; + use sui::coin::{Self, Coin}; + use sui::object::{Self, UID}; + use sui::random::{Self, Random, new_generator}; + use sui::sui::SUI; + use sui::table::{Self, Table}; + use sui::transfer; + use sui::tx_context::{TxContext, sender}; + + /// Error codes + const EGameInProgress: u64 = 0; + const EGameAlreadyCompleted: u64 = 1; + const EInvalidAmount: u64 = 2; + + /// Game represents a set of parameters of a single game. + struct Game has key { + id: UID, + cost_in_sui: u64, + participants: u32, + end_time: u64, + balance: Balance, + participants_table: Table, + } + + /// Create a shared-object Game. + public fun create(end_time: u64, cost_in_sui: u64, ctx: &mut TxContext) { + let game = Game { + id: object::new(ctx), + cost_in_sui, + participants: 0, + end_time, + balance: balance::zero(), + participants_table: table::new(ctx), + }; + transfer::share_object(game); + } + + /// Anyone can close the game and send the balance to the winner. + /// + /// The function is defined as private entry to prevent calls from other Move functions. (If calls from other + /// functions are allowed, the calling function might abort the transaction depending on the winner.) + /// Gas based attacks are not possible since the gas cost of this function is independent of the winner. + entry fun close(game: Game, r: &Random, clock: &Clock, ctx: &mut TxContext) { + assert!(game.end_time <= clock::timestamp_ms(clock), EGameInProgress); + let Game { id, cost_in_sui: _, participants, end_time: _, balance , participants_table } = game; + if (participants > 0) { + let generator = new_generator(r, ctx); + let winner = random::generate_u32_in_range(&mut generator, 1, participants); + let winner_address = *table::borrow(&participants_table, winner); + let reward = coin::from_balance(balance, ctx); + transfer::public_transfer(reward, winner_address); + } else { + balance::destroy_zero(balance); + }; + table::drop(participants_table); // TODO: will this work with 10K objects? + object::delete(id); + } + + /// Anyone can play. + public fun play(game: &mut Game, coin: Coin, clock: &Clock, ctx: &mut TxContext) { + assert!(game.end_time > clock::timestamp_ms(clock), EGameAlreadyCompleted); + assert!(coin::value(&coin) == game.cost_in_sui, EInvalidAmount); + + game.participants = game.participants + 1; + coin::put(&mut game.balance, coin); + table::add(&mut game.participants_table, game.participants, sender(ctx)); + } + + #[test_only] + public fun get_cost_in_sui(game: &Game): u64 { + game.cost_in_sui + } + + #[test_only] + public fun get_end_time(game: &Game): u64 { + game.end_time + } + + #[test_only] + public fun get_participants(game: &Game): u32 { + game.participants + } + + #[test_only] + public fun get_balance(game: &Game): u64 { + balance::value(&game.balance) + } +} \ No newline at end of file diff --git a/sui_programmability/examples/games/tests/raffle_tests.move b/sui_programmability/examples/games/tests/raffle_tests.move deleted file mode 100644 index 5ee8a867596ed..0000000000000 --- a/sui_programmability/examples/games/tests/raffle_tests.move +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) Mysten Labs, Inc. -// SPDX-License-Identifier: Apache-2.0 - -#[test_only] -module games::raffle_tests { - - use std::option; - use sui::clock; - use sui::coin::{Self, Coin}; - use sui::random::{Self, update_randomness_state_for_testing, Random}; - use sui::sui::SUI; - use sui::test_scenario::{Self, Scenario}; - use sui::transfer; - - use games::raffle; - - fun mint(addr: address, amount: u64, scenario: &mut Scenario) { - transfer::public_transfer(coin::mint_for_testing(amount, test_scenario::ctx(scenario)), addr); - test_scenario::next_tx(scenario, addr); - } - - #[test] - fun test_game() { - let user1 = @0x0; - let user2 = @0x1; - let user3 = @0x2; - let user4 = @0x3; - - let scenario_val = test_scenario::begin(user1); - let scenario = &mut scenario_val; - - // Setup randomness - random::create_for_testing(test_scenario::ctx(scenario)); - test_scenario::next_tx(scenario, user1); - let random_state = test_scenario::take_shared(scenario); - update_randomness_state_for_testing( - &mut random_state, - 0, - x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) - ); - - // Create the game and get back the output objects. - mint(user1, 1000, scenario); - let end_time = 100; - raffle::create(end_time, 10, test_scenario::ctx(scenario)); - test_scenario::next_tx(scenario, user1); - let game = test_scenario::take_shared(scenario); - assert!(raffle::get_cost_in_sui(&game) == 10, 1); - assert!(raffle::get_participants(&game) == 0, 1); - assert!(raffle::get_end_time(&game) == end_time, 1); - assert!(raffle::get_winner(&game) == option::none(), 1); - assert!(raffle::get_balance(&game) == 0, 1); - - let clock = clock::create_for_testing(test_scenario::ctx(scenario)); - clock::set_for_testing(&mut clock, 10); - - // Play with 4 users (everything here is deterministic) - test_scenario::next_tx(scenario, user1); - mint(user1, 10, scenario); - let coin = test_scenario::take_from_sender>(scenario); - let t1 = raffle::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); - assert!(raffle::get_participants(&game) == 1, 1); - raffle::destroy_ticket(t1); // loser - - test_scenario::next_tx(scenario, user2); - mint(user2, 10, scenario); - let coin = test_scenario::take_from_sender>(scenario); - let t2 = raffle::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); - assert!(raffle::get_participants(&game) == 2, 1); - raffle::destroy_ticket(t2); // loser - - test_scenario::next_tx(scenario, user3); - mint(user3, 10, scenario); - let coin = test_scenario::take_from_sender>(scenario); - let t3 = raffle::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); - assert!(raffle::get_participants(&game) == 3, 1); - raffle::destroy_ticket(t3); // loser - - test_scenario::next_tx(scenario, user4); - mint(user4, 10, scenario); - let coin = test_scenario::take_from_sender>(scenario); - let t4 = raffle::buy_ticket( &mut game, coin, &clock, test_scenario::ctx(scenario)); - assert!(raffle::get_participants(&game) == 4, 1); - // this is the winner - - // Determine the winner (-> user3) - clock::set_for_testing(&mut clock, 101); - raffle::determine_winner(&mut game, &random_state, &clock, test_scenario::ctx(scenario)); - assert!(raffle::get_winner(&game) == option::some(4), 1); - assert!(raffle::get_balance(&game) == 40, 1); - clock::destroy_for_testing(clock); - - // Take the reward - let coin = raffle::redeem(t4, game, test_scenario::ctx(scenario)); - assert!(coin::value(&coin) == 40, 1); - coin::burn_for_testing(coin); - - test_scenario::return_shared(random_state); - test_scenario::end(scenario_val); - } -} diff --git a/sui_programmability/examples/games/tests/raffles_tests.move b/sui_programmability/examples/games/tests/raffles_tests.move new file mode 100644 index 0000000000000..14013f1ddd2d5 --- /dev/null +++ b/sui_programmability/examples/games/tests/raffles_tests.move @@ -0,0 +1,178 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +#[test_only] +module games::raffles_tests { + + use std::option; + use sui::clock; + use sui::coin::{Self, Coin}; + use sui::random::{Self, update_randomness_state_for_testing, Random}; + use sui::sui::SUI; + use sui::test_scenario::{Self, Scenario}; + use sui::transfer; + use games::small_raffle; + + use games::raffle_with_tickets; + + fun mint(addr: address, amount: u64, scenario: &mut Scenario) { + transfer::public_transfer(coin::mint_for_testing(amount, test_scenario::ctx(scenario)), addr); + test_scenario::next_tx(scenario, addr); + } + + #[test] + fun test_game_with_tickets() { + let user1 = @0x0; + let user2 = @0x1; + let user3 = @0x2; + let user4 = @0x3; + + let scenario_val = test_scenario::begin(user1); + let scenario = &mut scenario_val; + + // Setup randomness + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // Create the game and get back the output objects. + mint(user1, 1000, scenario); + let end_time = 100; + raffle_with_tickets::create(end_time, 10, test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let game = test_scenario::take_shared(scenario); + assert!(raffle_with_tickets::get_cost_in_sui(&game) == 10, 1); + assert!(raffle_with_tickets::get_participants(&game) == 0, 1); + assert!(raffle_with_tickets::get_end_time(&game) == end_time, 1); + assert!(raffle_with_tickets::get_winner(&game) == option::none(), 1); + assert!(raffle_with_tickets::get_balance(&game) == 0, 1); + + let clock = clock::create_for_testing(test_scenario::ctx(scenario)); + clock::set_for_testing(&mut clock, 10); + + // Play with 4 users (everything here is deterministic) + test_scenario::next_tx(scenario, user1); + mint(user1, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t1 = raffle_with_tickets::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle_with_tickets::get_participants(&game) == 1, 1); + raffle_with_tickets::destroy_ticket(t1); // loser + + test_scenario::next_tx(scenario, user2); + mint(user2, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t2 = raffle_with_tickets::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle_with_tickets::get_participants(&game) == 2, 1); + raffle_with_tickets::destroy_ticket(t2); // loser + + test_scenario::next_tx(scenario, user3); + mint(user3, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t3 = raffle_with_tickets::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle_with_tickets::get_participants(&game) == 3, 1); + raffle_with_tickets::destroy_ticket(t3); // loser + + test_scenario::next_tx(scenario, user4); + mint(user4, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + let t4 = raffle_with_tickets::buy_ticket( &mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(raffle_with_tickets::get_participants(&game) == 4, 1); + // this is the winner + + // Determine the winner (-> user3) + clock::set_for_testing(&mut clock, 101); + raffle_with_tickets::determine_winner(&mut game, &random_state, &clock, test_scenario::ctx(scenario)); + assert!(raffle_with_tickets::get_winner(&game) == option::some(4), 1); + assert!(raffle_with_tickets::get_balance(&game) == 40, 1); + clock::destroy_for_testing(clock); + + // Take the reward + let coin = raffle_with_tickets::redeem(t4, game, test_scenario::ctx(scenario)); + assert!(coin::value(&coin) == 40, 1); + coin::burn_for_testing(coin); + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } + + #[test] + fun test_simple_game() { + let user1 = @0x0; + let user2 = @0x1; + let user3 = @0x2; + let user4 = @0x3; + + let scenario_val = test_scenario::begin(user1); + let scenario = &mut scenario_val; + + // Setup randomness + random::create_for_testing(test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + // Create the game and get back the output objects. + mint(user1, 1000, scenario); + let end_time = 100; + small_raffle::create(end_time, 10, test_scenario::ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let game = test_scenario::take_shared(scenario); + assert!(small_raffle::get_cost_in_sui(&game) == 10, 1); + assert!(small_raffle::get_participants(&game) == 0, 1); + assert!(small_raffle::get_end_time(&game) == end_time, 1); + assert!(small_raffle::get_balance(&game) == 0, 1); + + let clock = clock::create_for_testing(test_scenario::ctx(scenario)); + clock::set_for_testing(&mut clock, 10); + + // Play with 4 users (everything here is deterministic) + test_scenario::next_tx(scenario, user1); + mint(user1, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + small_raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(small_raffle::get_participants(&game) == 1, 1); + + test_scenario::next_tx(scenario, user2); + mint(user2, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + small_raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(small_raffle::get_participants(&game) == 2, 1); + + test_scenario::next_tx(scenario, user3); + mint(user3, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + small_raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(small_raffle::get_participants(&game) == 3, 1); + + test_scenario::next_tx(scenario, user4); + mint(user4, 10, scenario); + let coin = test_scenario::take_from_sender>(scenario); + small_raffle::play( &mut game, coin, &clock, test_scenario::ctx(scenario)); + assert!(small_raffle::get_participants(&game) == 4, 1); + + // Determine the winner (-> user4) + clock::set_for_testing(&mut clock, 101); + small_raffle::close(game, &random_state, &clock, test_scenario::ctx(scenario)); + clock::destroy_for_testing(clock); + + // Check that received the reward + test_scenario::next_tx(scenario, user4); + let coin = test_scenario::take_from_sender>(scenario); + assert!(coin::value(&coin) == 40, 1); + coin::burn_for_testing(coin); + + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } +} From acd2c680243a50e9d0fa3b78a533033becb62739 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 7 Feb 2024 17:08:55 +0200 Subject: [PATCH 07/17] delete the objects as well --- .../examples/games/sources/raffles.move | 12 +++++++++--- .../examples/games/tests/raffles_tests.move | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/sui_programmability/examples/games/sources/raffles.move b/sui_programmability/examples/games/sources/raffles.move index c835d70449007..77036c71583b1 100644 --- a/sui_programmability/examples/games/sources/raffles.move +++ b/sui_programmability/examples/games/sources/raffles.move @@ -135,7 +135,6 @@ module games::raffle_with_tickets { } - module games::small_raffle { use sui::balance::{Self, Balance}; use sui::clock::{Self, Clock}; @@ -192,7 +191,14 @@ module games::small_raffle { } else { balance::destroy_zero(balance); }; - table::drop(participants_table); // TODO: will this work with 10K objects? + + // TODO: will this work with 10K objects? + let i = 0; + while (i < participants) { + table::remove(&mut participants_table, i); + i = i + 1; + }; + table::destroy_empty(participants_table); object::delete(id); } @@ -225,4 +231,4 @@ module games::small_raffle { public fun get_balance(game: &Game): u64 { balance::value(&game.balance) } -} \ No newline at end of file +} diff --git a/sui_programmability/examples/games/tests/raffles_tests.move b/sui_programmability/examples/games/tests/raffles_tests.move index 14013f1ddd2d5..831a1aba87410 100644 --- a/sui_programmability/examples/games/tests/raffles_tests.move +++ b/sui_programmability/examples/games/tests/raffles_tests.move @@ -102,7 +102,7 @@ module games::raffles_tests { } #[test] - fun test_simple_game() { + fun test_small_raffle() { let user1 = @0x0; let user2 = @0x1; let user3 = @0x2; From 133551a5acbe278e0ef08737b07e7c36beb2ca73 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 7 Feb 2024 20:54:11 +0200 Subject: [PATCH 08/17] fix for the case of 100 --- sui_programmability/examples/games/sources/slot_machine.move | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sui_programmability/examples/games/sources/slot_machine.move b/sui_programmability/examples/games/sources/slot_machine.move index 69776f09f6620..ded3c0a0bf616 100644 --- a/sui_programmability/examples/games/sources/slot_machine.move +++ b/sui_programmability/examples/games/sources/slot_machine.move @@ -65,7 +65,8 @@ module games::slot_machine { // play the game let generator = new_generator(r, ctx); let bet = random::generate_u8_in_range(&mut generator, 1, 100); - let won = 1 - (bet / 50); // equals 1 with probability 49% and 0 otherwise + let lost = bet / 50; // 0 with probability 49%, and 1 or 2 with probability 51% + let won = (2 - lost) / 2; // 1 with probability 49%, and 0 with probability 51% // move the bet amount from the user's coin to the game's balance let coin_value = coin::value(coin); From 5a2de501d76a926b49c31685a56768bc29c5604a Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 7 Feb 2024 22:11:54 +0200 Subject: [PATCH 09/17] an example of airdropped hidden nfts --- .../examples/nfts/sources/random_nft.move | 160 ++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 sui_programmability/examples/nfts/sources/random_nft.move diff --git a/sui_programmability/examples/nfts/sources/random_nft.move b/sui_programmability/examples/nfts/sources/random_nft.move new file mode 100644 index 0000000000000..04053664e5325 --- /dev/null +++ b/sui_programmability/examples/nfts/sources/random_nft.move @@ -0,0 +1,160 @@ +// Copyright (c) Mysten Labs, Inc. +// SPDX-License-Identifier: Apache-2.0 + +/// A simple NFT that can be airdropped to users without a value and converted to a random metal NFT. +/// The probability of getting a gold, silver, or bronze NFT is 10%, 30%, and 60% respectively. +module nfts::random_nft_airdrop { + use std::string; + use std::vector; + use sui::object::{Self, UID}; + use sui::random; + use sui::random::{Random, new_generator}; + use sui::transfer; + use sui::tx_context::{Self, TxContext}; + + const GOLD: u8 = 1; + const SILVER: u8 = 2; + const BRONZE: u8 = 3; + + struct AirDropNFT has key, store { + id: UID, + } + + struct MetalNFT has key, store { + id: UID, + metal: u8, + } + + struct MintingCapability has key { + id: UID + } + + #[allow(unused_function)] + fun init(ctx: &mut TxContext) { + transfer::transfer(MintingCapability { + id: object::new(ctx) + }, tx_context::sender(ctx)); + } + + public fun mint(_cap: &MintingCapability, n: u16, ctx: &mut TxContext): vector { + let result = vector[]; + let i = 0; + while (i < n) { + vector::push_back(&mut result, AirDropNFT { id: object::new(ctx) }); + i = i + 1; + }; + result + } + + entry fun reveal(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { + destroy_airdrop_nft(nft); + + let generator = new_generator(r, ctx); + let v = random::generate_u8_in_range(&mut generator, 1, 100); + + let is_gold = arithmetic_is_less_than(v, 11, 100); // probability of 10% + let is_silver = arithmetic_is_less_than(v, 41, 100) * (1 - is_gold); // probability of 30% + let is_bronze = (1 - is_gold) * (1 - is_silver); // probability of 60% + let metal = is_gold * GOLD + is_silver * SILVER + is_bronze * BRONZE; + + transfer::public_transfer(MetalNFT { + id: object::new(ctx), + metal, + }, tx_context::sender(ctx)); + } + + // safe "is v < w? where v <= v_max" using integer arithmetic + fun arithmetic_is_less_than(v: u8, w: u8, v_max: u8): u8 { + assert!(v_max >= w && w > 0, 0); + let v_max_over_w = v_max / w; + let v_over_w = v / w; // 0 if v < w, [1, v_max_over_w] if above + (v_max_over_w - v_over_w) / v_max_over_w + } + + fun destroy_airdrop_nft(nft: AirDropNFT) { + let AirDropNFT { id } = nft; + object::delete(id) + } + + public fun metal_string(nft: &MetalNFT): string::String { + if (nft.metal == GOLD) { + return string::utf8(b"Gold") + }; + if (nft.metal == SILVER) { + return string::utf8(b"Silver") + }; + string::utf8(b"Bronze") + } + + #[test_only] + public fun destroy_cap(cap: MintingCapability) { + let MintingCapability { id } = cap; + object::delete(id) + } + + #[test_only] + public fun test_init(ctx: &mut TxContext) { + init(ctx) + } +} + +#[test_only] +module nfts::random_nft_airdrop_tests { + use sui::test_scenario; + use std::string; + use std::vector; + use sui::random; + use sui::random::{Random, update_randomness_state_for_testing}; + use sui::test_scenario::{ctx, take_from_sender, next_tx, return_to_sender}; + use nfts::random_nft_airdrop::{MintingCapability, MetalNFT}; + use nfts::random_nft_airdrop; + + #[test] + fun test_e2e() { + let user0 = @0x0; + let user1 = @0x1; + let scenario_val = test_scenario::begin(user0); + let scenario = &mut scenario_val; + + // Setup randomness + random::create_for_testing(ctx(scenario)); + test_scenario::next_tx(scenario, user0); + let random_state = test_scenario::take_shared(scenario); + update_randomness_state_for_testing( + &mut random_state, + 0, + x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", + test_scenario::ctx(scenario) + ); + + test_scenario::next_tx(scenario, user1); + // mint airdrops + random_nft_airdrop::test_init(ctx(scenario)); + test_scenario::next_tx(scenario, user1); + let cap = take_from_sender(scenario); + let nfts = random_nft_airdrop::mint(&cap, 20, ctx(scenario)); + + let seen_gold = false; + let seen_silver = false; + let seen_bronze = false; + let i = 0; + while (i < 20) { + random_nft_airdrop::reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); + next_tx(scenario, user1); + let nft = take_from_sender(scenario); + let metal = random_nft_airdrop::metal_string(&nft); + seen_gold = seen_gold || metal == string::utf8(b"Gold"); + seen_silver = seen_silver || metal == string::utf8(b"Silver"); + seen_bronze = seen_bronze || metal == string::utf8(b"Bronze"); + return_to_sender(scenario, nft); + i = i + 1; + }; + + assert!(seen_gold && seen_silver && seen_bronze, 1); + + vector::destroy_empty(nfts); + random_nft_airdrop::destroy_cap(cap); + test_scenario::return_shared(random_state); + test_scenario::end(scenario_val); + } +} From 61727890f5635c7bf8ff309f783e85884af52218 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Thu, 8 Feb 2024 13:20:23 +0200 Subject: [PATCH 10/17] add another alternative --- .../examples/nfts/sources/random_nft.move | 51 +++++++++++++++++-- 1 file changed, 48 insertions(+), 3 deletions(-) diff --git a/sui_programmability/examples/nfts/sources/random_nft.move b/sui_programmability/examples/nfts/sources/random_nft.move index 04053664e5325..8b0c96f9eeef9 100644 --- a/sui_programmability/examples/nfts/sources/random_nft.move +++ b/sui_programmability/examples/nfts/sources/random_nft.move @@ -12,6 +12,8 @@ module nfts::random_nft_airdrop { use sui::transfer; use sui::tx_context::{Self, TxContext}; + const EInvalidParams: u64 = 0; + const GOLD: u8 = 1; const SILVER: u8 = 2; const BRONZE: u8 = 3; @@ -46,6 +48,11 @@ module nfts::random_nft_airdrop { result } + + /// Reveal the metal of the airdrop NFT and convert it to a metal NFT. + /// This function uses arithmetic_is_less_than to determine the metal of the NFT in a way that consumes the same + /// amount of gas regardless of the value of the random number. + /// See alternative_reveal for a different implementation. entry fun reveal(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { destroy_airdrop_nft(nft); @@ -63,14 +70,48 @@ module nfts::random_nft_airdrop { }, tx_context::sender(ctx)); } - // safe "is v < w? where v <= v_max" using integer arithmetic + // Implements "is v < w? where v <= v_max" using integer arithmetic. Returns 1 if true, 0 otherwise. + // Safe in case w and v_max are independent of the randomenss (e.g., fixed). + // Does not check if v <= v_max. fun arithmetic_is_less_than(v: u8, w: u8, v_max: u8): u8 { - assert!(v_max >= w && w > 0, 0); + assert!(v_max >= w && w > 0, EInvalidParams); let v_max_over_w = v_max / w; let v_over_w = v / w; // 0 if v < w, [1, v_max_over_w] if above (v_max_over_w - v_over_w) / v_max_over_w } + /// An alternative implementation of reveal that uses if-else statements to determine the metal of the NFT. + /// Here the "happier flows" consume more gas than the less happy ones. + entry fun alternative_reveal(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { + destroy_airdrop_nft(nft); + + let generator = new_generator(r, ctx); + let v = random::generate_u8_in_range(&mut generator, 1, 100); + + if (v <= 60) { + transfer::public_transfer(MetalNFT { + id: object::new(ctx), + metal: BRONZE, + }, tx_context::sender(ctx)); + return + }; + + if (v <= 90) { + transfer::public_transfer(MetalNFT { + id: object::new(ctx), + metal: SILVER, + }, tx_context::sender(ctx)); + return + }; + + if (v <= 100) { + transfer::public_transfer(MetalNFT { + id: object::new(ctx), + metal: GOLD, + }, tx_context::sender(ctx)); + }; + } + fun destroy_airdrop_nft(nft: AirDropNFT) { let AirDropNFT { id } = nft; object::delete(id) @@ -139,7 +180,11 @@ module nfts::random_nft_airdrop_tests { let seen_bronze = false; let i = 0; while (i < 20) { - random_nft_airdrop::reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); + if (i % 2 == 1) { + random_nft_airdrop::reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); + } else { + random_nft_airdrop::alternative_reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); + }; next_tx(scenario, user1); let nft = take_from_sender(scenario); let metal = random_nft_airdrop::metal_string(&nft); From b40c6ae1b20937ad41e65a01349a70d2717f52e7 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Thu, 8 Feb 2024 15:08:52 +0200 Subject: [PATCH 11/17] add the third alternative for completion --- .../examples/nfts/sources/random_nft.move | 51 +++++++++++++++++-- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/sui_programmability/examples/nfts/sources/random_nft.move b/sui_programmability/examples/nfts/sources/random_nft.move index 8b0c96f9eeef9..90a0c66f7d5db 100644 --- a/sui_programmability/examples/nfts/sources/random_nft.move +++ b/sui_programmability/examples/nfts/sources/random_nft.move @@ -6,7 +6,7 @@ module nfts::random_nft_airdrop { use std::string; use std::vector; - use sui::object::{Self, UID}; + use sui::object::{Self, UID, delete}; use sui::random; use sui::random::{Random, new_generator}; use sui::transfer; @@ -52,7 +52,7 @@ module nfts::random_nft_airdrop { /// Reveal the metal of the airdrop NFT and convert it to a metal NFT. /// This function uses arithmetic_is_less_than to determine the metal of the NFT in a way that consumes the same /// amount of gas regardless of the value of the random number. - /// See alternative_reveal for a different implementation. + /// See reveal_alternative1 and reveal_alternative2_step1 for different implementations. entry fun reveal(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { destroy_airdrop_nft(nft); @@ -80,9 +80,11 @@ module nfts::random_nft_airdrop { (v_max_over_w - v_over_w) / v_max_over_w } + /// An alternative implementation of reveal that uses if-else statements to determine the metal of the NFT. - /// Here the "happier flows" consume more gas than the less happy ones. - entry fun alternative_reveal(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { + /// Here the "happier flows" consume more gas than the less happy ones (it assumes that users always prefer the + /// rarest metals). + entry fun reveal_alternative1(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { destroy_airdrop_nft(nft); let generator = new_generator(r, ctx); @@ -112,6 +114,45 @@ module nfts::random_nft_airdrop { }; } + + /// An alternative implementation of reveal that uses two steps to determine the metal of the NFT. + /// reveal_alternative2_step1 retrieves the random value, and reveal_alternative2_step2 determines the metal. + + struct RandomnessNFT has key, store { + id: UID, + value: u8, + } + + entry fun reveal_alternative2_step1(nft: AirDropNFT, r: &Random, ctx: &mut TxContext) { + destroy_airdrop_nft(nft); + + let generator = new_generator(r, ctx); + let v = random::generate_u8_in_range(&mut generator, 1, 100); + + transfer::public_transfer(RandomnessNFT { + id: object::new(ctx), + value: v, + }, tx_context::sender(ctx)); + } + + public fun reveal_alternative2_step2(nft: RandomnessNFT, ctx: &mut TxContext): MetalNFT { + let RandomnessNFT { id, value } = nft; + delete(id); + + let metal = BRONZE; + if (value <= 10) { + metal = GOLD; + }; + if (10 < value && value <= 40) { + metal = SILVER; + }; + + MetalNFT { + id: object::new(ctx), + metal, + } + } + fun destroy_airdrop_nft(nft: AirDropNFT) { let AirDropNFT { id } = nft; object::delete(id) @@ -183,7 +224,7 @@ module nfts::random_nft_airdrop_tests { if (i % 2 == 1) { random_nft_airdrop::reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); } else { - random_nft_airdrop::alternative_reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); + random_nft_airdrop::reveal_alternative1(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); }; next_tx(scenario, user1); let nft = take_from_sender(scenario); From 4397aabe4c2cb2cd00ad846270ef84626e006094 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 21 Feb 2024 15:00:02 +0200 Subject: [PATCH 12/17] fix base index --- sui_programmability/examples/games/sources/raffles.move | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sui_programmability/examples/games/sources/raffles.move b/sui_programmability/examples/games/sources/raffles.move index 77036c71583b1..3da2ca24cd62c 100644 --- a/sui_programmability/examples/games/sources/raffles.move +++ b/sui_programmability/examples/games/sources/raffles.move @@ -193,8 +193,8 @@ module games::small_raffle { }; // TODO: will this work with 10K objects? - let i = 0; - while (i < participants) { + let i = 1; + while (i <= participants) { table::remove(&mut participants_table, i); i = i + 1; }; From d38a0c52754051aec80c6b96be461ef915cef5dc Mon Sep 17 00:00:00 2001 From: benr-ml Date: Wed, 13 Mar 2024 11:23:13 +0200 Subject: [PATCH 13/17] update comments --- .../packages/sui-framework/sources/random.move | 7 +++---- .../examples/games/sources/raffles.move | 11 +++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index 95b15e0b69bf7..582eb2f797f30 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -105,7 +105,7 @@ module sui::random { let epoch = tx_context::epoch(ctx); let inner = load_inner_mut(self); if (inner.randomness_round == 0 && inner.epoch == 0 && - vector::is_empty(&inner.random_bytes)) { + vector::is_empty(&inner.random_bytes)) { // First update should be for round zero. assert!(new_round == 0, EInvalidRandomnessUpdate); } else { @@ -272,7 +272,7 @@ module sui::random { (u128_in_range(g, (min as u128), (max as u128), 9) as u8) } - /// Shuffle a vector using the random generator. + /// Shuffle a vector using the random generator (Fisher–Yates/Knuth shuffle). public fun shuffle(g: &mut RandomGenerator, v: &mut vector) { let n = (vector::length(v) as u32); if (n == 0) { @@ -280,7 +280,7 @@ module sui::random { }; let i: u32 = 0; while (i < (n - 1)) { - let j = generate_u32_in_range(g, i, n-1); + let j = generate_u32_in_range(g, i, n - 1); vector::swap(v, (i as u64), (j as u64)); i = i + 1; }; @@ -300,5 +300,4 @@ module sui::random { public fun generator_buffer(r: &RandomGenerator): &vector { &r.buffer } - } diff --git a/sui_programmability/examples/games/sources/raffles.move b/sui_programmability/examples/games/sources/raffles.move index 3da2ca24cd62c..ab609b35d4286 100644 --- a/sui_programmability/examples/games/sources/raffles.move +++ b/sui_programmability/examples/games/sources/raffles.move @@ -3,7 +3,7 @@ /// Basic raffles games that depends on Sui randomness. /// -/// Anyone can create a new lottery game with an end time and a price. After the end time, anyone can trigger +/// Anyone can create a new raffle game with an end time and a price. After the end time, anyone can trigger /// a function to determine the winner, and the winner gets the entire balance of the game. /// /// - raffle_with_tickets uses tickets which could be transferred to other accounts, used as NFTs, etc. @@ -104,7 +104,7 @@ module games::raffle_with_tickets { } public fun destroy_ticket(ticket: Ticket) { - let Ticket { id, game_id: _, participant_index: _} = ticket; + let Ticket { id, game_id: _, participant_index: _ } = ticket; object::delete(id); } @@ -150,6 +150,9 @@ module games::small_raffle { const EGameInProgress: u64 = 0; const EGameAlreadyCompleted: u64 = 1; const EInvalidAmount: u64 = 2; + const EReachedMaxParticipants: u64 = 3; + + const MaxParticipants: u32 = 500; /// Game represents a set of parameters of a single game. struct Game has key { @@ -181,7 +184,7 @@ module games::small_raffle { /// Gas based attacks are not possible since the gas cost of this function is independent of the winner. entry fun close(game: Game, r: &Random, clock: &Clock, ctx: &mut TxContext) { assert!(game.end_time <= clock::timestamp_ms(clock), EGameInProgress); - let Game { id, cost_in_sui: _, participants, end_time: _, balance , participants_table } = game; + let Game { id, cost_in_sui: _, participants, end_time: _, balance, participants_table } = game; if (participants > 0) { let generator = new_generator(r, ctx); let winner = random::generate_u32_in_range(&mut generator, 1, participants); @@ -192,7 +195,6 @@ module games::small_raffle { balance::destroy_zero(balance); }; - // TODO: will this work with 10K objects? let i = 1; while (i <= participants) { table::remove(&mut participants_table, i); @@ -206,6 +208,7 @@ module games::small_raffle { public fun play(game: &mut Game, coin: Coin, clock: &Clock, ctx: &mut TxContext) { assert!(game.end_time > clock::timestamp_ms(clock), EGameAlreadyCompleted); assert!(coin::value(&coin) == game.cost_in_sui, EInvalidAmount); + assert!(game.participants < MaxParticipants, EReachedMaxParticipants); game.participants = game.participants + 1; coin::put(&mut game.balance, coin); From c2337ce3e6d1cbb19c6cd274362ad7ee580d04f2 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Thu, 14 Mar 2024 11:29:44 +0200 Subject: [PATCH 14/17] address comments --- .../sui-framework/sources/random.move | 28 +++-- .../sui-framework/tests/random_tests.move | 119 ++++++++---------- .../examples/games/sources/raffles.move | 12 +- .../examples/games/sources/slot_machine.move | 3 +- .../examples/games/tests/raffles_tests.move | 9 +- .../games/tests/slot_machine_tests.move | 3 +- .../examples/nfts/sources/random_nft.move | 92 ++++++-------- 7 files changed, 123 insertions(+), 143 deletions(-) diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index 582eb2f797f30..bc5b37478c90c 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -17,9 +17,11 @@ module sui::random { const EWrongInnerVersion: u64 = 1; const EInvalidRandomnessUpdate: u64 = 2; const EInvalidRange: u64 = 3; + const EInvalidLength: u64 = 4; const CURRENT_VERSION: u64 = 1; const RAND_OUTPUT_LEN: u16 = 32; + const U16_MAX: u64 = 0xFFFF; /// Singleton shared object which stores the global randomness state. /// The actual state is stored in a versioned inner field. @@ -173,11 +175,12 @@ module sui::random { vector::append(&mut result, derive_next_block(g)); num_of_blocks = num_of_blocks - 1; }; - // Take remaining bytes from the generator's buffer. + // Fill the generator's buffer if needed. let num_of_bytes = (num_of_bytes as u64); if (vector::length(&g.buffer) < (num_of_bytes - vector::length(&result))) { fill_buffer(g); }; + // Take remaining bytes from the generator's buffer. while (vector::length(&result) < num_of_bytes) { vector::push_back(&mut result, vector::pop_back(&mut g.buffer)); }; @@ -186,6 +189,7 @@ module sui::random { // Helper function that extracts the given number of bytes from the random generator and returns it as u256. // Assumes that the caller has already checked that num_of_bytes is valid. + // TODO: Replace with a macro when we have support for it. fun u256_from_bytes(g: &mut RandomGenerator, num_of_bytes: u8): u256 { if (vector::length(&g.buffer) < (num_of_bytes as u64)) { fill_buffer(g); @@ -236,15 +240,20 @@ module sui::random { } // Helper function to generate a random u128 in [min, max] using a random number with num_of_bytes bytes. - // Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias. + // Assumes that the caller verified the inputs, and uses num_of_bytes to control the bias (e.g., 8 bytes larger + // than the actual type used by the caller function to limit the bias by 2^{-64}). + // TODO: Replace with a macro when we have support for it. fun u128_in_range(g: &mut RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128 { assert!(min <= max, EInvalidRange); if (min == max) { return min }; - let diff = ((max - min) as u256) + 1; + // Pick a random number in [0, max - min] by generating a random number that is larger than max-min, and taking + // the modulo of the random number by the range size. Then add the min to the result to get a number in + // [min, max]. + let range_size = ((max - min) as u256) + 1; let rand = u256_from_bytes(g, num_of_bytes); - min + ((rand % diff) as u128) + min + ((rand % range_size) as u128) } /// Generate a random u128 in [min, max] (with a bias of 2^{-64}). @@ -274,13 +283,16 @@ module sui::random { /// Shuffle a vector using the random generator (Fisher–Yates/Knuth shuffle). public fun shuffle(g: &mut RandomGenerator, v: &mut vector) { - let n = (vector::length(v) as u32); + let n = vector::length(v); if (n == 0) { return }; - let i: u32 = 0; - while (i < (n - 1)) { - let j = generate_u32_in_range(g, i, n - 1); + assert!(n <= U16_MAX, EInvalidLength); + let n = (n as u16); + let i: u16 = 0; + let end = n - 1; + while (i < end) { + let j = generate_u16_in_range(g, i, end); vector::swap(v, (i as u64), (j as u64)); i = i + 1; }; diff --git a/crates/sui-framework/packages/sui-framework/tests/random_tests.move b/crates/sui-framework/packages/sui-framework/tests/random_tests.move index 21177a02ce74d..882c75370854a 100644 --- a/crates/sui-framework/packages/sui-framework/tests/random_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/random_tests.move @@ -1,4 +1,3 @@ - // Copyright (c) Mysten Labs, Inc. // SPDX-License-Identifier: Apache-2.0 @@ -34,7 +33,7 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); let gen = new_generator(&random_state, test_scenario::ctx(scenario)); @@ -63,10 +62,10 @@ module sui::random_tests { &mut random_state, 0, global_random1, - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::next_tx(scenario, @0x0); - let gen1= new_generator(&random_state, test_scenario::ctx(scenario)); + let gen1 = new_generator(&random_state, test_scenario::ctx(scenario)); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); @@ -78,10 +77,10 @@ module sui::random_tests { &mut random_state, 1, global_random1, - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::next_tx(scenario, @0x0); - let gen2= new_generator(&random_state, test_scenario::ctx(scenario)); + let gen2 = new_generator(&random_state, test_scenario::ctx(scenario)); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); @@ -93,16 +92,16 @@ module sui::random_tests { &mut random_state, 2, global_random2, - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::next_tx(scenario, @0x0); - let gen3= new_generator(&random_state, test_scenario::ctx(scenario)); - let gen4= new_generator(&random_state, test_scenario::ctx(scenario)); + let gen3 = new_generator(&random_state, test_scenario::ctx(scenario)); + let gen4 = new_generator(&random_state, test_scenario::ctx(scenario)); test_scenario::return_shared(random_state); test_scenario::end(scenario_val); assert!(generator_counter(&gen1) == 0, 0); - assert!(*generator_buffer(&gen1) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen1)), 0); assert!(generator_seed(&gen1) == generator_seed(&gen2), 0); assert!(generator_seed(&gen1) != generator_seed(&gen3), 0); assert!(generator_seed(&gen3) != generator_seed(&gen4), 0); @@ -121,7 +120,7 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); // Regression (not critical for security, but still an indication that something is wrong). @@ -170,13 +169,13 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); let gen = new_generator(&random_state, test_scenario::ctx(scenario)); // Check the output size & internal generator state - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output = generate_bytes(&mut gen, 1); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 31, 0); @@ -205,9 +204,9 @@ module sui::random_tests { // Sanity check that the output is not all zeros. let output = generate_bytes(&mut gen, 10); let i = 0; - while (true) { // should break before the overflow - if (*vector::borrow(&output, i) != 0u8) - break; + loop { + // should break before the overflow + if (*vector::borrow(&output, i) != 0u8) break; i = i + 1; }; @@ -215,9 +214,9 @@ module sui::random_tests { let output1 = generate_bytes(&mut gen, 10); let output2 = generate_bytes(&mut gen, 10); i = 0; - while (true) { // should break before the overflow - if (vector::borrow(&output1, i) != vector::borrow(&output2, i)) - break; + loop { + // should break before the overflow + if (vector::borrow(&output1, i) != vector::borrow(&output2, i)) break; i = i + 1; }; @@ -238,12 +237,12 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); // u256 let gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u256(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 0, 0); @@ -260,13 +259,12 @@ module sui::random_tests { while (i < 32) { let x = generate_u256(&mut gen); let x_bytes = bcs::to_bytes(&x); - if (*vector::borrow(&x_bytes, i) != 0u8) - i = i + 1; + if (*vector::borrow(&x_bytes, i) != 0u8) i = i + 1; }; // u128 gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u128(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 16, 0); @@ -282,13 +280,12 @@ module sui::random_tests { while (i < 16) { let x = generate_u128(&mut gen); let x_bytes = bcs::to_bytes(&x); - if (*vector::borrow(&x_bytes, i) != 0u8) - i = i + 1; + if (*vector::borrow(&x_bytes, i) != 0u8) i = i + 1; }; // u64 gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u64(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 24, 0); @@ -304,13 +301,12 @@ module sui::random_tests { while (i < 8) { let x = generate_u64(&mut gen); let x_bytes = bcs::to_bytes(&x); - if (*vector::borrow(&x_bytes, i) != 0u8) - i = i + 1; + if (*vector::borrow(&x_bytes, i) != 0u8) i = i + 1; }; // u32 gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u32(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 28, 0); @@ -326,13 +322,12 @@ module sui::random_tests { while (i < 4) { let x = generate_u32(&mut gen); let x_bytes = bcs::to_bytes(&x); - if (*vector::borrow(&x_bytes, i) != 0u8) - i = i + 1; + if (*vector::borrow(&x_bytes, i) != 0u8) i = i + 1; }; // u16 gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u16(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 30, 0); @@ -348,13 +343,12 @@ module sui::random_tests { while (i < 2) { let x = generate_u16(&mut gen); let x_bytes = bcs::to_bytes(&x); - if (*vector::borrow(&x_bytes, i) != 0u8) - i = i + 1; + if (*vector::borrow(&x_bytes, i) != 0u8) i = i + 1; }; // u8 gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u8(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 31, 0); @@ -366,15 +360,14 @@ module sui::random_tests { let _output4 = generate_u8(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 13, 0); - while (true) { + loop { let x = generate_u8(&mut gen); - if (x != 0u8) - break + if (x != 0u8) break }; // bool gen = new_generator(&random_state, test_scenario::ctx(scenario)); - assert!(*generator_buffer(&gen) == vector::empty(), 0); + assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_bool(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 31, 0); @@ -387,9 +380,9 @@ module sui::random_tests { assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 13, 0); let saw_false = false; - while (true) { + loop { let x = generate_bool(&mut gen); - if (!x) saw_false = true; + saw_false = saw_false || !x; if (x && saw_false) break; }; @@ -410,11 +403,11 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); let gen = new_generator(&random_state, test_scenario::ctx(scenario)); - let v: vector = vector[0, 1, 2, 3, 4,]; + let v: vector = vector[0, 1, 2, 3, 4]; shuffle(&mut gen, &mut v); assert!(vector::length(&v) == 5, 0); let i: u16 = 0; @@ -424,15 +417,13 @@ module sui::random_tests { }; // check that numbers indeed eventaually move to all positions - while (true) { + loop { shuffle(&mut gen, &mut v); - if ((*vector::borrow(&v, 4) == 1u16)) - break; + if ((*vector::borrow(&v, 4) == 1u16)) break; }; - while (true) { + loop { shuffle(&mut gen, &mut v); - if ((*vector::borrow(&v, 0) == 2u16)) - break; + if ((*vector::borrow(&v, 0) == 2u16)) break; }; let v: vector = vector[]; @@ -461,7 +452,7 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); // generate_u128_in_range @@ -564,8 +555,8 @@ module sui::random_tests { let i = 0; while (i < 50) { let (min, max) = (generate_u8(&mut gen), generate_u8(&mut gen)); - let (min, max) = if (min < max) { (min, max) } else { (max, min) }; - let (min, max) = if (min == max) { (min, max + 1) } else { (min, max) }; + let (min, max) = if (min < max) (min, max) else (max, min); + let (min, max) = if (min == max) (min, max + 1) else (min, max); let output = generate_u8_in_range(&mut gen, min, max); assert!(output >= min, 0); assert!(output <= max, 0); @@ -593,7 +584,7 @@ module sui::random_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); let gen = new_generator(&random_state, test_scenario::ctx(scenario)); @@ -616,13 +607,13 @@ module sui::random_tests { &mut random_state, 0, vector[0, 1, 2, 3], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); update_randomness_state_for_testing( &mut random_state, 1, vector[4, 5, 6, 7], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::next_epoch(scenario, @0x0); @@ -631,7 +622,7 @@ module sui::random_tests { &mut random_state, 0, vector[8, 9, 10, 11], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::return_shared(random_state); @@ -650,15 +641,15 @@ module sui::random_tests { let random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, - 1, + 0, vector[0, 1, 2, 3], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); update_randomness_state_for_testing( &mut random_state, - 1, + 0, vector[0, 1, 2, 3], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::return_shared(random_state); @@ -677,15 +668,15 @@ module sui::random_tests { let random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, - 1, + 0, vector[0, 1, 2, 3], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); update_randomness_state_for_testing( &mut random_state, 3, vector[0, 1, 2, 3], - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::return_shared(random_state); diff --git a/sui_programmability/examples/games/sources/raffles.move b/sui_programmability/examples/games/sources/raffles.move index ab609b35d4286..630f5684f7528 100644 --- a/sui_programmability/examples/games/sources/raffles.move +++ b/sui_programmability/examples/games/sources/raffles.move @@ -11,15 +11,11 @@ module games::raffle_with_tickets { use std::option::{Self, Option}; - use sui::balance; - use sui::balance::Balance; - use sui::clock; - use sui::clock::Clock; - use sui::coin; - use sui::coin::Coin; + use sui::balance::{Self, Balance}; + use sui::clock::{Self, Clock}; + use sui::coin::{Self, Coin}; use sui::object::{Self, ID, UID}; - use sui::random; - use sui::random::{Random, new_generator}; + use sui::random::{Self, Random, new_generator}; use sui::sui::SUI; use sui::transfer; use sui::tx_context::TxContext; diff --git a/sui_programmability/examples/games/sources/slot_machine.move b/sui_programmability/examples/games/sources/slot_machine.move index ded3c0a0bf616..774dd24141222 100644 --- a/sui_programmability/examples/games/sources/slot_machine.move +++ b/sui_programmability/examples/games/sources/slot_machine.move @@ -34,7 +34,7 @@ module games::slot_machine { /// Create a new game with a given initial reward for the current epoch. public fun create( reward: Coin, - ctx: &mut TxContext + ctx: &mut TxContext, ) { let amount = coin::value(&reward); assert!(amount > 0, EInvalidAmount); @@ -89,5 +89,4 @@ module games::slot_machine { public fun get_epoch(game: &Game): u64 { game.epoch } - } diff --git a/sui_programmability/examples/games/tests/raffles_tests.move b/sui_programmability/examples/games/tests/raffles_tests.move index 831a1aba87410..60a84d32eb089 100644 --- a/sui_programmability/examples/games/tests/raffles_tests.move +++ b/sui_programmability/examples/games/tests/raffles_tests.move @@ -3,7 +3,6 @@ #[test_only] module games::raffles_tests { - use std::option; use sui::clock; use sui::coin::{Self, Coin}; @@ -38,7 +37,7 @@ module games::raffles_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); // Create the game and get back the output objects. @@ -81,7 +80,7 @@ module games::raffles_tests { test_scenario::next_tx(scenario, user4); mint(user4, 10, scenario); let coin = test_scenario::take_from_sender>(scenario); - let t4 = raffle_with_tickets::buy_ticket( &mut game, coin, &clock, test_scenario::ctx(scenario)); + let t4 = raffle_with_tickets::buy_ticket(&mut game, coin, &clock, test_scenario::ctx(scenario)); assert!(raffle_with_tickets::get_participants(&game) == 4, 1); // this is the winner @@ -119,7 +118,7 @@ module games::raffles_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); // Create the game and get back the output objects. @@ -158,7 +157,7 @@ module games::raffles_tests { test_scenario::next_tx(scenario, user4); mint(user4, 10, scenario); let coin = test_scenario::take_from_sender>(scenario); - small_raffle::play( &mut game, coin, &clock, test_scenario::ctx(scenario)); + small_raffle::play(&mut game, coin, &clock, test_scenario::ctx(scenario)); assert!(small_raffle::get_participants(&game) == 4, 1); // Determine the winner (-> user4) diff --git a/sui_programmability/examples/games/tests/slot_machine_tests.move b/sui_programmability/examples/games/tests/slot_machine_tests.move index b29ea893ebe80..9d14c5bc6c78f 100644 --- a/sui_programmability/examples/games/tests/slot_machine_tests.move +++ b/sui_programmability/examples/games/tests/slot_machine_tests.move @@ -8,7 +8,6 @@ module games::slot_machine_tests { use sui::sui::SUI; use sui::test_scenario::{Self, Scenario}; use sui::transfer; - use games::slot_machine; fun mint(addr: address, amount: u64, scenario: &mut Scenario) { @@ -31,7 +30,7 @@ module games::slot_machine_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); // Create the game and get back the output objects. diff --git a/sui_programmability/examples/nfts/sources/random_nft.move b/sui_programmability/examples/nfts/sources/random_nft.move index 90a0c66f7d5db..d8146762fac9a 100644 --- a/sui_programmability/examples/nfts/sources/random_nft.move +++ b/sui_programmability/examples/nfts/sources/random_nft.move @@ -28,14 +28,15 @@ module nfts::random_nft_airdrop { } struct MintingCapability has key { - id: UID + id: UID, } #[allow(unused_function)] fun init(ctx: &mut TxContext) { - transfer::transfer(MintingCapability { - id: object::new(ctx) - }, tx_context::sender(ctx)); + transfer::transfer( + MintingCapability { id: object::new(ctx) }, + tx_context::sender(ctx), + ); } public fun mint(_cap: &MintingCapability, n: u16, ctx: &mut TxContext): vector { @@ -64,10 +65,10 @@ module nfts::random_nft_airdrop { let is_bronze = (1 - is_gold) * (1 - is_silver); // probability of 60% let metal = is_gold * GOLD + is_silver * SILVER + is_bronze * BRONZE; - transfer::public_transfer(MetalNFT { - id: object::new(ctx), - metal, - }, tx_context::sender(ctx)); + transfer::public_transfer( + MetalNFT { id: object::new(ctx), metal, }, + tx_context::sender(ctx) + ); } // Implements "is v < w? where v <= v_max" using integer arithmetic. Returns 1 if true, 0 otherwise. @@ -91,26 +92,20 @@ module nfts::random_nft_airdrop { let v = random::generate_u8_in_range(&mut generator, 1, 100); if (v <= 60) { - transfer::public_transfer(MetalNFT { - id: object::new(ctx), - metal: BRONZE, - }, tx_context::sender(ctx)); - return - }; - - if (v <= 90) { - transfer::public_transfer(MetalNFT { - id: object::new(ctx), - metal: SILVER, - }, tx_context::sender(ctx)); - return - }; - - if (v <= 100) { - transfer::public_transfer(MetalNFT { - id: object::new(ctx), - metal: GOLD, - }, tx_context::sender(ctx)); + transfer::public_transfer( + MetalNFT { id: object::new(ctx), metal: BRONZE, }, + tx_context::sender(ctx), + ); + } else if (v <= 90) { + transfer::public_transfer( + MetalNFT { id: object::new(ctx), metal: SILVER, }, + tx_context::sender(ctx), + ); + } else if (v <= 100) { + transfer::public_transfer( + MetalNFT { id: object::new(ctx), metal: GOLD, }, + tx_context::sender(ctx), + ); }; } @@ -129,23 +124,20 @@ module nfts::random_nft_airdrop { let generator = new_generator(r, ctx); let v = random::generate_u8_in_range(&mut generator, 1, 100); - transfer::public_transfer(RandomnessNFT { - id: object::new(ctx), - value: v, - }, tx_context::sender(ctx)); + transfer::public_transfer( + RandomnessNFT { id: object::new(ctx), value: v, }, + tx_context::sender(ctx), + ); } public fun reveal_alternative2_step2(nft: RandomnessNFT, ctx: &mut TxContext): MetalNFT { let RandomnessNFT { id, value } = nft; delete(id); - let metal = BRONZE; - if (value <= 10) { - metal = GOLD; - }; - if (10 < value && value <= 40) { - metal = SILVER; - }; + let metal = + if (value <= 10) GOLD + else if (10 < value && value <= 40) SILVER + else BRONZE; MetalNFT { id: object::new(ctx), @@ -159,13 +151,9 @@ module nfts::random_nft_airdrop { } public fun metal_string(nft: &MetalNFT): string::String { - if (nft.metal == GOLD) { - return string::utf8(b"Gold") - }; - if (nft.metal == SILVER) { - return string::utf8(b"Silver") - }; - string::utf8(b"Bronze") + if (nft.metal == GOLD) string::utf8(b"Gold") + else if (nft.metal == SILVER) string::utf8(b"Silver") + else string::utf8(b"Bronze") } #[test_only] @@ -188,8 +176,7 @@ module nfts::random_nft_airdrop_tests { use sui::random; use sui::random::{Random, update_randomness_state_for_testing}; use sui::test_scenario::{ctx, take_from_sender, next_tx, return_to_sender}; - use nfts::random_nft_airdrop::{MintingCapability, MetalNFT}; - use nfts::random_nft_airdrop; + use nfts::random_nft_airdrop::{Self, MintingCapability, MetalNFT}; #[test] fun test_e2e() { @@ -206,7 +193,7 @@ module nfts::random_nft_airdrop_tests { &mut random_state, 0, x"1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F", - test_scenario::ctx(scenario) + test_scenario::ctx(scenario), ); test_scenario::next_tx(scenario, user1); @@ -221,11 +208,8 @@ module nfts::random_nft_airdrop_tests { let seen_bronze = false; let i = 0; while (i < 20) { - if (i % 2 == 1) { - random_nft_airdrop::reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); - } else { - random_nft_airdrop::reveal_alternative1(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); - }; + if (i % 2 == 1) random_nft_airdrop::reveal(vector::pop_back(&mut nfts), &random_state, ctx(scenario)) + else random_nft_airdrop::reveal_alternative1(vector::pop_back(&mut nfts), &random_state, ctx(scenario)); next_tx(scenario, user1); let nft = take_from_sender(scenario); let metal = random_nft_airdrop::metal_string(&nft); From c2bd05dce510ab1e3d44b01420ad455ba18f71c9 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Thu, 21 Mar 2024 14:18:39 +0200 Subject: [PATCH 15/17] Move 2024 updates --- .../docs/sui-framework/random.md | 632 +++++++++++++++++- .../sui-framework/sources/random.move | 12 +- .../sui-framework/tests/random_tests.move | 100 +-- 3 files changed, 684 insertions(+), 60 deletions(-) diff --git a/crates/sui-framework/docs/sui-framework/random.md b/crates/sui-framework/docs/sui-framework/random.md index 451f5d306bd9d..a00c385f2b753 100644 --- a/crates/sui-framework/docs/sui-framework/random.md +++ b/crates/sui-framework/docs/sui-framework/random.md @@ -3,18 +3,42 @@ title: Module `0x2::random` --- +This module provides functionality for generating secure randomness. - [Resource `Random`](#0x2_random_Random) - [Struct `RandomInner`](#0x2_random_RandomInner) +- [Struct `RandomGenerator`](#0x2_random_RandomGenerator) - [Constants](#@Constants_0) - [Function `create`](#0x2_random_create) - [Function `load_inner_mut`](#0x2_random_load_inner_mut) - [Function `load_inner`](#0x2_random_load_inner) - [Function `update_randomness_state`](#0x2_random_update_randomness_state) - - -
use 0x1::vector;
+-  [Function `new_generator`](#0x2_random_new_generator)
+-  [Function `derive_next_block`](#0x2_random_derive_next_block)
+-  [Function `fill_buffer`](#0x2_random_fill_buffer)
+-  [Function `generate_bytes`](#0x2_random_generate_bytes)
+-  [Function `u256_from_bytes`](#0x2_random_u256_from_bytes)
+-  [Function `generate_u256`](#0x2_random_generate_u256)
+-  [Function `generate_u128`](#0x2_random_generate_u128)
+-  [Function `generate_u64`](#0x2_random_generate_u64)
+-  [Function `generate_u32`](#0x2_random_generate_u32)
+-  [Function `generate_u16`](#0x2_random_generate_u16)
+-  [Function `generate_u8`](#0x2_random_generate_u8)
+-  [Function `generate_bool`](#0x2_random_generate_bool)
+-  [Function `u128_in_range`](#0x2_random_u128_in_range)
+-  [Function `generate_u128_in_range`](#0x2_random_generate_u128_in_range)
+-  [Function `generate_u64_in_range`](#0x2_random_generate_u64_in_range)
+-  [Function `generate_u32_in_range`](#0x2_random_generate_u32_in_range)
+-  [Function `generate_u16_in_range`](#0x2_random_generate_u16_in_range)
+-  [Function `generate_u8_in_range`](#0x2_random_generate_u8_in_range)
+-  [Function `shuffle`](#0x2_random_shuffle)
+
+
+
use 0x1::bcs;
+use 0x1::vector;
+use 0x2::address;
+use 0x2::hmac;
 use 0x2::object;
 use 0x2::transfer;
 use 0x2::tx_context;
@@ -101,6 +125,46 @@ The actual state is stored in a versioned inner field.
 
 
 
+
+
+
+
+## Struct `RandomGenerator`
+
+Unique randomness generator, derived from the global randomness.
+
+
+
struct RandomGenerator has drop
+
+ + + +
+Fields + + +
+
+seed: vector<u8> +
+
+ +
+
+counter: u16 +
+
+ +
+
+buffer: vector<u8> +
+
+ +
+
+ +
@@ -135,6 +199,15 @@ The actual state is stored in a versioned inner field. + + + + +
const EInvalidLength: u64 = 4;
+
+ + + @@ -144,6 +217,33 @@ The actual state is stored in a versioned inner field. + + + + +
const EInvalidRange: u64 = 3;
+
+ + + + + + + +
const RAND_OUTPUT_LEN: u16 = 32;
+
+ + + + + + + +
const U16_MAX: u64 = 65535;
+
+ + + ## Function `create` @@ -280,7 +380,7 @@ transaction. let epoch = tx_context::epoch(ctx); let inner = load_inner_mut(self); if (inner.randomness_round == 0 && inner.epoch == 0 && - vector::is_empty(&inner.random_bytes)) { + vector::is_empty(&inner.random_bytes)) { // First update should be for round zero. assert!(new_round == 0, EInvalidRandomnessUpdate); } else { @@ -302,4 +402,528 @@ transaction. + + + + +## Function `new_generator` + +Create a generator. Can be used to derive up to MAX_U16 * 32 random bytes. + + +
public fun new_generator(r: &random::Random, ctx: &mut tx_context::TxContext): random::RandomGenerator
+
+ + + +
+Implementation + + +
public fun new_generator(r: &Random, ctx: &mut TxContext): RandomGenerator {
+    let inner = load_inner(r);
+    let seed = hmac_sha3_256(
+        &inner.random_bytes,
+        &to_bytes(fresh_object_address(ctx))
+    );
+    RandomGenerator { seed, counter: 0, buffer: vector[] }
+}
+
+ + + +
+ + + +## Function `derive_next_block` + + + +
fun derive_next_block(g: &mut random::RandomGenerator): vector<u8>
+
+ + + +
+Implementation + + +
fun derive_next_block(g: &mut RandomGenerator): vector<u8> {
+    g.counter = g.counter + 1;
+    hmac_sha3_256(&g.seed, &bcs::to_bytes(&g.counter))
+}
+
+ + + +
+ + + +## Function `fill_buffer` + + + +
fun fill_buffer(g: &mut random::RandomGenerator)
+
+ + + +
+Implementation + + +
fun fill_buffer(g: &mut RandomGenerator) {
+    let next_block = derive_next_block(g);
+    vector::append(&mut g.buffer, next_block);
+}
+
+ + + +
+ + + +## Function `generate_bytes` + +Generate n random bytes. + + +
public fun generate_bytes(g: &mut random::RandomGenerator, num_of_bytes: u16): vector<u8>
+
+ + + +
+Implementation + + +
public fun generate_bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector<u8> {
+    let mut result = vector[];
+    // Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer.
+    let mut num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN;
+    while (num_of_blocks > 0) {
+        vector::append(&mut result, derive_next_block(g));
+        num_of_blocks = num_of_blocks - 1;
+    };
+    // Fill the generator's buffer if needed.
+    let num_of_bytes = (num_of_bytes as u64);
+    if (vector::length(&g.buffer) < (num_of_bytes - vector::length(&result))) {
+        fill_buffer(g);
+    };
+    // Take remaining bytes from the generator's buffer.
+    while (vector::length(&result) < num_of_bytes) {
+        vector::push_back(&mut result, vector::pop_back(&mut g.buffer));
+    };
+    result
+}
+
+ + + +
+ + + +## Function `u256_from_bytes` + + + +
fun u256_from_bytes(g: &mut random::RandomGenerator, num_of_bytes: u8): u256
+
+ + + +
+Implementation + + +
fun u256_from_bytes(g: &mut RandomGenerator, num_of_bytes: u8): u256 {
+    if (vector::length(&g.buffer) < (num_of_bytes as u64)) {
+        fill_buffer(g);
+    };
+    let mut result: u256 = 0;
+    let mut i = 0;
+    while (i < num_of_bytes) {
+        let byte = vector::pop_back(&mut g.buffer);
+        result = (result << 8) + (byte as u256);
+        i = i + 1;
+    };
+    result
+}
+
+ + + +
+ + + +## Function `generate_u256` + +Generate a u256. + + +
public fun generate_u256(g: &mut random::RandomGenerator): u256
+
+ + + +
+Implementation + + +
public fun generate_u256(g: &mut RandomGenerator): u256 {
+    u256_from_bytes(g, 32)
+}
+
+ + + +
+ + + +## Function `generate_u128` + +Generate a u128. + + +
public fun generate_u128(g: &mut random::RandomGenerator): u128
+
+ + + +
+Implementation + + +
public fun generate_u128(g: &mut RandomGenerator): u128 {
+    (u256_from_bytes(g, 16) as u128)
+}
+
+ + + +
+ + + +## Function `generate_u64` + +Generate a u64. + + +
public fun generate_u64(g: &mut random::RandomGenerator): u64
+
+ + + +
+Implementation + + +
public fun generate_u64(g: &mut RandomGenerator): u64 {
+    (u256_from_bytes(g, 8) as u64)
+}
+
+ + + +
+ + + +## Function `generate_u32` + +Generate a u32. + + +
public fun generate_u32(g: &mut random::RandomGenerator): u32
+
+ + + +
+Implementation + + +
public fun generate_u32(g: &mut RandomGenerator): u32 {
+    (u256_from_bytes(g, 4) as u32)
+}
+
+ + + +
+ + + +## Function `generate_u16` + +Generate a u16. + + +
public fun generate_u16(g: &mut random::RandomGenerator): u16
+
+ + + +
+Implementation + + +
public fun generate_u16(g: &mut RandomGenerator): u16 {
+    (u256_from_bytes(g, 2) as u16)
+}
+
+ + + +
+ + + +## Function `generate_u8` + +Generate a u8. + + +
public fun generate_u8(g: &mut random::RandomGenerator): u8
+
+ + + +
+Implementation + + +
public fun generate_u8(g: &mut RandomGenerator): u8 {
+    (u256_from_bytes(g, 1) as u8)
+}
+
+ + + +
+ + + +## Function `generate_bool` + +Generate a boolean. + + +
public fun generate_bool(g: &mut random::RandomGenerator): bool
+
+ + + +
+Implementation + + +
public fun generate_bool(g: &mut RandomGenerator): bool {
+    (u256_from_bytes(g, 1) & 1) == 1
+}
+
+ + + +
+ + + +## Function `u128_in_range` + + + +
fun u128_in_range(g: &mut random::RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128
+
+ + + +
+Implementation + + +
fun u128_in_range(g: &mut RandomGenerator, min: u128, max: u128, num_of_bytes: u8): u128 {
+    assert!(min <= max, EInvalidRange);
+    if (min == max) {
+        return min
+    };
+    // Pick a random number in [0, max - min] by generating a random number that is larger than max-min, and taking
+    // the modulo of the random number by the range size. Then add the min to the result to get a number in
+    // [min, max].
+    let range_size = ((max - min) as u256) + 1;
+    let rand = u256_from_bytes(g, num_of_bytes);
+    min + ((rand % range_size) as u128)
+}
+
+ + + +
+ + + +## Function `generate_u128_in_range` + +Generate a random u128 in [min, max] (with a bias of 2^{-64}). + + +
public fun generate_u128_in_range(g: &mut random::RandomGenerator, min: u128, max: u128): u128
+
+ + + +
+Implementation + + +
public fun generate_u128_in_range(g: &mut RandomGenerator, min: u128, max: u128): u128 {
+    u128_in_range(g, min, max, 24)
+}
+
+ + + +
+ + + +## Function `generate_u64_in_range` + + + +
public fun generate_u64_in_range(g: &mut random::RandomGenerator, min: u64, max: u64): u64
+
+ + + +
+Implementation + + +
public fun generate_u64_in_range(g: &mut RandomGenerator, min: u64, max: u64): u64 {
+    (u128_in_range(g, (min as u128), (max as u128), 16) as u64)
+}
+
+ + + +
+ + + +## Function `generate_u32_in_range` + +Generate a random u32 in [min, max] (with a bias of 2^{-64}). + + +
public fun generate_u32_in_range(g: &mut random::RandomGenerator, min: u32, max: u32): u32
+
+ + + +
+Implementation + + +
public fun generate_u32_in_range(g: &mut RandomGenerator, min: u32, max: u32): u32 {
+    (u128_in_range(g, (min as u128), (max as u128), 12) as u32)
+}
+
+ + + +
+ + + +## Function `generate_u16_in_range` + +Generate a random u16 in [min, max] (with a bias of 2^{-64}). + + +
public fun generate_u16_in_range(g: &mut random::RandomGenerator, min: u16, max: u16): u16
+
+ + + +
+Implementation + + +
public fun generate_u16_in_range(g: &mut RandomGenerator, min: u16, max: u16): u16 {
+    (u128_in_range(g, (min as u128), (max as u128), 10) as u16)
+}
+
+ + + +
+ + + +## Function `generate_u8_in_range` + +Generate a random u8 in [min, max] (with a bias of 2^{-64}). + + +
public fun generate_u8_in_range(g: &mut random::RandomGenerator, min: u8, max: u8): u8
+
+ + + +
+Implementation + + +
public fun generate_u8_in_range(g: &mut RandomGenerator, min: u8, max: u8): u8 {
+    (u128_in_range(g, (min as u128), (max as u128), 9) as u8)
+}
+
+ + + +
+ + + +## Function `shuffle` + +Shuffle a vector using the random generator (Fisher–Yates/Knuth shuffle). + + +
public fun shuffle<T>(g: &mut random::RandomGenerator, v: &mut vector<T>)
+
+ + + +
+Implementation + + +
public fun shuffle<T>(g: &mut RandomGenerator, v: &mut vector<T>) {
+    let n = vector::length(v);
+    if (n == 0) {
+        return
+    };
+    assert!(n <= U16_MAX, EInvalidLength);
+    let n = (n as u16);
+    let mut i: u16 = 0;
+    let end = n - 1;
+    while (i < end) {
+        let j = generate_u16_in_range(g, i, end);
+        vector::swap(v, (i as u64), (j as u64));
+        i = i + 1;
+    };
+}
+
+ + +
diff --git a/crates/sui-framework/packages/sui-framework/sources/random.move b/crates/sui-framework/packages/sui-framework/sources/random.move index bc5b37478c90c..e8f7b1313d0f0 100644 --- a/crates/sui-framework/packages/sui-framework/sources/random.move +++ b/crates/sui-framework/packages/sui-framework/sources/random.move @@ -138,7 +138,7 @@ module sui::random { /// Unique randomness generator, derived from the global randomness. - struct RandomGenerator has drop { + public struct RandomGenerator has drop { seed: vector, counter: u16, buffer: vector, @@ -168,9 +168,9 @@ module sui::random { /// Generate n random bytes. public fun generate_bytes(g: &mut RandomGenerator, num_of_bytes: u16): vector { - let result = vector[]; + let mut result = vector[]; // Append RAND_OUTPUT_LEN size buffers directly without going through the generator's buffer. - let num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN; + let mut num_of_blocks = num_of_bytes / RAND_OUTPUT_LEN; while (num_of_blocks > 0) { vector::append(&mut result, derive_next_block(g)); num_of_blocks = num_of_blocks - 1; @@ -194,8 +194,8 @@ module sui::random { if (vector::length(&g.buffer) < (num_of_bytes as u64)) { fill_buffer(g); }; - let result: u256 = 0; - let i = 0; + let mut result: u256 = 0; + let mut i = 0; while (i < num_of_bytes) { let byte = vector::pop_back(&mut g.buffer); result = (result << 8) + (byte as u256); @@ -289,7 +289,7 @@ module sui::random { }; assert!(n <= U16_MAX, EInvalidLength); let n = (n as u16); - let i: u16 = 0; + let mut i: u16 = 0; let end = n - 1; while (i < end) { let j = generate_u16_in_range(g, i, end); diff --git a/crates/sui-framework/packages/sui-framework/tests/random_tests.move b/crates/sui-framework/packages/sui-framework/tests/random_tests.move index 882c75370854a..393fcda3a69bb 100644 --- a/crates/sui-framework/packages/sui-framework/tests/random_tests.move +++ b/crates/sui-framework/packages/sui-framework/tests/random_tests.move @@ -22,13 +22,13 @@ module sui::random_tests { #[test] fun random_test_basic_flow() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -36,7 +36,7 @@ module sui::random_tests { test_scenario::ctx(scenario), ); - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); let _o256 = generate_u256(&mut gen); test_scenario::return_shared(random_state); @@ -49,15 +49,15 @@ module sui::random_tests { let global_random2 = x"2F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1F1A"; // Create Random - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::end(scenario_val); // Set random to global_random1 - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -70,9 +70,9 @@ module sui::random_tests { test_scenario::end(scenario_val); // Set random again to global_random1 - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 1, @@ -85,9 +85,9 @@ module sui::random_tests { test_scenario::end(scenario_val); // Set random to global_random2 - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 2, @@ -109,13 +109,13 @@ module sui::random_tests { #[test] fun random_tests_regression() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -124,7 +124,7 @@ module sui::random_tests { ); // Regression (not critical for security, but still an indication that something is wrong). - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); let o256 = generate_u256(&mut gen); assert!(o256 == 85985798878417437391783029796051418802193098452099584085821130568389745847195, 0); let o128 = generate_u128(&mut gen); @@ -158,13 +158,13 @@ module sui::random_tests { #[test] fun test_bytes() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -172,7 +172,7 @@ module sui::random_tests { test_scenario::ctx(scenario), ); - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); // Check the output size & internal generator state assert!(vector::is_empty(generator_buffer(&gen)), 0); @@ -203,7 +203,7 @@ module sui::random_tests { // Sanity check that the output is not all zeros. let output = generate_bytes(&mut gen, 10); - let i = 0; + let mut i = 0; loop { // should break before the overflow if (*vector::borrow(&output, i) != 0u8) break; @@ -226,13 +226,13 @@ module sui::random_tests { #[test] fun random_tests_uints() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -241,7 +241,7 @@ module sui::random_tests { ); // u256 - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); assert!(vector::is_empty(generator_buffer(&gen)), 0); let output1 = generate_u256(&mut gen); assert!(generator_counter(&gen) == 1, 0); @@ -255,7 +255,7 @@ module sui::random_tests { assert!(generator_counter(&gen) == 4, 0); assert!(vector::length(generator_buffer(&gen)) == 31, 0); // Check that we indeed generate all bytes as random - let i = 0; + let mut i = 0; while (i < 32) { let x = generate_u256(&mut gen); let x_bytes = bcs::to_bytes(&x); @@ -276,7 +276,7 @@ module sui::random_tests { let _output4 = generate_u128(&mut gen); assert!(generator_counter(&gen) == 2, 0); assert!(vector::length(generator_buffer(&gen)) == 15, 0); - let i = 0; + let mut i = 0; while (i < 16) { let x = generate_u128(&mut gen); let x_bytes = bcs::to_bytes(&x); @@ -297,7 +297,7 @@ module sui::random_tests { let _output4 = generate_u64(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 7, 0); - let i = 0; + let mut i = 0; while (i < 8) { let x = generate_u64(&mut gen); let x_bytes = bcs::to_bytes(&x); @@ -318,7 +318,7 @@ module sui::random_tests { let _output4 = generate_u32(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 19, 0); - let i = 0; + let mut i = 0; while (i < 4) { let x = generate_u32(&mut gen); let x_bytes = bcs::to_bytes(&x); @@ -339,7 +339,7 @@ module sui::random_tests { let _output4 = generate_u16(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 25, 0); - let i = 0; + let mut i = 0; while (i < 2) { let x = generate_u16(&mut gen); let x_bytes = bcs::to_bytes(&x); @@ -379,7 +379,7 @@ module sui::random_tests { let _output4 = generate_u8(&mut gen); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 13, 0); - let saw_false = false; + let mut saw_false = false; loop { let x = generate_bool(&mut gen); saw_false = saw_false || !x; @@ -392,13 +392,13 @@ module sui::random_tests { #[test] fun test_shuffle() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -406,11 +406,11 @@ module sui::random_tests { test_scenario::ctx(scenario), ); - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); - let v: vector = vector[0, 1, 2, 3, 4]; + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut v: vector = vector[0, 1, 2, 3, 4]; shuffle(&mut gen, &mut v); assert!(vector::length(&v) == 5, 0); - let i: u16 = 0; + let mut i: u16 = 0; while (i < 5) { assert!(vector::contains(&v, &i), 0); i = i + 1; @@ -426,11 +426,11 @@ module sui::random_tests { if ((*vector::borrow(&v, 0) == 2u16)) break; }; - let v: vector = vector[]; + let mut v: vector = vector[]; shuffle(&mut gen, &mut v); assert!(vector::length(&v) == 0, 0); - let v: vector = vector[321]; + let mut v: vector = vector[321]; shuffle(&mut gen, &mut v); assert!(vector::length(&v) == 1, 0); assert!(*vector::borrow(&v, 0) == 321u32, 0); @@ -441,13 +441,13 @@ module sui::random_tests { #[test] fun random_tests_in_range() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -456,7 +456,7 @@ module sui::random_tests { ); // generate_u128_in_range - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); let output1 = generate_u128_in_range(&mut gen, 11, 123454321); assert!(generator_counter(&gen) == 1, 0); assert!(vector::length(generator_buffer(&gen)) == 8, 0); @@ -468,7 +468,7 @@ module sui::random_tests { assert!((output == 123454321) || (output == 123454321 + 1), 0); // test the edge case of u128_in_range (covers also the other in_range functions) let _output = generate_u128_in_range(&mut gen, 0, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF); - let i = 0; + let mut i = 0; while (i < 50) { let min = generate_u128(&mut gen); let max = min + (generate_u64(&mut gen) as u128); @@ -489,7 +489,7 @@ module sui::random_tests { assert!(output1 != output2, 0); let output = generate_u64_in_range(&mut gen, 123454321, 123454321 + 1); assert!((output == 123454321) || (output == 123454321 + 1), 0); - let i = 0; + let mut i = 0; while (i < 50) { let min = generate_u64(&mut gen); let max = min + (generate_u32(&mut gen) as u64); @@ -510,7 +510,7 @@ module sui::random_tests { assert!(output1 != output2, 0); let output = generate_u32_in_range(&mut gen, 123454321, 123454321 + 1); assert!((output == 123454321) || (output == 123454321 + 1), 0); - let i = 0; + let mut i = 0; while (i < 50) { let min = generate_u32(&mut gen); let max = min + (generate_u16(&mut gen) as u32); @@ -531,7 +531,7 @@ module sui::random_tests { assert!(output1 != output2, 0); let output = generate_u16_in_range(&mut gen, 12345, 12345 + 1); assert!((output == 12345) || (output == 12345 + 1), 0); - let i = 0; + let mut i = 0; while (i < 50) { let min = generate_u16(&mut gen); let max = min + (generate_u8(&mut gen) as u16); @@ -552,7 +552,7 @@ module sui::random_tests { assert!(output1 != output2, 0); let output = generate_u8_in_range(&mut gen, 123, 123 + 1); assert!((output == 123) || (output == 123 + 1), 0); - let i = 0; + let mut i = 0; while (i < 50) { let (min, max) = (generate_u8(&mut gen), generate_u8(&mut gen)); let (min, max) = if (min < max) (min, max) else (max, min); @@ -573,13 +573,13 @@ module sui::random_tests { #[test] #[expected_failure(abort_code = random::EInvalidRange)] fun random_tests_invalid_range() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -587,7 +587,7 @@ module sui::random_tests { test_scenario::ctx(scenario), ); - let gen = new_generator(&random_state, test_scenario::ctx(scenario)); + let mut gen = new_generator(&random_state, test_scenario::ctx(scenario)); let _output = generate_u128_in_range(&mut gen, 511, 500); test_scenario::return_shared(random_state); @@ -596,13 +596,13 @@ module sui::random_tests { #[test] fun random_tests_update_after_epoch_change() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -632,13 +632,13 @@ module sui::random_tests { #[test] #[expected_failure(abort_code = random::EInvalidRandomnessUpdate)] fun random_tests_duplicate() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, @@ -659,13 +659,13 @@ module sui::random_tests { #[test] #[expected_failure(abort_code = random::EInvalidRandomnessUpdate)] fun random_tests_out_of_order() { - let scenario_val = test_scenario::begin(@0x0); + let mut scenario_val = test_scenario::begin(@0x0); let scenario = &mut scenario_val; random::create_for_testing(test_scenario::ctx(scenario)); test_scenario::next_tx(scenario, @0x0); - let random_state = test_scenario::take_shared(scenario); + let mut random_state = test_scenario::take_shared(scenario); update_randomness_state_for_testing( &mut random_state, 0, From 42ab9ffa0dd35625123e45c390d497aeeefca830 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Thu, 21 Mar 2024 14:24:56 +0200 Subject: [PATCH 16/17] remove files --- .../dependencies/sui-framework/hmac.md | 35 ------------------- .../dependencies/sui-framework/hmac.md | 35 ------------------- 2 files changed, 70 deletions(-) delete mode 100644 crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md delete mode 100644 crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md diff --git a/crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md b/crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md deleted file mode 100644 index d9cefc6191cd4..0000000000000 --- a/crates/sui-framework/docs/deepbook/dependencies/sui-framework/hmac.md +++ /dev/null @@ -1,35 +0,0 @@ - - - -# Module `0x2::hmac` - - - -- [Function `hmac_sha3_256`](#0x2_hmac_hmac_sha3_256) - - -
- - - - - -## Function `hmac_sha3_256` - - - -
public fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>
-
- - - -
-Implementation - - -
public native fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>;
-
- - - -
diff --git a/crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md b/crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md deleted file mode 100644 index d9cefc6191cd4..0000000000000 --- a/crates/sui-framework/docs/sui-system/dependencies/sui-framework/hmac.md +++ /dev/null @@ -1,35 +0,0 @@ - - - -# Module `0x2::hmac` - - - -- [Function `hmac_sha3_256`](#0x2_hmac_hmac_sha3_256) - - -
- - - - - -## Function `hmac_sha3_256` - - - -
public fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>
-
- - - -
-Implementation - - -
public native fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>;
-
- - - -
From 4b31e2f73eca1be1d76e8650223f9a6c32964594 Mon Sep 17 00:00:00 2001 From: benr-ml Date: Thu, 21 Mar 2024 16:36:06 +0200 Subject: [PATCH 17/17] update --- .../sui-framework/docs/sui-framework/hmac.md | 4 +-- ..._populated_genesis_snapshot_matches-2.snap | 28 +++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/crates/sui-framework/docs/sui-framework/hmac.md b/crates/sui-framework/docs/sui-framework/hmac.md index cf5dbeea4418e..1bc40a4c1feb2 100644 --- a/crates/sui-framework/docs/sui-framework/hmac.md +++ b/crates/sui-framework/docs/sui-framework/hmac.md @@ -21,7 +21,7 @@ title: Module `0x2::hmac` Returns the 32 bytes digest of HMAC-SHA3-256(key, msg). -
public fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>
+
public fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>
 
@@ -30,7 +30,7 @@ Returns the 32 bytes digest of HMAC-SHA3-256(key, msg). Implementation -
public native fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>;
+
public native fun hmac_sha3_256(key: &vector<u8>, msg: &vector<u8>): vector<u8>;
 
diff --git a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap index 8a060d534aa15..a2c951bfcd8ae 100644 --- a/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap +++ b/crates/sui-swarm-config/tests/snapshots/snapshot_tests__populated_genesis_snapshot_matches-2.snap @@ -240,13 +240,13 @@ validators: next_epoch_worker_address: ~ extra_fields: id: - id: "0xbd30c27f7e1bba23be03029496b0153427b2a6b62342e2144ddb3f7e3b3bfcac" + id: "0x9ba102555aefff34898f1240c70527f528200003ae841cb57cd7d40a7f9a986e" size: 0 voting_power: 10000 - operation_cap_id: "0xccf67fe04c6a4cca7de3f13504b00662228596ada99e17c1e9e19996b86964f6" + operation_cap_id: "0xc2a525d6e84f9f9082e2a50f6f6e19e1cef582c49ef9e9952d857c2a52c95615" gas_price: 1000 staking_pool: - id: "0x5b1db67d8a069bacc26bd993979f173fbb3a0ce6d8ae90c2a48cae492b06b25c" + id: "0xbaa5dc0e24e9a7f976b79b04353192d3c126b10ea60a89d31c8a50a847eda95c" activation_epoch: 0 deactivation_epoch: ~ sui_balance: 20000000000000000 @@ -254,14 +254,14 @@ validators: value: 0 pool_token_balance: 20000000000000000 exchange_rates: - id: "0x24a627d9d21be7bb10d0b4508c74337db3bef1c862b7306c8c5b85b100a6138a" + id: "0xb402891e5af76ca819b2c8782b887b94770f86727a626c067b7bf99cd78d7528" size: 1 pending_stake: 0 pending_total_sui_withdraw: 0 pending_pool_token_withdraw: 0 extra_fields: id: - id: "0x66b1868391655b9471481dc429d89eca0f088ad2b702a5459af3049f5371a7cf" + id: "0xf4380b30a25b68991cffec25f805b2202d956aff5485fcf70fb14e6e41935130" size: 0 commission_rate: 200 next_epoch_stake: 20000000000000000 @@ -269,27 +269,27 @@ validators: next_epoch_commission_rate: 200 extra_fields: id: - id: "0xded819d256bbd3352632e83c09c20c21a61c9e7b42135092b5212c0b61b3b19d" + id: "0x1f623545b36c41227e7352235dc78836d2287e3ab652d4d6bab41d6bff830637" size: 0 pending_active_validators: contents: - id: "0x82a3a8c972ff446b20086ae5b55f37ac2a2781ec1042fb3f33b0eec729df01c4" + id: "0x731c6458e1e53b261efd1a198d9be78c04285c050daa9822129e09a10d762d5c" size: 0 pending_removals: [] staking_pool_mappings: - id: "0x2440bf3ff18377a98b6b0594b41393c921c5ae83e3807724bda348c74c433d22" + id: "0xe8f3d3aa0861f6f6d11678896d53090e08efe24a21a5b477e64884cf8396c150" size: 1 inactive_validators: - id: "0xa0eeb75a32a4a97d7acf1363df47fc1e1cd9252884d75026e4465e9cd541bd88" + id: "0xde531fb6ff92e3b5791614931e7742b63688706f2070d2829ba3f1e96ea6d907" size: 0 validator_candidates: - id: "0xe594528b39b4adf84cd091779c14dc8383d28fcee242fa518de7080e31dc5409" + id: "0xc4556750e835f2b1813e322eec5ec85ddcb9b4612c424f92c2a8d9c620004568" size: 0 at_risk_validators: contents: [] extra_fields: id: - id: "0x8dcf49f8aba31b5e5600f85766453ee45b4407ca326694dd8ed089a6c063c371" + id: "0x24d3a110b3a2960b18cf12ee4529bd1211e80732a5289c3777257287f4128d94" size: 0 storage_fund: total_object_storage_rebates: @@ -306,7 +306,7 @@ parameters: validator_low_stake_grace_period: 7 extra_fields: id: - id: "0x87d00e604247bbebfb8fa93c76d871feaa0b3aa716526b9d96444ea1c361a8d1" + id: "0x292f323ab679f1a1d9674963bdaeda9a16bfab6cd86314bc95a7f2e618d5aaed" size: 0 reference_gas_price: 1000 validator_report_records: @@ -320,7 +320,7 @@ stake_subsidy: stake_subsidy_decrease_rate: 1000 extra_fields: id: - id: "0xb1805990a7888ccf1cad230790b57a778689de0b44268c72e13abaddbf280dcb" + id: "0x9016457565abef8804500f298fa8b570fc2703b1e48962a73ebf2c2327b371df" size: 0 safe_mode: false safe_mode_storage_rewards: @@ -332,6 +332,6 @@ safe_mode_non_refundable_storage_fee: 0 epoch_start_timestamp_ms: 10 extra_fields: id: - id: "0xe77af8cb9a1b07cb495fc3c9991226e5e5e620b7878e670cb20362c20df0d498" + id: "0xdb4cb00356f5b5e878275141fe6bdfe984633103f766bf60d7f419873581bd86" size: 0