diff --git a/Cargo.lock b/Cargo.lock index 692ce16..3deffcd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3327,6 +3327,7 @@ version = "0.1.0" dependencies = [ "anyhow", "clap", + "colored", "gpc-wallet", "griffin-core", "hex", diff --git a/README.md b/README.md index f2fd203..4985ca3 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ As the result of the discovery process, after surveying many developers, it was In this repository we include the [on-chain code](./game/onchain/), which comes with a [design document](./game/onchain/docs/design/design.md) that thoroughly explains the transactions involved in the game. In the game [src](./game/src/) you can find the implementation of the commands necessary to play the game and a [README](./game/README.md) that goes over the app design, in the context of the game's integration into the node, and [instructions](./game/README.md#game-usage) on how to run the commands. +![Asteria Game Board](./game/game-board.png "Game Board") + ## Setting up a Partner Chain The project includes the `partner-chains-cli` from IOG's [Partner Chain SDK](https://github.com/input-output-hk/partner-chains). This `CLI` allows us to set up governance UTxOs on the Cardano side for the partner chain. An extensive explanation on the integration and usage of the CLI can be found at the previously mentioned [partner_chain_integration](./docs/dev_logs/partner_chain_integration.md) document. diff --git a/game/Cargo.toml b/game/Cargo.toml index b9c901d..6a00a98 100644 --- a/game/Cargo.toml +++ b/game/Cargo.toml @@ -8,6 +8,7 @@ edition.workspace = true [dependencies] anyhow = { workspace = true } clap = { features = ["derive"], workspace = true } +colored = { workspace = true } griffin-core = { workspace = true } gpc-wallet = { workspace = true } hex = { workspace = true } diff --git a/game/README.md b/game/README.md index c09cbe6..1a2855c 100644 --- a/game/README.md +++ b/game/README.md @@ -166,4 +166,6 @@ This command can be triggered when the ship reaches Asteria, i.e., its coordinat - *mine-coin-amount*: amount of coin to be mined from Asteria. This must be less or equal to the allowed maximum percentage of the total prize defined by MAX_ASTERIA_MINING. - *params-path*: path to the JSON file containing all the game scripts parameters and the applied scripts directory. +### Queries +There are also three query commands that allow the user to inspect the game state UTxOs, with their datums in a human-readable format: `show-asteria`, `show-pellets`, and `show-ships`. Each of them takes no arguments, except for the `params-path` argument that is common to all game commands. diff --git a/game/game-board.png b/game/game-board.png new file mode 100644 index 0000000..307dddc Binary files /dev/null and b/game/game-board.png differ diff --git a/game/src/game.rs b/game/src/game.rs index dda1805..f0a3b3f 100644 --- a/game/src/game.rs +++ b/game/src/game.rs @@ -1,4 +1,6 @@ -use crate::{CreateShipArgs, DeployScriptsArgs, GatherFuelArgs, MineAsteriaArgs, MoveShipArgs}; +use crate::{ + types::*, CreateShipArgs, DeployScriptsArgs, GatherFuelArgs, MineAsteriaArgs, MoveShipArgs, +}; use anyhow::anyhow; use gpc_wallet::{ cli::{ShowOutputsAtArgs, ShowOutputsWithAssetArgs}, @@ -6,12 +8,10 @@ use gpc_wallet::{ }; use griffin_core::{ checks_interface::{babbage_minted_tx_from_cbor, babbage_tx_to_cbor}, - h224::H224, pallas_codec::{ minicbor, utils::{Int, MaybeIndefArray::Indef}, }, - pallas_crypto::hash::Hash as PallasHash, pallas_primitives::{ babbage::{ BigInt, BoundedBytes, Constr, MintedTx, PlutusData as PallasPlutusData, @@ -29,7 +29,6 @@ use griffin_core::{ use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; use parity_scale_codec::Encode; use sc_keystore::LocalKeystore; -use serde::{Deserialize, Serialize}; use sled::Db; use sp_core::ed25519::Public; use sp_runtime::traits::{BlakeTwo256, Hash}; @@ -836,7 +835,7 @@ pub async fn mine_asteria( transaction.transaction_body.inputs = inputs; // BURNS - let ship_fuel = quanity_of(&ship_value, &pellet_policy, &fuel_name); + let ship_fuel = (&ship_value).quantity_of(&pellet_policy, &fuel_name); let ship_fuel_i64: i64 = ship_fuel .try_into() .expect("Fuel amount too large to fit in i64"); @@ -901,7 +900,7 @@ pub async fn mine_asteria( address: pilot_utxo.address.clone(), value: pilot_utxo.value.clone() + Value::Coin(args.mine_coin_amount) - + Value::Coin(coin_of(&ship_value)), + + Value::Coin((&ship_value).coin_of()), datum_option: pilot_utxo.datum_option.clone(), }, Output { @@ -1131,208 +1130,3 @@ pub async fn deploy_scripts(args: DeployScriptsArgs) -> anyhow::Result<()> { println!("All scripts written successfully!"); Ok(()) } - -fn quanity_of(value: &Value, policy: &PolicyId, name: &AssetName) -> u64 { - if let Value::Multiasset(_, ma) = value { - if let Some(assets) = ma.0.get(policy) { - if let Some(quantity) = assets.0.get(name) { - return *quantity as u64; - } - } - } - 0 -} - -fn coin_of(value: &Value) -> u64 { - match value { - Value::Coin(c) => *c, - Value::Multiasset(c, _) => *c, - } -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct ScriptsParams { - admin_policy: String, - admin_name: String, - fuel_per_step: u64, - initial_fuel: u64, - max_speed: Speed, - max_ship_fuel: u64, - max_asteria_mining: u64, - min_asteria_distance: u64, - ship_mint_lovelace_fee: u64, - scripts_directory: String, -} - -#[derive(Serialize, Deserialize, Debug, Clone)] -struct Speed { - distance: u64, - time: u64, -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum AsteriaDatum { - Ok { - ship_counter: u16, - shipyard_policy: PolicyId, - }, - MalformedAsteriaDatum, -} - -impl From for Datum { - fn from(order_datum: AsteriaDatum) -> Self { - Datum(PlutusData::from(PallasPlutusData::from(order_datum)).0) - } -} - -impl From for AsteriaDatum { - fn from(datum: Datum) -> Self { - <_>::from(PallasPlutusData::from(PlutusData(datum.0))) - } -} - -impl From for PallasPlutusData { - fn from(order_datum: AsteriaDatum) -> Self { - match order_datum { - AsteriaDatum::Ok { - ship_counter, - shipyard_policy, - } => PallasPlutusData::from(PallasPlutusData::Constr(Constr { - tag: 121, - any_constructor: None, - fields: Indef( - [ - PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( - ship_counter, - )))), - PallasPlutusData::BoundedBytes(BoundedBytes(shipyard_policy.0.to_vec())), - ] - .to_vec(), - ), - })), - AsteriaDatum::MalformedAsteriaDatum => { - PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from(-1)))) - } - } - } -} - -impl From for AsteriaDatum { - fn from(data: PallasPlutusData) -> Self { - if let PallasPlutusData::Constr(Constr { - tag: 121, - any_constructor: None, - fields: Indef(asteria_datum), - }) = data - { - if let [PallasPlutusData::BigInt(BigInt::Int(Int(ship_counter))), PallasPlutusData::BoundedBytes(BoundedBytes(shipyard_policy_vec))] = - &asteria_datum[..] - { - AsteriaDatum::Ok { - ship_counter: TryFrom::::try_from(*ship_counter).unwrap(), - shipyard_policy: H224::from(PallasHash::from(shipyard_policy_vec.as_slice())), - } - } else { - AsteriaDatum::MalformedAsteriaDatum - } - } else { - AsteriaDatum::MalformedAsteriaDatum - } - } -} - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum ShipDatum { - Ok { - pos_x: i16, - pos_y: i16, - ship_token_name: AssetName, - pilot_token_name: AssetName, - last_move_latest_time: u64, - }, - MalformedShipDatum, -} - -impl From for Datum { - fn from(ship_datum: ShipDatum) -> Self { - Datum(PlutusData::from(PallasPlutusData::from(ship_datum)).0) - } -} - -impl From for ShipDatum { - fn from(datum: Datum) -> Self { - <_>::from(PallasPlutusData::from(PlutusData(datum.0))) - } -} - -impl From for PallasPlutusData { - fn from(ship_datum: ShipDatum) -> Self { - match ship_datum { - ShipDatum::Ok { - pos_x, - pos_y, - ship_token_name, - pilot_token_name, - last_move_latest_time, - } => PallasPlutusData::from(PallasPlutusData::Constr(Constr { - tag: 121, - any_constructor: None, - fields: Indef( - [ - PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( - pos_x, - )))), - PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( - pos_y, - )))), - PallasPlutusData::BoundedBytes(BoundedBytes( - ship_token_name.0.clone().into(), - )), - PallasPlutusData::BoundedBytes(BoundedBytes( - pilot_token_name.0.clone().into(), - )), - PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( - last_move_latest_time, - )))), - ] - .to_vec(), - ), - })), - ShipDatum::MalformedShipDatum => { - PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from(-1)))) - } - } - } -} - -impl From for ShipDatum { - fn from(data: PallasPlutusData) -> Self { - if let PallasPlutusData::Constr(Constr { - tag: 121, - any_constructor: None, - fields: Indef(ship_datum), - }) = data - { - if let [PallasPlutusData::BigInt(BigInt::Int(Int(pos_x))), PallasPlutusData::BigInt(BigInt::Int(Int(pos_y))), PallasPlutusData::BoundedBytes(BoundedBytes(ship_name_vec)), PallasPlutusData::BoundedBytes(BoundedBytes(pilot_name_vec)), PallasPlutusData::BigInt(BigInt::Int(Int(last_move_latest_time)))] = - &ship_datum[..] - { - ShipDatum::Ok { - pos_x: TryFrom::::try_from(*pos_x).unwrap(), - pos_y: TryFrom::::try_from(*pos_y).unwrap(), - ship_token_name: AssetName(String::from_utf8(ship_name_vec.to_vec()).unwrap()), - pilot_token_name: AssetName( - String::from_utf8(pilot_name_vec.to_vec()).unwrap(), - ), - last_move_latest_time: TryFrom::::try_from( - *last_move_latest_time, - ) - .unwrap(), - } - } else { - ShipDatum::MalformedShipDatum - } - } else { - ShipDatum::MalformedShipDatum - } - } -} diff --git a/game/src/lib.rs b/game/src/lib.rs index 7a3c617..f11c00d 100644 --- a/game/src/lib.rs +++ b/game/src/lib.rs @@ -1,5 +1,7 @@ mod game; +mod queries; mod tests; +mod types; use clap::{Args, Subcommand}; use gpc_wallet::{context::Context, keystore, utils}; @@ -24,6 +26,12 @@ pub enum Command { MineAsteria(MineAsteriaArgs), /// Apply parameters and write game scripts DeployScripts(DeployScriptsArgs), + /// Show Asteria UTxO + ShowAsteria(ShowAsteriaArgs), + /// Show pellet UTxOs + ShowPellets(ShowPelletsArgs), + /// Show ship UTxOs + ShowShips(ShowShipsArgs), } impl GameCommand { @@ -66,6 +74,18 @@ impl GameCommand { let _ = game::deploy_scripts(args).await.unwrap(); Ok(()) } + Command::ShowAsteria(args) => { + let _ = queries::show_asteria(&db, args).await.unwrap(); + Ok(()) + } + Command::ShowPellets(args) => { + let _ = queries::show_pellets(&db, args).await.unwrap(); + Ok(()) + } + Command::ShowShips(args) => { + let _ = queries::show_ships(&db, args).await.unwrap(); + Ok(()) + } }, None => { log::info!(" Asteria game"); @@ -276,3 +296,7 @@ pub struct DeployScriptsArgs { )] pub params_path: String, } + +pub type ShowAsteriaArgs = DeployScriptsArgs; +pub type ShowPelletsArgs = DeployScriptsArgs; +pub type ShowShipsArgs = DeployScriptsArgs; diff --git a/game/src/queries.rs b/game/src/queries.rs new file mode 100644 index 0000000..59bd3b3 --- /dev/null +++ b/game/src/queries.rs @@ -0,0 +1,208 @@ +use crate::{types::*, ShowAsteriaArgs, ShowPelletsArgs, ShowShipsArgs}; +use anyhow::anyhow; +use colored::Colorize; +use gpc_wallet::sync; +use griffin_core::types::{ + compute_plutus_v2_script_hash, Address, Datum, PlutusScript, PolicyId, Value, +}; +use parity_scale_codec::Decode; +use sled::Db; + +/// Show Asteria UTxO. +pub async fn show_asteria(db: &Db, args: ShowAsteriaArgs) -> anyhow::Result<()> { + println!("\n###### ASTERIA ###########\n"); + + let params_json: String = std::fs::read_to_string(args.params_path) + .map_err(|e| anyhow!("Failed to read params file: {}", e))?; + + let params: ScriptsParams = + serde_json::from_str(¶ms_json).map_err(|e| anyhow!("Invalid params JSON: {}", e))?; + + let asteria_script_hex: &str = + &std::fs::read_to_string(params.scripts_directory.clone() + "asteria.txt") + .map_err(|e| anyhow!("Failed to read asteria script: {}", e))?; + + let wallet_unspent_tree = db.open_tree(sync::UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (input_ivec, owner_amount_datum_ivec) = x?; + let input = hex::encode(input_ivec); + let (owner_pubkey, value, datum_option) = + <(Address, Value, Option)>::decode(&mut &owner_amount_datum_ivec[..])?; + + let asteria_script: PlutusScript = + PlutusScript(hex::decode(asteria_script_hex).expect("Failed to decode asteria script")); + let asteria_hash: PolicyId = compute_plutus_v2_script_hash(asteria_script.clone()); + let asteria_address: Address = Address( + hex::decode("70".to_owned() + &hex::encode(asteria_hash)) + .map_err(|e| anyhow!("Failed to decode asteria address: {}", e))?, + ); + + if owner_pubkey == asteria_address { + let asteria_datum = datum_option.map(|d| AsteriaDatum::from(d)); + match asteria_datum { + None | Some(AsteriaDatum::MalformedAsteriaDatum) => { + println!( + "{}: datum {:?}, value: {}", + input, + asteria_datum, + value.normalize(), + ); + } + Some(AsteriaDatum::Ok { + ship_counter, + shipyard_policy, + }) => { + println!( + "{}:\n {} {:?}\n {} {:#?}\n {} {}\n", + input.bold(), + "SHIP COUNTER:".bold(), + ship_counter, + "SHIPYARD POLICY:".bold(), + shipyard_policy, + "VALUE:".bold(), + value.normalize(), + ); + } + } + } + } + + Ok(()) +} + +/// Show pellet UTxOs. +pub async fn show_pellets(db: &Db, args: ShowPelletsArgs) -> anyhow::Result<()> { + println!("\n###### PELLETS ###########\n"); + + let params_json: String = std::fs::read_to_string(args.params_path) + .map_err(|e| anyhow!("Failed to read params file: {}", e))?; + + let params: ScriptsParams = + serde_json::from_str(¶ms_json).map_err(|e| anyhow!("Invalid params JSON: {}", e))?; + + let pellet_script_hex: &str = + &std::fs::read_to_string(params.scripts_directory.clone() + "pellet.txt") + .map_err(|e| anyhow!("Failed to read pellet script: {}", e))?; + + let wallet_unspent_tree = db.open_tree(sync::UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (input_ivec, owner_amount_datum_ivec) = x?; + let input = hex::encode(input_ivec); + let (owner_pubkey, value, datum_option) = + <(Address, Value, Option)>::decode(&mut &owner_amount_datum_ivec[..])?; + + let pellet_script: PlutusScript = + PlutusScript(hex::decode(pellet_script_hex).expect("Failed to decode pellet script")); + let pellet_hash: PolicyId = compute_plutus_v2_script_hash(pellet_script.clone()); + let pellet_address: Address = Address( + hex::decode("70".to_owned() + &hex::encode(pellet_hash)) + .map_err(|e| anyhow!("Failed to decode pellet address: {}", e))?, + ); + + if owner_pubkey == pellet_address { + let pellet_datum = datum_option.map(|d| PelletDatum::from(d)); + match pellet_datum { + None | Some(PelletDatum::MalformedPelletDatum) => { + println!( + "{}: datum {:?}, value: {}", + input, + pellet_datum, + value.normalize(), + ); + } + Some(PelletDatum::Ok { + pos_x, + pos_y, + shipyard_policy, + }) => { + println!( + "{}:\n {} {:?}\n {} {:?}\n {} {:#?}\n {} {}\n", + input.bold(), + "POS X:".bold(), + pos_x, + "POS Y:".bold(), + pos_y, + "SHIPYARD POLICY:".bold(), + shipyard_policy, + "VALUE:".bold(), + value.normalize(), + ); + } + } + } + } + + Ok(()) +} + +/// Show ship UTxOs. +pub async fn show_ships(db: &Db, args: ShowShipsArgs) -> anyhow::Result<()> { + println!("\n###### SHIPS ###########\n"); + + let params_json: String = std::fs::read_to_string(args.params_path) + .map_err(|e| anyhow!("Failed to read params file: {}", e))?; + + let params: ScriptsParams = + serde_json::from_str(¶ms_json).map_err(|e| anyhow!("Invalid params JSON: {}", e))?; + + let spacetime_script_hex: &str = + &std::fs::read_to_string(params.scripts_directory.clone() + "spacetime.txt") + .map_err(|e| anyhow!("Failed to read spacetime script: {}", e))?; + + let wallet_unspent_tree = db.open_tree(sync::UNSPENT)?; + for x in wallet_unspent_tree.iter() { + let (input_ivec, owner_amount_datum_ivec) = x?; + let input = hex::encode(input_ivec); + let (owner_pubkey, value, datum_option) = + <(Address, Value, Option)>::decode(&mut &owner_amount_datum_ivec[..])?; + + let spacetime_script: PlutusScript = PlutusScript( + hex::decode(spacetime_script_hex).expect("Failed to decode spacetime script"), + ); + let spacetime_hash: PolicyId = compute_plutus_v2_script_hash(spacetime_script.clone()); + let spacetime_address: Address = Address( + hex::decode("70".to_owned() + &hex::encode(spacetime_hash)) + .map_err(|e| anyhow!("Failed to decode spacetime address: {}", e))?, + ); + + if owner_pubkey == spacetime_address { + let ship_datum = datum_option.map(|d| ShipDatum::from(d)); + match ship_datum { + None | Some(ShipDatum::MalformedShipDatum) => { + println!( + "{}: datum {:?}, value: {}", + input, + ship_datum, + value.normalize(), + ); + } + Some(ShipDatum::Ok { + pos_x, + pos_y, + ship_token_name, + pilot_token_name, + last_move_latest_time, + }) => { + println!( + "{}:\n {} {:?}\n {} {:?}\n {} {:?}\n {} {:?}\n {} {:?}\n {} {}\n", + input.bold(), + "POS X:".bold(), + pos_x, + "POS Y:".bold(), + pos_y, + "SHIP TOKEN NAME:".bold(), + ship_token_name.0, + "PILOT TOKEN NAME:".bold(), + pilot_token_name.0, + "LAST MOVE LATEST TIME:".bold(), + last_move_latest_time, + "VALUE:".bold(), + value.normalize(), + ); + } + } + } + } + + Ok(()) +} diff --git a/game/src/types.rs b/game/src/types.rs new file mode 100644 index 0000000..1b12950 --- /dev/null +++ b/game/src/types.rs @@ -0,0 +1,275 @@ +use griffin_core::{ + h224::H224, + pallas_codec::{ + minicbor, + utils::{Int, MaybeIndefArray::Indef}, + }, + pallas_crypto::hash::Hash as PallasHash, + pallas_primitives::babbage::{BigInt, BoundedBytes, Constr, PlutusData as PallasPlutusData}, + types::{AssetName, Datum, PlutusData, PolicyId}, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct ScriptsParams { + pub admin_policy: String, + pub admin_name: String, + pub fuel_per_step: u64, + pub initial_fuel: u64, + pub max_speed: Speed, + pub max_ship_fuel: u64, + pub max_asteria_mining: u64, + pub min_asteria_distance: u64, + pub ship_mint_lovelace_fee: u64, + pub scripts_directory: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub(crate) struct Speed { + pub distance: u64, + pub time: u64, +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum AsteriaDatum { + Ok { + ship_counter: u16, + shipyard_policy: PolicyId, + }, + MalformedAsteriaDatum, +} + +impl From for Datum { + fn from(asteria_datum: AsteriaDatum) -> Self { + Datum(PlutusData::from(PallasPlutusData::from(asteria_datum)).0) + } +} + +impl From for AsteriaDatum { + fn from(datum: Datum) -> Self { + <_>::from(PallasPlutusData::from(PlutusData(datum.0))) + } +} + +impl From for PallasPlutusData { + fn from(asteria_datum: AsteriaDatum) -> Self { + match asteria_datum { + AsteriaDatum::Ok { + ship_counter, + shipyard_policy, + } => PallasPlutusData::from(PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + ship_counter, + )))), + PallasPlutusData::BoundedBytes(BoundedBytes(shipyard_policy.0.to_vec())), + ] + .to_vec(), + ), + })), + AsteriaDatum::MalformedAsteriaDatum => { + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from(-1)))) + } + } + } +} + +impl From for AsteriaDatum { + fn from(data: PallasPlutusData) -> Self { + if let PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef(asteria_datum), + }) = data + { + if let [PallasPlutusData::BigInt(BigInt::Int(Int(ship_counter))), PallasPlutusData::BoundedBytes(BoundedBytes(shipyard_policy_vec))] = + &asteria_datum[..] + { + AsteriaDatum::Ok { + ship_counter: TryFrom::::try_from(*ship_counter).unwrap(), + shipyard_policy: H224::from(PallasHash::from(shipyard_policy_vec.as_slice())), + } + } else { + AsteriaDatum::MalformedAsteriaDatum + } + } else { + AsteriaDatum::MalformedAsteriaDatum + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum PelletDatum { + Ok { + pos_x: i16, + pos_y: i16, + shipyard_policy: PolicyId, + }, + MalformedPelletDatum, +} + +impl From for Datum { + fn from(pellet_datum: PelletDatum) -> Self { + Datum(PlutusData::from(PallasPlutusData::from(pellet_datum)).0) + } +} + +impl From for PelletDatum { + fn from(datum: Datum) -> Self { + <_>::from(PallasPlutusData::from(PlutusData(datum.0))) + } +} + +impl From for PallasPlutusData { + fn from(pellet_datum: PelletDatum) -> Self { + match pellet_datum { + PelletDatum::Ok { + pos_x, + pos_y, + shipyard_policy, + } => PallasPlutusData::from(PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + pos_x, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + pos_y, + )))), + PallasPlutusData::BoundedBytes(BoundedBytes(shipyard_policy.0.to_vec())), + ] + .to_vec(), + ), + })), + PelletDatum::MalformedPelletDatum => { + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from(-1)))) + } + } + } +} + +impl From for PelletDatum { + fn from(data: PallasPlutusData) -> Self { + if let PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef(pellet_datum), + }) = data + { + if let [PallasPlutusData::BigInt(BigInt::Int(Int(pos_x))), PallasPlutusData::BigInt(BigInt::Int(Int(pos_y))), PallasPlutusData::BoundedBytes(BoundedBytes(shipyard_policy_vec))] = + &pellet_datum[..] + { + PelletDatum::Ok { + pos_x: TryFrom::::try_from(*pos_x).unwrap(), + pos_y: TryFrom::::try_from(*pos_y).unwrap(), + shipyard_policy: H224::from(PallasHash::from(shipyard_policy_vec.as_slice())), + } + } else { + PelletDatum::MalformedPelletDatum + } + } else { + PelletDatum::MalformedPelletDatum + } + } +} + +#[derive(Debug, PartialEq, Eq, Clone)] +pub enum ShipDatum { + Ok { + pos_x: i16, + pos_y: i16, + ship_token_name: AssetName, + pilot_token_name: AssetName, + last_move_latest_time: u64, + }, + MalformedShipDatum, +} + +impl From for Datum { + fn from(ship_datum: ShipDatum) -> Self { + Datum(PlutusData::from(PallasPlutusData::from(ship_datum)).0) + } +} + +impl From for ShipDatum { + fn from(datum: Datum) -> Self { + <_>::from(PallasPlutusData::from(PlutusData(datum.0))) + } +} + +impl From for PallasPlutusData { + fn from(ship_datum: ShipDatum) -> Self { + match ship_datum { + ShipDatum::Ok { + pos_x, + pos_y, + ship_token_name, + pilot_token_name, + last_move_latest_time, + } => PallasPlutusData::from(PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + pos_x, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + pos_y, + )))), + PallasPlutusData::BoundedBytes(BoundedBytes( + ship_token_name.0.clone().into(), + )), + PallasPlutusData::BoundedBytes(BoundedBytes( + pilot_token_name.0.clone().into(), + )), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + last_move_latest_time, + )))), + ] + .to_vec(), + ), + })), + ShipDatum::MalformedShipDatum => { + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from(-1)))) + } + } + } +} + +impl From for ShipDatum { + fn from(data: PallasPlutusData) -> Self { + if let PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef(ship_datum), + }) = data + { + if let [PallasPlutusData::BigInt(BigInt::Int(Int(pos_x))), PallasPlutusData::BigInt(BigInt::Int(Int(pos_y))), PallasPlutusData::BoundedBytes(BoundedBytes(ship_name_vec)), PallasPlutusData::BoundedBytes(BoundedBytes(pilot_name_vec)), PallasPlutusData::BigInt(BigInt::Int(Int(last_move_latest_time)))] = + &ship_datum[..] + { + ShipDatum::Ok { + pos_x: TryFrom::::try_from(*pos_x).unwrap(), + pos_y: TryFrom::::try_from(*pos_y).unwrap(), + ship_token_name: AssetName(String::from_utf8(ship_name_vec.to_vec()).unwrap()), + pilot_token_name: AssetName( + String::from_utf8(pilot_name_vec.to_vec()).unwrap(), + ), + last_move_latest_time: TryFrom::::try_from( + *last_move_latest_time, + ) + .unwrap(), + } + } else { + ShipDatum::MalformedShipDatum + } + } else { + ShipDatum::MalformedShipDatum + } + } +} diff --git a/griffin-core/src/types.rs b/griffin-core/src/types.rs index 39278e9..43d5e4b 100644 --- a/griffin-core/src/types.rs +++ b/griffin-core/src/types.rs @@ -1001,6 +1001,24 @@ impl Value { } false } + + pub fn quantity_of(&self, policy: &PolicyId, name: &AssetName) -> u64 { + if let Value::Multiasset(_, ma) = self { + if let Some(assets) = ma.0.get(policy) { + if let Some(quantity) = assets.0.get(name) { + return *quantity; + } + } + } + 0 + } + + pub fn coin_of(&self) -> u64 { + match self { + Value::Coin(c) => *c, + Value::Multiasset(c, _) => *c, + } + } } impl From for AssetName { diff --git a/wallet/src/sync.rs b/wallet/src/sync.rs index 8b1e2df..bb3a9b7 100644 --- a/wallet/src/sync.rs +++ b/wallet/src/sync.rs @@ -38,7 +38,7 @@ const BLOCKS: &str = "blocks"; const BLOCK_HASHES: &str = "block_hashes"; /// The identifier for the unspent tree in the db. -const UNSPENT: &str = "unspent"; +pub const UNSPENT: &str = "unspent"; /// The identifier for the spent tree in the db. const SPENT: &str = "spent";