From e0372fd50e74a4923c8de6d673e365853ecd90d1 Mon Sep 17 00:00:00 2001 From: Ryzen XP Date: Wed, 26 Feb 2025 23:07:57 +0530 Subject: [PATCH] feat: Implement Hashing --- onchain/src/contracts/dewordle.cairo | 32 ++++++++--- onchain/src/interfaces.cairo | 3 +- onchain/src/utils.cairo | 50 +++++++++++------ onchain/tests/test_dewordle.cairo | 32 ++++++++++- onchain/tests/test_utils.cairo | 84 +++++++++++++++++++++++----- 5 files changed, 157 insertions(+), 44 deletions(-) diff --git a/onchain/src/contracts/dewordle.cairo b/onchain/src/contracts/dewordle.cairo index 25fd18f..6194586 100644 --- a/onchain/src/contracts/dewordle.cairo +++ b/onchain/src/contracts/dewordle.cairo @@ -3,13 +3,13 @@ pub mod DeWordle { use dewordle::constants::LetterState; use dewordle::interfaces::{IDeWordle, PlayerStat, DailyPlayerStat}; - use dewordle::utils::{compare_word, is_correct_word}; + use dewordle::utils::{compare_word, is_correct_hashed_word, hash_word, hash_letter}; use openzeppelin::access::accesscontrol::{AccessControlComponent}; use openzeppelin::access::ownable::OwnableComponent; use openzeppelin::introspection::src5::SRC5Component; use starknet::storage::{ - StoragePointerReadAccess, StoragePointerWriteAccess, Map, Vec, MutableVecTrait, + StoragePointerReadAccess, StoragePointerWriteAccess, Map, Vec, VecTrait, MutableVecTrait, }; use starknet::{ContractAddress}; @@ -37,7 +37,7 @@ pub mod DeWordle { #[storage] struct Storage { - word_of_the_day: ByteArray, //TODO: hash word + word_of_the_day: felt252, //TODO: hash word letters_in_word: Vec, //TODO: hash letters word_len: u8, player_stat: Map, @@ -73,21 +73,36 @@ pub mod DeWordle { fn set_daily_word(ref self: ContractState, word: ByteArray) { self.accesscontrol.assert_only_role(ADMIN_ROLE); let word_len = word.len(); + let hash_word = hash_word(word.clone()); + self.word_of_the_day.write(hash_word); let mut i = 0; while (i < word_len) { - self.letters_in_word.append().write(word[i].into()); + let hashed_letter = hash_letter(word[i].into()); + self.letters_in_word.append().write(hashed_letter); i += 1; }; - self.word_of_the_day.write(word); + self.word_len.write(word_len.try_into().unwrap()); } - fn get_daily_word(self: @ContractState) -> ByteArray { + fn get_daily_word(self: @ContractState) -> felt252 { self.accesscontrol.assert_only_role(ADMIN_ROLE); self.word_of_the_day.read() } + fn get_daily_letters(self: @ContractState) -> Array { + self.accesscontrol.assert_only_role(ADMIN_ROLE); + let mut letter_arr = array![]; + for i in 0 + ..self + .letters_in_word + .len() { + letter_arr.append(self.letters_in_word.at(i).read()); + }; + letter_arr + } + fn get_player_daily_stat(self: @ContractState, player: ContractAddress) -> DailyPlayerStat { let daily_stat = self.daily_player_stat.read(player); @@ -119,7 +134,8 @@ pub mod DeWordle { let daily_stat = self.daily_player_stat.read(caller); assert(!daily_stat.has_won, 'Player has already won'); assert(daily_stat.attempt_remaining > 0, 'Player has exhausted attempts'); - if is_correct_word(self.get_daily_word(), guessed_word.clone()) { + let hash_guessed_word = hash_word(guessed_word.clone()); + if is_correct_hashed_word(self.get_daily_word(), hash_guessed_word) { let new_daily_stat = DailyPlayerStat { player: caller, attempt_remaining: daily_stat.attempt_remaining - 1, @@ -136,7 +152,7 @@ pub mod DeWordle { won_at_attempt: 0, }; self.daily_player_stat.write(caller, new_daily_stat); - Option::Some(compare_word(self.get_daily_word(), guessed_word.clone())) + Option::Some(compare_word(self.get_daily_letters(), guessed_word.clone())) } } } diff --git a/onchain/src/interfaces.cairo b/onchain/src/interfaces.cairo index 711403a..95dba44 100644 --- a/onchain/src/interfaces.cairo +++ b/onchain/src/interfaces.cairo @@ -4,7 +4,8 @@ use starknet::ContractAddress; #[starknet::interface] pub trait IDeWordle { fn set_daily_word(ref self: TContractState, word: ByteArray); - fn get_daily_word(self: @TContractState) -> ByteArray; + fn get_daily_word(self: @TContractState) -> felt252; + fn get_daily_letters(self: @TContractState) -> Array; fn get_player_daily_stat(self: @TContractState, player: ContractAddress) -> DailyPlayerStat; fn play(ref self: TContractState); diff --git a/onchain/src/utils.cairo b/onchain/src/utils.cairo index 5476854..8662f7e 100644 --- a/onchain/src/utils.cairo +++ b/onchain/src/utils.cairo @@ -1,22 +1,23 @@ +use core::pedersen::{pedersen}; use dewordle::constants::LetterState; -pub fn compare_word(word: ByteArray, guessed_word: ByteArray) -> Span { +pub fn compare_word(letters: Array, guessed_word: ByteArray) -> Span { let guessed_word_len = guessed_word.len(); - assert(guessed_word_len == word.len(), 'Length does not match'); + assert(guessed_word_len == letters.len(), 'Length does not match'); - // Initialize tracking arrays + // Initialize tracking arrays let mut i = 0; let mut word_states = array![]; // Final letter states let mut temp_states = array![]; // Temporary states to track exact matches let mut letter_count_list = array![]; // To track letter frequency in the target word - // Count occurrences of each letter in the daily word + // Count occurrences of each letter in the daily word while (i < guessed_word_len) { let mut count: u32 = 0; let mut j = 0; while (j < guessed_word_len) { - if (word[i] == word[j]) { + if is_correct_hashed_word(*letters.at(i), *letters.at(j)) { count += 1; // Count occurrences of the letter } j += 1; @@ -27,41 +28,37 @@ pub fn compare_word(word: ByteArray, guessed_word: ByteArray) -> Span Span bool { - guessed_word == correct_word +pub fn is_correct_hashed_word(hashed_word: felt252, hashed_guess: felt252) -> bool { + hashed_word == hashed_guess +} + +pub fn hash_word(word: ByteArray) -> felt252 { + let mut hash_accumulator = 0; + let word_len = word.len(); + + let mut i = 0; + while i < word_len { + hash_accumulator = pedersen(hash_accumulator, word[i].into()); + i += 1; + }; + + hash_accumulator +} + +pub fn hash_letter(letter: felt252) -> felt252 { + pedersen(letter, 0) } diff --git a/onchain/tests/test_dewordle.cairo b/onchain/tests/test_dewordle.cairo index 00e9c6c..961dcc0 100644 --- a/onchain/tests/test_dewordle.cairo +++ b/onchain/tests/test_dewordle.cairo @@ -1,4 +1,5 @@ use dewordle::interfaces::{IDeWordleDispatcher, IDeWordleDispatcherTrait}; +use dewordle::utils::{compare_word, is_correct_hashed_word, hash_word, hash_letter}; use snforge_std::{ declare, ContractClassTrait, DeclareResultTrait, start_cheat_caller_address, stop_cheat_caller_address @@ -31,7 +32,7 @@ fn test_set_daily_word() { dewordle.set_daily_word(daily_word.clone()); // Verify that the daily word was set correctly - assert(dewordle.get_daily_word() == daily_word, 'Daily word not stored correctly'); + assert(dewordle.get_daily_word() == hash_word(daily_word), 'Daily word not stored correctly'); } #[test] @@ -189,7 +190,7 @@ fn test_play_does_not_affect_other_storage() { dewordle.play(); // Check that daily word is unchanged - assert(dewordle.get_daily_word() == "test", 'Daily word changed unexpectedly'); + assert(dewordle.get_daily_word() == hash_word("test"), 'Daily word changed unexpectedly'); stop_cheat_caller_address(contract_address); } @@ -344,3 +345,30 @@ fn test_submit_guess_when_correct() { new_daily_stat.won_at_attempt == 6 - daily_stat.attempt_remaining, 'Wrong won_at_attempt' ); } + +#[test] +fn test_get_daily_letters() { + let contract_address = deploy_contract(); + let dewordle = IDeWordleDispatcher { contract_address }; + + start_cheat_caller_address(contract_address, OWNER()); + + // Define and set the daily word + let daily_word = "test"; + dewordle.set_daily_word(daily_word.clone()); + + // Get the stored letters + let stored_letters = dewordle.get_daily_letters(); + let word = array![ + hash_letter('t'.into()), + hash_letter('e'.into()), + hash_letter('s'.into()), + hash_letter('t'.into()) + ]; + + for i in 0..word.len() { + assert(stored_letters[i] == word[i], 'Mismatched letter hash'); + }; + + stop_cheat_caller_address(contract_address); +} diff --git a/onchain/tests/test_utils.cairo b/onchain/tests/test_utils.cairo index 2a1754e..edba3ba 100644 --- a/onchain/tests/test_utils.cairo +++ b/onchain/tests/test_utils.cairo @@ -1,9 +1,14 @@ use dewordle::constants::LetterState; -use dewordle::utils::{compare_word, is_correct_word}; +use dewordle::utils::{compare_word, is_correct_hashed_word, hash_word, hash_letter}; #[test] fn test_compare_word_when_all_letters_are_correct() { - let daily_word = "test"; + let daily_word = array![ + hash_letter('t'.into()), + hash_letter('e'.into()), + hash_letter('s'.into()), + hash_letter('t'.into()) + ]; assert( compare_word( @@ -18,7 +23,12 @@ fn test_compare_word_when_all_letters_are_correct() { #[test] fn test_compare_word_when_some_letters_are_misplaced() { - let daily_word = "test"; + let daily_word = array![ + hash_letter('t'.into()), + hash_letter('e'.into()), + hash_letter('s'.into()), + hash_letter('t'.into()) + ]; assert( compare_word( @@ -33,7 +43,12 @@ fn test_compare_word_when_some_letters_are_misplaced() { #[test] fn test_compare_word_when_some_letters_are_absent() { - let daily_word = "test"; + let daily_word = array![ + hash_letter('t'.into()), + hash_letter('e'.into()), + hash_letter('s'.into()), + hash_letter('t'.into()) + ]; assert( compare_word( @@ -49,14 +64,26 @@ fn test_compare_word_when_some_letters_are_absent() { #[test] #[should_panic(expected: 'Length does not match')] fn test_compare_word_panics() { - let daily_word = "slept"; + let daily_word = array![ + hash_letter('s'.into()), + hash_letter('l'.into()), + hash_letter('e'.into()), + hash_letter('p'.into()), + hash_letter('t'.into()) + ]; compare_word(daily_word, "sweeps"); } #[test] fn test_compare_word_when_some_letters_are_repeated() { - let daily_word = "slept"; + let daily_word = array![ + hash_letter('s'.into()), + hash_letter('l'.into()), + hash_letter('e'.into()), + hash_letter('p'.into()), + hash_letter('t'.into()) + ]; assert( compare_word( @@ -72,7 +99,12 @@ fn test_compare_word_when_some_letters_are_repeated() { 'Word not compared correctly' ); - let daily_word = "test"; + let daily_word = array![ + hash_letter('t'.into()), + hash_letter('e'.into()), + hash_letter('s'.into()), + hash_letter('t'.into()) + ]; assert( compare_word( @@ -86,16 +118,38 @@ fn test_compare_word_when_some_letters_are_repeated() { } #[test] -fn test_is_correct_word() { - let correct_word = "hello"; +fn test_is_correct_hashed_word() { + let correct_word = hash_word("hello"); // Test case 1: Correct guess - let guessed_word = "hello"; - let result = is_correct_word(correct_word.clone(), guessed_word.clone()); - assert(result, 'Test case 1 failed'); + let guessed_word = hash_word("hello"); + assert(is_correct_hashed_word(correct_word, guessed_word), 'Test case 1 failed'); // Test case 2: Incorrect guess - let guessed_word = "world"; - let result = is_correct_word(correct_word, guessed_word.clone()); - assert(!result, 'Test case 2 failed'); + let guessed_word = hash_word("world"); + assert(!is_correct_hashed_word(correct_word, guessed_word), 'Test case 2 failed'); +} + +#[test] +fn test_hash_letter() { + let letter_a = 'a'.into(); + let letter_b = 'b'.into(); + + let hash_a = hash_letter(letter_a.clone()); // Clone before passing + let hash_b = hash_letter(letter_b.clone()); + + assert!(hash_a != hash_b, "Different letters"); + assert!(hash_a == hash_letter(letter_a), "Same letter"); +} + +#[test] +fn test_hash_word() { + let word1 = "hello"; + let word2 = "world"; + + let hash1 = hash_word(word1.clone()); + let hash2 = hash_word(word2.clone()); + + assert!(hash1 != hash2, "Different words"); + assert!(hash1 == hash_word(word1), "Same word"); }