/ test / contracts / coin_toss.aes
coin_toss.aes
  1  include "String.aes"
  2  
  3  contract CoinToss =
  4    record state = { player          : address,
  5                     casino          : address,
  6                     hash            : option(hash),
  7                     height          : int,
  8                     casino_pick     : option(string),
  9                     stake           : int,
 10                     reaction_time   : int
 11                     }
 12  
 13    entrypoint init(player: address, casino: address, reaction_time: int) : state =
 14      require(Call.value == 0, "no_deposit")
 15      { player          = player,
 16        casino          = casino,
 17        hash            = None,
 18        height          = 0,
 19        casino_pick     = None,
 20        stake    = 0,
 21        reaction_time   = reaction_time
 22        }
 23  
 24    payable stateful entrypoint provide_hash(hash: hash) =
 25      require_player()
 26      require(state.hash == None, "already_has_hash")
 27      put(state{ hash = Some(hash),
 28                 stake = Call.value,
 29                 height = Chain.block_height})
 30  
 31    payable stateful entrypoint casino_pick(coin_side: string) =
 32      require_casino()
 33      ensure_casino_turn_to_pick()
 34      ensure_coin_side(coin_side)
 35      require(Call.value == state.stake,
 36        String.concat("wrong_stake, expected ", Int.to_str(state.stake)))
 37      put(state{ casino_pick  = Some(coin_side),
 38                 height       = Chain.block_height})
 39  
 40    stateful entrypoint reveal(key: string, coin_side: string) =
 41      require_player()
 42      ensure_player_turn_to_reveal()
 43      ensure_coin_side(coin_side)
 44      ensure_if_key_is_valid(key, coin_side)
 45      let Some(casino_pick) = state.casino_pick
 46      let winner : address =  
 47        if (coin_side == casino_pick)
 48          state.casino
 49        else
 50          state.player
 51      Chain.spend(winner, Contract.balance)
 52      reset_state()
 53      winner
 54  
 55    stateful entrypoint casino_dispute_no_reveal() =
 56      require_casino()
 57      ensure_player_turn_to_reveal()
 58      require(state.height  + state.reaction_time < Chain.block_height, "not_yet_allowed")
 59      Chain.spend(state.casino, Contract.balance)
 60      reset_state()
 61  
 62    stateful entrypoint player_dispute_no_pick() =
 63      require_player()
 64      ensure_casino_turn_to_pick()
 65      require(state.height + state.reaction_time < Chain.block_height, "not_yet_allowed")
 66      Chain.spend(state.player, Contract.balance)
 67      reset_state()
 68  
 69    // a friendly helper function
 70    entrypoint compute_hash(key: string, coin_side: string) : hash =
 71      ensure_coin_side(coin_side)
 72      String.sha256(String.concat(key, coin_side))
 73      
 74    // internal functions
 75  
 76    function ensure_coin_side(coin_side: string) =
 77      require(coin_side == "heads" || coin_side == "tails", "invalid_coin_side")
 78  
 79    function ensure_casino_turn_to_pick() =
 80      require(state.hash != None, "no_hash")
 81      require(state.casino_pick == None, "there_is_a_pick_already")
 82    
 83    function ensure_player_turn_to_reveal() =
 84      require(state.casino_pick != None, "there_is_no_pick")
 85  
 86    function require_casino() =
 87      require(Call.caller == state.casino, "not_casino")
 88      
 89    function require_player() =
 90      require(Call.caller == state.player, "not_player")
 91  
 92    function ensure_if_key_is_valid(key: string, coin_side: string) =
 93      let computed_hash = compute_hash(key, coin_side)
 94      let Some(stored_hash) = state.hash
 95      require(stored_hash == computed_hash, "invalid_key_and_answer")
 96  
 97    stateful function reset_state() =
 98      put(state{hash            = None,
 99                height          = 0,
100                casino_pick     = None,
101                stake           = 0
102                })