#![allow(non_snake_case)]
#![feature(int_to_from_bytes)]
#![feature(extern_prelude)]
extern crate owasm_ethereum;
extern crate owasm_abi;
#[macro_use]
extern crate owasm_abi_derive;
extern crate owasm_std;
extern crate parity_hash;
extern crate tiny_keccak;
extern crate byteorder;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate oasis_game_core;
mod external {
extern {
pub fn debug(str_ptr: *const u8, str_len: u32);
}
}
pub fn debug(msg: &str) {
unsafe { external::debug(msg.as_ptr(), msg.len() as u32); }
}
pub mod helpers {
use tiny_keccak::Keccak;
use parity_hash::H256;
use byteorder::{LittleEndian, WriteBytesExt};
use std::{mem, cmp};
use serde::Serialize;
use serde::de::DeserializeOwned;
use owasm_std::logger::debug;
pub fn keccak<T>(input: T) -> H256 where T: AsRef<[u8]> {
let mut keccak = Keccak::new_keccak256();
let mut res = H256::new();
keccak.update(input.as_ref());
keccak.finalize(&mut res);
res
}
fn path_hashes(path: String, len: u64) -> Vec<H256> {
let mut hashes = vec![];
let mut idx: u64 = 0;
let base = keccak(path);
while idx < len {
let mut hash = base.clone();
let mut bs = [0u8; mem::size_of::<u64>()];
bs.as_mut().write_u64::<LittleEndian>(idx).unwrap();
for (i, b) in bs.iter().enumerate() {
hash[i] = *b;
}
hashes.push(hash);
idx += 32
}
hashes
}
pub fn read(path: String, len: u64) -> Vec<u8> {
let p = path.clone();
let hashes = path_hashes(path, len);
let mut v: Vec<u8> = vec![];
let mut idx = 0;
for (_, hash) in hashes.iter().enumerate() {
let value = owasm_ethereum::read(&hash);
for j in value.iter() {
if idx < len {
v.push(*j);
idx += 1;
}
}
}
v
}
pub fn write(path: String, value: Vec<u8>) {
let length = value.len();
let hashes = path_hashes(path, length as u64);
for (i, hash) in hashes.iter().enumerate() {
let idx = i * 32;
let mut arr = [0u8; 32];
let lower = idx;
let upper = cmp::min(idx+32, length);
let slice = &value[lower..upper];
for (i, val) in slice.iter().enumerate() {
arr[i] = *val;
}
owasm_ethereum::write(&hash, &arr);
}
}
pub trait Storable<T>: Serialize + DeserializeOwned {
fn save(&self, path: String) {
let serialized = serde_json::to_vec(&self).expect("Could not serialize storable.");
let mut bs = [0u8; mem::size_of::<u64>()];
owasm_std::write_u64(&mut bs, serialized.len() as u64);
write(path.clone(), serialized);
write(format!("{}/length", path), bs.to_vec());
}
fn load(path: String) -> Option<Self> {
let mut len_bytes = read(format!("{}/length", path), mem::size_of::<u64>() as u64);
let len = owasm_std::read_u64(&mut len_bytes);
if len == 0 {
return None;
}
let value: Self = serde_json::from_slice(read(path, len).as_slice())
.expect("Could not deserialize storable.");
Some(value)
}
}
}
mod models {
use helpers::{Storable};
use oasis_game_core::{Action};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Registration {
pub token: [u8; 20],
pub is_bot: bool
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct PlayerInfo {
pub game_id: u64,
pub token: [u8; 20],
pub address: Option<[u8; 20]>,
pub id: u16,
pub ready: bool,
pub is_bot: bool
}
impl PlayerInfo {
fn path(game_id: u64, id: u16) -> String {
format!("/game/{}/players/{}", game_id, id)
}
pub fn load(game_id: u64, id: u16) -> Option<PlayerInfo> {
Storable::load(PlayerInfo::path(game_id, id))
}
pub fn save(&self) {
Storable::save(self, PlayerInfo::path(self.game_id, self.id))
}
}
impl Storable<PlayerInfo> for PlayerInfo {}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub enum GameStatus {
Created,
InProgress,
Finished
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GameMetadata {
pub game_id: u64,
pub status: GameStatus,
pub seed: Option<u128>,
pub entropy: Vec<u8>,
pub player_ids: Vec<u16>,
pub ready_players: u16
}
impl GameMetadata {
fn path(game_id: u64) -> String {
format!("/game/{}", game_id)
}
pub fn load(game_id: u64) -> Option<GameMetadata> {
Storable::load(GameMetadata::path(game_id))
}
pub fn save(&self) {
Storable::save(self, GameMetadata::path(self.game_id))
}
}
impl Storable<GameMetadata> for GameMetadata {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GamesOverview {
pub num_games: u64
}
impl GamesOverview {
fn path() -> String {
format!("/games")
}
pub fn load() -> Option<GamesOverview> {
Storable::load(GamesOverview::path())
}
pub fn save(&self) {
Storable::save(self, GamesOverview::path())
}
}
impl Storable<GamesOverview> for GamesOverview {}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct GameState {
pub game_id: u64,
pub state: serde_json::Value
}
impl GameState {
fn path(game_id: u64) -> String {
format!("/game/{}/state", game_id)
}
pub fn load(game_id: u64) -> Option<GameState> {
Storable::load(GameState::path(game_id))
}
pub fn save(&self) {
Storable::save(self, GameState::path(self.game_id))
}
}
impl Storable<GameState> for GameState {}
#[derive(Serialize, Deserialize, Debug)]
pub struct GameMoves {
pub game_id: u64,
pub moves: Vec<Action>
}
impl GameMoves {
fn path(game_id: u64) -> String {
format!("/game/{}/moves", game_id)
}
pub fn load(game_id: u64) -> Option<GameMoves> {
Storable::load(GameMoves::path(game_id))
}
pub fn save(&self) {
Storable::save(self, GameMoves::path(self.game_id))
}
}
impl Storable<GameMoves> for GameMoves {}
}
pub mod gameserver {
use parity_hash::*;
use oasis_game_core::{StoreFactory, Store, Action, GameError};
use owasm_std::logger::debug;
use helpers::keccak;
use models::*;
use std::u16;
pub struct GameServer {
pub factory: Box<StoreFactory>
}
impl GameServer {
fn load_store(&self, game_id: u64) -> Option<Box<Store>> {
let metadata = GameMetadata::load(game_id)?;
let game_state = GameState::load(game_id)?;
let players = (1..=metadata.player_ids.len() as u16).collect::<Vec<u16>>();
let mut store = self.factory.create(u16::MAX, players, true, true, false, metadata.seed);
store.dispatch(Action::Restore(game_state.state)).expect("Could not restore state");
Some(store)
}
fn save_store(&mut self, game_id: u64, store: &Box<Store>) {
let state = store.get_state();
let complete_state = state.get(&u16::MAX).unwrap();
let state_value = serde_json::to_value(complete_state)
.expect("Could not serialize state.");
let game_state = GameState {
game_id,
state: state_value,
};
game_state.save();
}
fn get_filtered_state(&self, id: u16, store: &Box<Store>) -> Vec<u8> {
let state = store.get_state();
let val: &serde_json::Value = state.get(&id)
.expect("State does not exist for user.");
let v: Vec<u8> = serde_json::to_vec(val)
.expect("Could not serialize state.");
v
}
fn forfeit (&mut self, store: &mut Box<Store>, game_id: u64, id: u16, game_action: Action) -> bool {
store.forfeit(GameError::UnauthorizedAction(id, game_action));
self.save_store(game_id, &store);
true
}
fn apply_move(&mut self, store: &mut Box<Store>, game_id: u64, id: u16, raw_move: Vec<u8>) -> bool {
let game_action: Action = serde_json::from_slice(raw_move.as_slice())
.unwrap();
match game_action.clone() {
Action::MakeMove(game_move) => {
if game_move.player_id != id {
panic!("Player is attempting to make an invalid move.");
}
},
Action::GameEvent(game_event) => {
if game_event.player_id != id {
panic!("Player is attempting to generate an invalid event.");
}
},
_ => {
return self.forfeit(store, game_id, id, game_action);
}
}
let result = store.dispatch(game_action.clone());
if result.is_err() {
panic!("Player attempted to make an invalid move.");
} else {
}
self.save_store(game_id, &store);
if result.is_err() || store.is_game_over() {
return true;
}
false
}
fn process_bot_moves(&mut self, store: &mut Box<Store>, game_id: u64) {
}
fn request_bot_moves(&mut self, store: &mut Box<Store>, game_id: u64) -> Vec<(u16, Vec<u8>)> {
vec![]
}
fn register(&mut self, game_id: u64, token: [u8; 20], player_id: u64, is_bot: bool) -> Option<()> {
let info = PlayerInfo {
game_id,
token,
id: player_id as u16,
ready: false,
is_bot: is_bot,
address: None
};
info.save();
if let Some(mut store) = self.load_store(game_id) {
self.process_bot_moves(&mut store, game_id);
}
Some(())
}
pub fn create(&mut self, tokens: Vec<u8>) -> u64 {
let registrations: Vec<Registration> = serde_json::from_slice(tokens.as_slice()).unwrap();
let mut overview = GamesOverview::load().unwrap_or_else(|| GamesOverview {
num_games: 0
});
overview.num_games += 1;
let game_id = overview.num_games;
let mut ids: Vec<u16> = vec![];
for (i, registration) in registrations.iter().enumerate() {
let id = (i + 1) as u64;
self.register(game_id, registration.token, id, registration.is_bot);
ids.push(id as u16);
}
let metadata = GameMetadata {
game_id,
seed: None,
entropy: vec![],
status: GameStatus::Created,
player_ids: ids,
ready_players: 0
};
overview.save();
metadata.save();
game_id
}
pub fn ready(&mut self, game_id: u64, token: Vec<u8>, entropy: &mut Vec<u8>) -> Option<(u16, bool)> {
let mut metadata = GameMetadata::load(game_id)?;
let mut players = self._get_players(game_id)?;
let player = &mut players.iter_mut().find(|player| player.token == token.as_slice())?;
if metadata.status != GameStatus::Created {
panic!("Cannot call ready on a game that's already begun.");
}
if let Some(address) = player.address {
panic!("That player ID has already been bound to an address.");
}
if player.ready {
panic!("A player cannot call ready twice");
}
if entropy.len() != 32 {
panic!("Each player must send 32 bytes of entropy");
}
let sender = owasm_ethereum::sender();
player.address = Some(sender.into());
player.ready = true;
metadata.ready_players += 1;
metadata.entropy.append(entropy);
let mut started = false;
if metadata.ready_players == metadata.player_ids.len() as u16 {
let hash: [u8; 32] = keccak(metadata.entropy.clone()).into();
let mut half_hash: [u8; 16] = [0; 16];
for i in 0..16 {
half_hash[i] = hash[i]
}
metadata.seed = Some(u128::from_le_bytes(half_hash));
metadata.status = GameStatus::InProgress;
let store = self.factory.create(u16::MAX, metadata.player_ids.clone(), true, true, true, metadata.seed);
self.save_store(game_id, &store);
started = true
}
metadata.save();
player.save();
Some((player.id, started))
}
pub fn handle_action(&mut self, game_id: u64, player_id: u64, game_move: Vec<u8>) -> Option<()> {
let id = player_id as u16;
let mut metadata = GameMetadata::load(game_id)?;
if metadata.status != GameStatus::InProgress {
panic!("Cannot perform an action on a game that is not in-progress.");
}
let player = PlayerInfo::load(game_id, id)?;
match player.address {
None => panic!("This player has not been bound to an address."),
Some(address) => {
if Address::from(address) != owasm_ethereum::sender() {
panic!("The player is not registered with the game.")
}
}
};
if let Some(mut store) = self.load_store(game_id) {
let finished = self.apply_move(&mut store, game_id, id, game_move);
self.process_bot_moves(&mut store, game_id);
if finished {
metadata.status = GameStatus::Finished;
metadata.save();
return Some(())
}
if let Some(seed) = metadata.seed {
metadata.seed = Some(seed + 1);
metadata.save()
}
}
Some(())
}
pub fn get_state(&mut self, game_id: u64, player_id: u64) -> Option<Vec<u8>> {
let store = self.load_store(game_id)?;
let res: Vec<u8> = serde_json::to_vec(store.get_state().get(&(player_id as u16))?)
.expect("Could not serialize game state");
Some(res)
}
fn _get_players(&mut self, game_id: u64) -> Option<Vec<PlayerInfo>> {
let mut players: Vec<PlayerInfo> = vec![];
let game_info = GameMetadata::load(game_id)?;
for player_id in 1..=game_info.player_ids.len() as u16 {
if let Some(player_info) = PlayerInfo::load(game_id, player_id) {
players.push(player_info)
}
}
Some(players)
}
pub fn get_players(&mut self, game_id: u64) -> Option<Vec<u8>> {
let players: Vec<PlayerInfo> = self._get_players(game_id).unwrap();
let res: Vec<u8> = serde_json::to_vec(&players)
.expect("Could not serialize player info.");
Some(res)
}
pub fn get_moves(&mut self, game_id: u64) -> Option<Vec<u8>> {
let moves = GameMoves::load(game_id)?;
Some(serde_json::to_vec(&moves.moves).expect("Could not serialize game moves."))
}
}
#[owasm_abi_derive::eth_abi(BotEndpoint, BotClient)]
trait BotInterface {
fn makeMove(&mut self, _player_id: u64, _state: Vec<u8>) -> Vec<u8>;
}
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}