Skip to content

Commit

Permalink
[contract]: refac + add docstrings (#281)
Browse files Browse the repository at this point in the history
  • Loading branch information
manlikeHB authored Mar 10, 2025
1 parent c152a68 commit 0f4c9c6
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 70 deletions.
71 changes: 49 additions & 22 deletions onchain/src/contracts/dewordle.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ pub mod DeWordle {
#[storage]
struct Storage {
word_of_the_day: felt252,
letters_in_word: Vec<felt252>, //TODO: hash letters
letters_in_word: Vec<felt252>,
word_len: u8,
player_stat: Map<ContractAddress, PlayerStat>,
daily_player_stat: Map<ContractAddress, DailyPlayerStat>, // TODO: track day
daily_player_stat: Map<ContractAddress, DailyPlayerStat>,
end_of_day_timestamp: u64,
#[substorage(v0)]
ownable: OwnableComponent::Storage,
Expand Down Expand Up @@ -82,6 +82,10 @@ pub mod DeWordle {

#[abi(embed_v0)]
impl DeWordleImpl of IDeWordle<ContractState> {
/// @notice Sets the word of the day
/// @param word: The ByteArray representing the new word to be set
/// @dev Only callable by an address with ADMIN_ROLE
/// @dev Hashes the word and stores it, along with each individual letter
fn set_daily_word(ref self: ContractState, word: ByteArray) {
self.accesscontrol.assert_only_role(ADMIN_ROLE);
let word_len = word.len();
Expand All @@ -98,23 +102,10 @@ pub mod DeWordle {
self.word_len.write(word_len.try_into().unwrap());
}

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<felt252> {
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
}

/// @notice Retrieves a player's daily statistics
/// @param player: The address of the player
/// @return DailyPlayerStat: The daily stats for the given player
/// @dev Returns default values if player hasn't played today (6 attempts, not won)
fn get_player_daily_stat(self: @ContractState, player: ContractAddress) -> DailyPlayerStat {
let daily_stat = self.daily_player_stat.read(player);

Expand All @@ -128,6 +119,7 @@ pub mod DeWordle {
}
}

// TODO: remove this
fn play(ref self: ContractState) {
let caller: ContractAddress = starknet::get_caller_address();

Expand All @@ -138,6 +130,12 @@ pub mod DeWordle {
self.daily_player_stat.write(caller, new_daily_stat);
}

/// @notice Submit a guess for the daily word
/// @param guessed_word: The ByteArray representing the player's guess
/// @return Option<Span<LetterState>>: None if the guess is correct, otherwise returns a
/// Span of LetterState indicating the correctness of each letter @dev Verifies the guess
/// length, validates player has attempts remaining and hasn't already won @dev Updates
/// player stats based on the outcome of the guess
fn submit_guess(
ref self: ContractState, guessed_word: ByteArray
) -> Option<Span<LetterState>> {
Expand All @@ -147,7 +145,7 @@ pub mod DeWordle {
assert(!daily_stat.has_won, 'Player has already won');
assert(daily_stat.attempt_remaining > 0, 'Player has exhausted attempts');
let hash_guessed_word = hash_word(guessed_word.clone());
if is_correct_hashed_word(self.get_daily_word(), hash_guessed_word) {
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,
Expand All @@ -164,10 +162,17 @@ pub mod DeWordle {
won_at_attempt: 0,
};
self.daily_player_stat.write(caller, new_daily_stat);
Option::Some(compare_word(self.get_daily_letters(), guessed_word.clone()))
Option::Some(compare_word(self._get_daily_letters(), guessed_word.clone()))
}
}


// TODO refac: move to internal
// update tests accordingly

/// @notice Updates the end of day timestamp if the current day has ended
/// @dev Checks if current block timestamp is past the end of day and updates to next
/// midnight if needed @dev Emits a DayUpdated event when the timestamp is updated
fn update_end_of_day(ref self: ContractState) {
if get_block_timestamp() >= self.end_of_day_timestamp.read() {
let new_end_of_day = get_next_midnight_timestamp();
Expand All @@ -176,11 +181,33 @@ pub mod DeWordle {
}
}

/// @notice Gets the timestamp for when the current day ends
/// @return u64 The Unix timestamp for the end of the current day
fn get_end_of_day_timestamp(self: @ContractState) -> u64 {
self.end_of_day_timestamp.read()
}
}

#[generate_trait]
pub impl InternalFunctions of InternalFunctionsTrait {}
pub impl InternalFunctions of InternalFunctionsTrait {
/// @notice Retrieves the hashed word of the day
/// @return felt252 The hashed word of the day
fn _get_daily_word(self: @ContractState) -> felt252 {
self.word_of_the_day.read()
}

/// @notice Gets the array of hashed letters for the daily word
/// @return Array<felt252> An array containing each hashed letter of the daily word
fn _get_daily_letters(self: @ContractState) -> Array<felt252> {
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
}
}
}
2 changes: 0 additions & 2 deletions onchain/src/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ use starknet::ContractAddress;
#[starknet::interface]
pub trait IDeWordle<TContractState> {
fn set_daily_word(ref self: TContractState, word: ByteArray);
fn get_daily_word(self: @TContractState) -> felt252;
fn get_daily_letters(self: @TContractState) -> Array<felt252>;

fn get_player_daily_stat(self: @TContractState, player: ContractAddress) -> DailyPlayerStat;
fn play(ref self: TContractState);
Expand Down
93 changes: 47 additions & 46 deletions onchain/tests/test_dewordle.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,22 @@ fn deploy_contract() -> ContractAddress {
contract_address
}

#[test]
fn test_set_daily_word() {
// Deploy the contract
let contract_address = deploy_contract();
let dewordle = IDeWordleDispatcher { contract_address: contract_address };
// #[test]
// fn test_set_daily_word() {
// // Deploy the contract
// let contract_address = deploy_contract();
// let dewordle = IDeWordleDispatcher { contract_address: contract_address };

start_cheat_caller_address(contract_address, OWNER());
// start_cheat_caller_address(contract_address, OWNER());

// Define and set the daily word
let daily_word = "test";
dewordle.set_daily_word(daily_word.clone());
// // Define and set the daily word
// let daily_word = "test";
// dewordle.set_daily_word(daily_word.clone());

// Verify that the daily word was set correctly
assert(dewordle.get_daily_word() == hash_word(daily_word), 'Daily word not stored correctly');
}
// // Verify that the daily word was set correctly
// assert(dewordle.get_daily_word() == hash_word(daily_word), 'Daily word not stored
// correctly');
// }

#[test]
fn test_play_initializes_daily_player_stat() {
Expand Down Expand Up @@ -176,24 +177,24 @@ fn test_play_after_losing() {
stop_cheat_caller_address(contract_address);
}

#[test]
fn test_play_does_not_affect_other_storage() {
let contract_address = deploy_contract();
let dewordle = IDeWordleDispatcher { contract_address: contract_address };
// #[test]
// fn test_play_does_not_affect_other_storage() {
// let contract_address = deploy_contract();
// let dewordle = IDeWordleDispatcher { contract_address: contract_address };

start_cheat_caller_address(contract_address, OWNER());
// start_cheat_caller_address(contract_address, OWNER());

// Set up initial state
dewordle.set_daily_word("test");
// // Set up initial state
// dewordle.set_daily_word("test");

// Play
dewordle.play();
// // Play
// dewordle.play();

// Check that daily word is unchanged
assert(dewordle.get_daily_word() == hash_word("test"), 'Daily word changed unexpectedly');
// // Check that daily word is unchanged
// assert(dewordle.get_daily_word() == hash_word("test"), 'Daily word changed unexpectedly');

stop_cheat_caller_address(contract_address);
}
// stop_cheat_caller_address(contract_address);
// }

#[test]
#[should_panic(expected: 'Length does not match')]
Expand Down Expand Up @@ -346,32 +347,32 @@ fn test_submit_guess_when_correct() {
);
}

#[test]
fn test_get_daily_letters() {
let contract_address = deploy_contract();
let dewordle = IDeWordleDispatcher { contract_address };
// #[test]
// fn test_get_daily_letters() {
// let contract_address = deploy_contract();
// let dewordle = IDeWordleDispatcher { contract_address };

start_cheat_caller_address(contract_address, OWNER());
// start_cheat_caller_address(contract_address, OWNER());

// Define and set the daily word
let daily_word = "test";
dewordle.set_daily_word(daily_word.clone());
// // 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())
];
// // 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');
};
// for i in 0..word.len() {
// assert(stored_letters[i] == word[i], 'Mismatched letter hash');
// };

stop_cheat_caller_address(contract_address);
}
// stop_cheat_caller_address(contract_address);
// }

#[test]
fn test_update_end_of_day() {
Expand Down

0 comments on commit 0f4c9c6

Please sign in to comment.