Skip to content

Commit

Permalink
optimistic starting
Browse files Browse the repository at this point in the history
  • Loading branch information
ponderingdemocritus committed Feb 27, 2024
1 parent 43c0eb3 commit 0512694
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 19 deletions.
5 changes: 4 additions & 1 deletion contracts/game/src/game/constants.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ mod messages {
const NOT_OWNER_OF_TOKEN: felt252 = 'Not owner of token';
const MA_PERIOD_LESS_THAN_WEEK: felt252 = 'MA period too small';
const TERMINAL_TIME_REACHED: felt252 = 'terminal time reached';
const STARTING_HASH_NOT_ZERO: felt252 = 'Starting hash must be zero';
const NO_OPTIMISTIC_START: felt252 = 'No optimistic start';
const NOT_ENOUGH_BLOCKS: felt252 = 'Not enough blocks';
}

// TODO: Update for mainnet
const BLOCKS_IN_A_WEEK: u64 = 1000;
const COST_TO_PLAY: u128 = 25000000000000000000;
const COST_TO_PLAY: u128 = 10000000000000000000;
const NUM_STARTING_STATS: u8 = 9;
const STARTING_GAME_ENTROPY_ROTATION_INTERVAL: u8 = 6;
const MINIMUM_DAMAGE_FROM_BEASTS: u8 = 2;
Expand Down
11 changes: 9 additions & 2 deletions contracts/game/src/game/interfaces.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ use game_snapshot::GamesPlayedSnapshot;
trait IGame<TContractState> {
// ------ Game Actions ------
fn new_game(
ref self: TContractState, client_reward_address: ContractAddress, weapon: u8, name: u128, golden_token_id: u256, interface_camel: bool
ref self: TContractState,
client_reward_address: ContractAddress,
weapon: u8,
name: u128,
golden_token_id: u256,
interface_camel: bool
);
fn explore(ref self: TContractState, adventurer_id: felt252, till_beast: bool);
fn attack(ref self: TContractState, adventurer_id: felt252, to_the_death: bool);
Expand All @@ -32,6 +37,8 @@ trait IGame<TContractState> {
fn rotate_game_entropy(ref self: TContractState);
fn update_cost_to_play(ref self: TContractState);
fn initiate_price_change(ref self: TContractState);
fn optimistic_start(ref self: TContractState, adventurer_id: felt252, block_hash: felt252);
fn slay_invalid_adventurer(ref self: TContractState, adventurer_id: felt252);

// ------ View Functions ------

Expand Down Expand Up @@ -154,4 +161,4 @@ trait IGame<TContractState> {
fn get_cost_to_play(self: @TContractState) -> u128;
fn get_games_played_snapshot(self: @TContractState) -> GamesPlayedSnapshot;
fn can_play(self: @TContractState, golden_token_id: u256) -> bool;
}
}
100 changes: 98 additions & 2 deletions contracts/game/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ mod Game {
_cost_to_play: u128,
_games_played_snapshot: GamesPlayedSnapshot,
_terminal_timestamp: u64,
_starting_entropy: LegacyMap::<felt252, felt252>,
}

#[event]
Expand Down Expand Up @@ -224,6 +225,45 @@ mod Game {
_start_game(ref self, weapon, name, interface_camel);
}

fn optimistic_start(ref self: ContractState, adventurer_id: felt252, block_hash: felt252) {
// assert the starting entropy has not been set
assert(
self._starting_entropy.read(adventurer_id) == 0, messages::STARTING_HASH_NOT_ZERO
);

self._starting_entropy.write(adventurer_id, block_hash);

// then attack the starting beast
self.attack(adventurer_id, false);
}


fn slay_invalid_adventurer(ref self: ContractState, adventurer_id: felt252) {
// get starting blockhash
let optimistic_hash = self._starting_entropy.read(adventurer_id);

// must be optimistic start
assert(optimistic_hash != 0, messages::NO_OPTIMISTIC_START);

// get adventurer meta data
let mut adventurer_meta = _load_adventurer_metadata(@self, adventurer_id);

// at least 11 blocks must have passed
assert(
starknet::get_block_info().unbox().block_number > adventurer_meta.start_block + 11,
messages::NOT_ENOUGH_BLOCKS
);

// get the blockhash
let block_hash = starknet::get_block_hash_syscall(adventurer_meta.start_block + 1)
.unwrap_syscall();

// slay
if (optimistic_hash != block_hash) {
_slay_invalid_adventurer(ref self, adventurer_id);
}
}

/// @title Explore Function
///
/// @notice Allows an adventurer to explore
Expand Down Expand Up @@ -306,7 +346,12 @@ mod Game {

// ensure player is not exceeding the rate limit
let block_number = starknet::get_block_info().unbox().block_number;
if !adventurer.block_changed_since_last_action(block_number) {

// we check if optimistic start and has not started
let is_starting = self._starting_entropy.read(adventurer_id) == 0
|| adventurer.get_level() == 1;

if !is_starting || !adventurer.block_changed_since_last_action(block_number) {
_assert_rate_limit(adventurer.actions_per_block, game_entropy.get_rate_limit());
}

Expand All @@ -320,10 +365,12 @@ mod Game {

// if the adventurer is on level 1, this is their first action of the game
if adventurer.get_level() == 1 {
// so we reveal their starting stats and store them in Adventurer Meta
let adventurer_meta = _handle_stat_reveal(@self, ref adventurer, adventurer_id);
// update adventurer meta data (this is the last time this storage slot is updated)
_save_adventurer_metadata(ref self, adventurer_id, adventurer_meta);

// at least one block must have passed
assert(block_number > adventurer_meta.start_block, 'CANNOT START IN SAME BLOCK');
}

// get number of blocks between actions
Expand Down Expand Up @@ -1083,6 +1130,27 @@ mod Game {
_save_adventurer_no_boosts(ref self, adventurer, adventurer_id);
}

fn _slay_invalid_adventurer(ref self: ContractState, adventurer_id: felt252) {
// unpack adventurer from storage (no need for stat boosts)
let mut adventurer = _load_adventurer_no_boosts(@self, adventurer_id);

// assert adventurer is not already dead
_assert_not_dead(adventurer);

// assert adventurer is idle
_assert_is_idle(@self, adventurer, adventurer_id);

// slay adventurer by setting health to 0 and 0 the xp because of invalid start
adventurer.health = 0;
adventurer.xp = 0;

// handle adventurer death
_process_adventurer_death(ref self, adventurer, adventurer_id, 0, 0,);

// save adventurer (gg)
_save_adventurer_no_boosts(ref self, adventurer, adventurer_id);
}

fn _process_beast_death(
ref self: ContractState,
ref adventurer: Adventurer,
Expand Down Expand Up @@ -1210,13 +1278,41 @@ mod Game {
}
}

fn _check_and_kill_invalid_adventurer(ref self: ContractState, adventurer_id: felt252) {
let optimistic_hash = self._starting_entropy.read(adventurer_id);

if (optimistic_hash != 0) {
// get adventurer meta data
let mut adventurer_meta = _load_adventurer_metadata(@self, adventurer_id);

// get the blockhash
let block_hash = starknet::get_block_hash_syscall(adventurer_meta.start_block + 1)
.unwrap_syscall();

if (optimistic_hash != block_hash) { // zero out
let mut adventurer = _load_adventurer_no_boosts(@self, adventurer_id);

// zero out adventurer stats
adventurer.xp = 0;
adventurer.health = 0;

// save adventurer
_save_adventurer_no_boosts(ref self, adventurer, adventurer_id);
}
}
}


fn _process_adventurer_death(
ref self: ContractState,
adventurer: Adventurer,
adventurer_id: felt252,
beast_id: u8,
obstacle_id: u8
) {
// optimistic check
_check_and_kill_invalid_adventurer(ref self, adventurer_id);

let adventurer_state = AdventurerState {
owner: _owner_of(@self, adventurer_id), adventurer_id, adventurer
};
Expand Down
48 changes: 34 additions & 14 deletions contracts/game/src/tests/test_game.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -258,14 +258,16 @@ mod tests {
'wrong starter beast health '
);

testing::set_block_number(starting_block + 1);

game
}

fn new_adventurer_lvl2_with_idle_penalty() -> IGameDispatcher {
// start game on block number 1
let mut game = new_adventurer(1000, 1696201757);

// fast forward chain to block number 400
// fast forward chain to block number 1002
testing::set_block_number(1002);

// double attack beast
Expand Down Expand Up @@ -620,6 +622,7 @@ mod tests {
// is annotated in the test
game.explore(ADVENTURER_ID, true);
}

#[test]
#[should_panic]
#[available_gas(90000000)]
Expand Down Expand Up @@ -1323,7 +1326,7 @@ mod tests {

// verify last action block number is correct
assert(
adventurer.last_action_block == STARTING_BLOCK_NUMBER.try_into().unwrap(),
adventurer.last_action_block == STARTING_BLOCK_NUMBER.try_into().unwrap() + 1,
'unexpected last action block'
);

Expand Down Expand Up @@ -1438,6 +1441,8 @@ mod tests {
add_adventurer_to_game(ref game, 0);
add_adventurer_to_game(ref game, 0);

testing::set_block_number(STARTING_BLOCK_NUMBER + 2);

// attack starter beast, resulting in adventurer last action block number being 1
game.attack(ADVENTURER_ID, false);
game.attack(ADVENTURER2_ID, false);
Expand Down Expand Up @@ -2238,9 +2243,7 @@ mod tests {
let starting_block = 364063;
let starting_timestamp = 1698678554;
let terminal_timestamp = 0;
let (mut game, _, _, _) = setup(
starting_block, starting_timestamp, terminal_timestamp
);
let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp);
add_adventurer_to_game(ref game, 1);
testing::set_block_timestamp(starting_timestamp + DAY);
add_adventurer_to_game(ref game, 1);
Expand All @@ -2253,9 +2256,7 @@ mod tests {
let starting_block = 364063;
let starting_timestamp = 1698678554;
let terminal_timestamp = 0;
let (mut game, _, _, _) = setup(
starting_block, starting_timestamp, terminal_timestamp
);
let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp);
assert(game.can_play(1), 'should be able to play');
add_adventurer_to_game(ref game, golden_token_id);
assert(!game.can_play(1), 'should not be able to play');
Expand All @@ -2273,9 +2274,7 @@ mod tests {
let starting_block = 364063;
let starting_timestamp = 1698678554;
let terminal_timestamp = 0;
let (mut game, _, _, _) = setup(
starting_block, starting_timestamp, terminal_timestamp
);
let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp);
add_adventurer_to_game(ref game, golden_token_id);
}

Expand All @@ -2287,9 +2286,7 @@ mod tests {
let starting_block = 364063;
let starting_timestamp = 1698678554;
let terminal_timestamp = 0;
let (mut game, _, _, _) = setup(
starting_block, starting_timestamp, terminal_timestamp
);
let (mut game, _, _, _) = setup(starting_block, starting_timestamp, terminal_timestamp);
add_adventurer_to_game(ref game, golden_token_id);

// roll blockchain forward 1 second less than a day
Expand Down Expand Up @@ -2323,4 +2320,27 @@ mod tests {
let (is_idle, _) = game.is_idle(ADVENTURER_ID);
assert(is_idle, 'should be idle');
}

const TEST_BLOCKHASH: felt252 = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890ab;

fn optimistic_start() -> IGameDispatcher {
// start new game
let mut game = new_adventurer(1000, 1696201757);

game.optimistic_start(ADVENTURER_ID, TEST_BLOCKHASH);

game
}

#[test]
#[available_gas(2300000000)]
fn test_optimistic_start() {
optimistic_start();
}

#[test]
#[available_gas(2300000000)]
fn test_optimistic_start_slay_invalid() {
let mut game = optimistic_start();
}
}

0 comments on commit 0512694

Please sign in to comment.