diff --git a/Cargo.lock b/Cargo.lock index 024a773..1129ad1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3321,6 +3321,27 @@ dependencies = [ "byteorder", ] +[[package]] +name = "game" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap", + "griffin-core", + "griffin-wallet", + "hex", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-cli", + "sc-keystore", + "serde", + "serde_json", + "sled", + "sp-core", + "sp-runtime", +] + [[package]] name = "generic-array" version = "0.12.4" @@ -3544,9 +3565,11 @@ dependencies = [ "docify", "futures", "futures-timer", + "game", "griffin-core", "griffin-partner-chains-runtime", "griffin-rpc", + "griffin-wallet", "jsonrpsee", "partner-chains-cli", "partner-chains-node-commands", @@ -3570,6 +3593,7 @@ dependencies = [ "sp-runtime", "sp-timestamp", "substrate-build-script-utils", + "tokio", ] [[package]] @@ -3639,6 +3663,7 @@ dependencies = [ "jsonrpsee", "log", "parity-scale-codec", + "sc-cli", "sc-keystore", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 9e6d424..7e12abf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "node", "runtime", "wallet", + "game", "toolkit/partner-chains-cli", "toolkit/cli/commands", "toolkit/cli/node-commands", @@ -76,6 +77,8 @@ zero-prefixed-literal = { level = "allow", priority = 2 } # 00_1000_0 griffin-core = { default-features = false, path = "griffin-core" } griffin-partner-chains-runtime = { path = "./runtime", default-features = false } griffin-rpc = { default-features = false, path = "griffin-rpc" } +griffin-wallet = { default-features = false, path = "wallet" } +game = { default-features = false, path = "game" } clap = { version = "4.5.13" } derive-new = { version = "0.7.0" } docify = { version = "0.2.9" } diff --git a/README.md b/README.md index 9514131..080c78e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ This repository will contain a reference implementation of a Substrate partnerch - [Start a chain with docker](#build-and-run-with-docker) -- [Use-case: Asteria game]() +- [Use-case: Asteria game](#use-case-asteria-game) - [Setting up a partner chain](#setting-up-a-partner-chain) @@ -92,7 +92,7 @@ Make sure that the endpoint you are connecting to matches the node's being run i ## Use-case: Asteria game As the result of the discovery process, after surveying many developers, it was decided that the use case for the partnerchain reference implementation would be a game. We chose the game Asteria which consists of ships moving across a board to find the Asteria prize and collect a portion of it. -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 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 [detailed instructions](./game/README.md) on how to run them. ## Setting up a Partner Chain diff --git a/game/Cargo.toml b/game/Cargo.toml new file mode 100644 index 0000000..fd4c41f --- /dev/null +++ b/game/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "game" +description = "Game implementation" +version = "0.1.0" +repository.workspace = true +edition.workspace = true + +[dependencies] +anyhow = { workspace = true } +clap = { features = ["derive"], workspace = true } +griffin-core = { workspace = true } +griffin-wallet = { workspace = true } +hex = { workspace = true } +jsonrpsee = { workspace = true } +log = { workspace = true } +parity-scale-codec = { workspace = true } +sc-cli = { workspace = true } +sc-keystore = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sp-core ={ workspace = true } +sp-runtime ={ workspace = true } + +sled = "0.34.7" \ No newline at end of file diff --git a/game/README.md b/game/README.md new file mode 100644 index 0000000..1d8949d --- /dev/null +++ b/game/README.md @@ -0,0 +1,107 @@ +# Game: Asteria + +As part of the discovery process, it was decided that the use case for the partnerchain reference implementation would be a game. We decided on implementing Asteria which is a simple game about that showcases the capabilities of the eUTxO model. +The game consists of ships moving across a two-dimension grid to find the Asteria, which holds a prize pool that the players can redeem a portion of. In the grid there are also pellets that hold fuel for the ships to collect. The operations of the game are: create a ship, move a ship, gather fuel and mine asteria. + +The game is implemented through commands that build the transactions for each operation. A more in depth explanation of the game can be found in the [design document](./onchain/docs/design/design.md). + +## Game usage + +Each of the following actions must be run with a running instance of the node. + +### Deploy Scripts + +This command reads all the script parameters from `game/src/deploy_params.json` and applies them to the generic (parameterized) +scripts, writing the resulting ones in their respective files, inside the `scripts` directory. + +```console +./target/release/griffin-partner-chains-node game deploy-scripts +--params +``` + +#### Arguments details: + +*params*: path to the json file containing all the script parameters. + +### Create Ship + +This command creates the player’s Ship. The transaction also mints the initial ship’s fuel, the ship and pilot tokens, and pays an inscription fee that is added to the total prize in the Asteria UTxO. The pilot token goes back to the wallet input owner, and serves as a proof of the ownership of the Ship. + +```console +./target/release/griffin-partner-chains-node game create-ship +--input +--witness +--pos-x +--pos-y +--ttl +``` +#### Arguments details: + +- *input*: a wallet input that must be consumed to pay for the minimal amount of coin in the Ship output and the fee added to the Asteria accumulated rewards. +- *witness*: public key of the input owner. If omitted, Shawn’s pub key is the default value, since this makes it easier to test transactions in a `dev` environment. +- *pos-x*: initial “x” coordinate of the Ship output. +- *pos-y*: initial “y” coordinate of the Ship output. +- *ttl*: the transaction’s time-to-live. The resulting POSIX time of the validity interval is used to set the initial `last-move-latest-time` field in the Ship output datum. + +### Gather Fuel + +This command moves fuel tokens from a pellet UTxO to a ship UTxO, only if they have the same position in the grid, as specified in the datums. The amount of fuel to gather is specified in the redeemer, and the total ship fuel must not exceed its maximum capacity. + +```console +./target/release/griffin-partner-chains-node game gather-fuel +--ship +--pellet +--witness +--fuel +--validity-interval-start +``` + +#### Arguments details: + +- *ship*: reference to the ship UTxO. +- *pellet*: reference to the pellet UTxO. +- *witness*: public key of the pilot token owner. This is necessary since the pilot UTxO must be provided as input to prove the ship ownership. If omitted, Shawn’s pub key is the default value. +- *fuel*: the amount of fuel to transfer from the pellet to the ship. +- *validity-interval-start*: start of the transaction’s validity interval. The corresponding POSIX must be greater than the `last-move-latest-time` field in the ship datum, in order to respect the speed limit of the last move. + +### Move Ship + +This command moves the ship to a different point in the grid (updates de `pos_x` and `pos_y` fields in the ship datum). The transaction also burns the fuel tokens consumed. + +```console +./target/release/griffin-partner-chains-node game move-ship +--ship +--witness +--pos-x +--pos-y +--validity-interval-start +--ttl +``` + +#### Arguments details: + +- *ship*: reference to the ship UTxO. +witness: public key of the pilot token owner. This is necessary since the pilot UTxO must be provided as input to prove the ship ownership. If omitted, Shawn’s pub key is the default value. +- *pos-x*: new “x” coordinate of the ship. +- *pos-y*: new “y” coordinate of the ship. +- *validity-interval-start*: start of the transaction’s validity interval. The corresponding POSIX must be greater than the `last-move-latest-time` field in the ship datum, in order to respect the speed limit of the last move. +- *ttl*: the transaction’s time-to-live. The resulting POSIX time of the validity interval is used to set the initial `last-move-latest-time` field in the Ship output datum. The manhattan distance travelled divided by the POSIX validity range must be less or equal to the max speed. + +### Mine Asteria + +This command can be triggered when the ship reaches Asteria, i.e., its coordinates are both zero. Then the ship owner can receive a percentage of the total prize given by (MAX_ASTERIA_MINING/100). This transaction also burns the ship and all remaining fuel tokens. + +```console +./target/release/griffin-partner-chains-node game mine-asteria +--ship +--witness +--validity-interval-start +``` + +#### Arguments details: + +- *ship*: reference to the ship UTxO. +- *witness*: public key of the pilot token owner. This is necessary since the pilot UTxO must be provided as input to prove the ship ownership. If omitted, Shawn’s pub key is the default value. +- *validity-interval-start*: start of the transaction’s validity interval. The corresponding POSIX must be greater than the `last-move-latest-time` field in the ship datum, in order to respect the speed limit of the last move. + + diff --git a/game/src/deploy_params.json b/game/src/deploy_params.json new file mode 100644 index 0000000..341d622 --- /dev/null +++ b/game/src/deploy_params.json @@ -0,0 +1,14 @@ +{ + "admin_policy": "516238dd0a79bac4bebe041c44bad8bf880d74720733d2fc0d255d28", + "admin_name": "asteriaAdmin", + "fuel_per_step": 1, + "initial_fuel": 30, + "max_speed": { + "distance": 1, + "time": 30000 + }, + "max_ship_fuel": 100, + "max_asteria_mining": 50, + "min_asteria_distance": 10, + "ship_mint_lovelace_fee": 3000000 +} diff --git a/game/src/game.rs b/game/src/game.rs new file mode 100644 index 0000000..04aea21 --- /dev/null +++ b/game/src/game.rs @@ -0,0 +1,1237 @@ +use crate::{CreateShipArgs, GatherFuelArgs, MineAsteriaArgs, MoveShipArgs}; +use anyhow::anyhow; +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, + Tx as PallasTransaction, + }, + Fragment, + }, + pallas_traverse::OriginalHash, + types::{ + compute_plutus_v2_script_hash, Address, AssetName, Datum, Input, Multiasset, Output, + PlutusData, PlutusScript, PolicyId, Redeemer, RedeemerTag, Transaction, VKeyWitness, Value, + }, + uplc::tx::{apply_params_to_script, SlotConfig}, +}; +use griffin_wallet::{ + cli::{ShowOutputsAtArgs, ShowOutputsWithAssetArgs}, + keystore, sync, +}; +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}; + +const ASTERIA_PARAMETERIZED: &str = "590cf901000032323232323232323232323232322232232232323232232323232253330163232323232323232533301e300c3020375400e264646464a666044602a60486ea80044c8c8c8c8c8c8c94ccc0a4c0700184c94ccc0b800454cc0ac094584c94ccc0bcc0c80084c94ccc0b0c064c0b8dd500089919191919192999819299981919baf300b303537540106e98cc894ccc0d0c09c0b040044c8c8cc00400400c894ccc0ec0044cc0f0cdd82601014000374c00697adef6c60132323232533303b3375e66012911000024c103d879800013304033760981014000374c00e00a2a66607666e3d22100002132533303c302f303e375400226608266ec13010140003042303f3754002008200864a666078a66608000229445280a6103d87a80001301d33041374c00297ae03233001001002225333041001133042337609801014000375006a97adef6c6013232323253330413375e6601e911000024c103d879800013304633760981014000375007200a2a66608266e3d22100002132533304230353044375400226608e66ec1301014000304830453754002008200864a666084606a002298103d87a80001302333047375000297ae03370000207226608c66ec0dd48011ba800133006006003375a60860066eb8c104008c114008c10c0044cc100cdd81ba9002374c0026600c00c0066eacc0f400cdd7181d801181f801181e8009919001191980080080111299981d8008a4c264a66607800229309919299981d1816981e1baa33008375c607860800086eb8c0f00084cc014014cc0fc00800454cc0ed2401326b65797320696e206173736f63696174697665206c697374206172656e277420696e20617363656e64696e67206f7264657200163040002303e001303e0013303933760981014000375005897adef6c60225333034337200040022980103d8798000153330343371e0040022980103d87a800014c103d87b800037566016606a6ea8c02cc0d4dd50080a511533033491146d7573745f6164645f666565203f2046616c73650014a02a666064a666064006294454cc0cd2411d6d7573745f686f6c645f61646d696e5f746f6b656e203f2046616c73650014a02a666064a666064004294454cc0cd2411e6d7573745f696e6372656d656e745f636f756e746572203f2046616c73650014a02a666064002294454cc0cd2401256d7573745f70726573657276655f73686970796172645f706f6c696379203f2046616c73650014a029405280a503371e6eb8c028c0d0dd500200a19b87375a602460666ea800ccdc000a2400466e21200033300a3756601060646ea8014dd7180898191baa029375c601060646ea80a4cc0800040ad4cccccc0d4004400454cc0b80a85854cc0b80a85854cc0b80a85854cc0b80a858c0c8c0bcdd50008a998168140b180298171baa001153302c0261630300013300100f23375e6018605a6ea8004c030c0b4dd5180198169baa008132533302a3018007132533302f001153302c0261613253330303033002132533302d301a302f375400226464646464a666064604a60686ea80044c8c8c94ccc0d54ccc0d40145288a9981b24811d6d7573745f696e7075745f736869705f746f6b656e203f2046616c73650014a02a66606aa66606a004294454cc0d92411f6d7573745f726573706563745f6d61785f6d696e696e67203f2046616c73650014a02a66606a002294454cc0d924011b6d7573745f70726573657276655f646174756d203f2046616c73650014a0294052819baf0280063232323371266e08dd6981e0009bad303c303d002337046eb4c0f0008dd6981e181e800981c1baa323230183303c375066e08dd6981e8011bad303d0013303c375066e08dd6981e981f0011bad303d303e0014bd70181c9baa32323232301b3303f375066e04cdc11bad3040004001337046eb4c10000800ccc0fcdd419b820030014bd701bad303f3040001303b3754607c60766ea801cdd6981e981f000981c9baa300348008c0e0dd5180118019bab300e30383754601c60706ea804cc0dcdd5180098011bab300d303737540124602a660726ea0004cc0e530010101004bd701199807000a450048810015330334913f65787065637420536f6d652870657263656e7461676529203d20726174696f6e616c2e6e6577286d61785f617374657269615f6d696e696e672c20313030290016533303130244832004530103d87a80001533303133710906400a4000260246606c60246606c6ea0cdc0a400004e6606c6ea0cdc024000906380a5eb812f5c0260246606c60246606c6ea009ccc0d9301021864004bd7025eb80c01ccc04005c8c8cc004004c8cc004004c94ccc0d0c088c0d8dd50008a5eb7bdb1804dd5981d181b9baa0013300f37566018606c6ea8c030c0d8dd500180b11299981c0008a5eb804cc0e4c0d8c0e8004cc008008c0ec004894ccc0dc004528099299981a19b8f33371890001b8d489045348495000375c607400491104534849500014a2266006006002607400266042002058a66666606c00220022a6605e0562c2a6605e0562c2a6605e0562c2a6605e0562c606660606ea800454cc0b80a458c018c0bcdd50008a998168138b181880099801008119baf300d302e3754002601a605c6ea8c010c0b8dd50048a99981518009980500892999815999815a999815980f18169baa300d302e3754601a605c6ea8c010c0b8dd50008a5014a294128899b8848000ccc018dd5980218171baa3004302e37540026eb8c034c0b8dd50129bae3004302e375404a29405288a99815a481296f7074696f6e2e69735f736f6d652861646d696e5f746f6b656e5f696e70757429203f2046616c73650014a04a666054603a60586ea80045288a5022323300100100322533303000114bd70099192999817180280109981980119802002000899802002000981a00118190009181718178009181698171817000911192999814980b98159baa0011480004dd6981798161baa0013253330293017302b3754002298103d87a80001323300100137566060605a6ea8008894ccc0bc004530103d87a80001323232533302e3371e00e6eb8c0c000c4c03ccc0ccdd4000a5eb804cc014014008dd6981800118198011818800998020018011119198008008019129998160008a60103d87a80001323232533302b3371e00c6eb8c0b400c4c030cc0c0dd3000a5eb804cc014014008dd598168011818001181700098129baa0123028302537540022a6604692013c65787065637420536f6d6528617374657269615f696e70757429203d2066696e645f696e70757428696e707574732c20617374657269615f726566290016323300200923375e600a604c6ea8004008c09cc090dd50051119198008008019129998140008a60103d87a80001323253330263005002130073302b0024bd70099802002000981600118150009ba5480008c09400454cc07d24123657870656374205370656e6428617374657269615f72656629203d20707572706f73650016375c604660480046eb4c088004c078dd50079bac3020302130210023758603e00260366ea8c078008c074c078004c064dd50008a4c2a6602e9211856616c696461746f722072657475726e65642066616c73650013656325333015300800115333019301837540082930a9980b0098b0a99980a98018008a99980c980c1baa004149854cc05804c5854ccc054c00800454ccc064c060dd50020a4c2a6602c0262c2a6602c0262c602c6ea800cdc3a40086e1d200253333330190011001153301200f16153301200f16153301200f16153301200f163300100300e225333010300330123754004264a66602a0022a660240042c26464a66602e0022a660280082c264a66603060360042930a9980a8028b19299999980e0008a9980a8028b0a9980a8028b0a9980a8028b0a9980a8028b09bae0013019001301900232533333301a0011533013003161533013003161533013003161375a0022a660260062c602e00260266ea800854cc04400458dc3a4000a66666602800220022a6601a0162c2a6601a0162c2a6601a0162c2a6601a0162c6eb4004dd6800a499c657870656374205b617374657269615f6f75747075745d203d0a202020202020202020206c6973742e66696c746572280a2020202020202020202020206f7574707574732c0a202020202020202020202020666e286f757470757429207b206f75747075742e61646472657373203d3d20617374657269615f696e7075742e6f75747075742e61646472657373207d2c0a20202020202020202020290049013f65787065637420496e6c696e65446174756d28617374657269615f6f75747075745f646174756d29203d20617374657269615f6f75747075742e646174756d0049014065787065637420617374657269615f6f75747075745f646174756d3a2041737465726961446174756d203d20617374657269615f6f75747075745f646174756d0049011972656465656d65723a204173746572696152656465656d657200490113646174756d3a2041737465726961446174756d005734ae7155ceaab9e5573eae815d0aba257481"; +const SPACETIME_PARAMETERIZED: &str = "592d94010000323232323232323232323232323232323232323232323223223222232232232232223253333330280021532323232323232323330283001302a37540142a660529211c52756e6e696e672032206172672076616c696461746f72206d696e740013232533302a3232323232323232325333033300c303537540102646464a66606c601e60706ea80384c8c8c94ccc0e4c048c0ecdd500089919191919299981f180c98201baa0011323232325333046001153304303e161325333047304a0021325333044301f30463754002264646464a666090604860946ea80044c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc16d4ccc16c0805288a9982e24811d6d7573745f62655f76616c69645f61737465726961203f2046616c73650014a02a6660b6a6660b6010294454cc171241206d7573745f6d696e745f65787065637465645f76616c7565203f2046616c73650014a02a6660b6a6660b600e294454cc171241216d7573745f726573706563745f6d696e5f64697374616e6365203f2046616c73650014a02a6660b6a6660b600c294454cc1712411b6d7573745f686176655f736869705f6e616d65203f2046616c73650014a02a6660b6a6660b600a294454cc1712411c6d7573745f686176655f70696c6f745f6e616d65203f2046616c73650014a02a6660b6a6660b6008294454cc1712411d6d7573745f686176655f6c61746573745f74696d65203f2046616c73650014a02a6660b6a6660b6006294454cc1712411c6d7573745f686f6c645f736869705f746f6b656e203f2046616c73650014a02a6660b6a6660b6004294454cc1712411e6d7573745f686f6c645f696e697469616c5f6675656c203f2046616c73650014a02a6660b6002294454cc17124011a6d7573745f686f6c645f335f617373657473203f2046616c73650014a029405280a5014a029405280a5014a06068664600200244a6660c0002290000981d99801001183180099198008009bab3025305e375403244a6660c0002297ae01323332223233001001003225333066001100313233068374e660d06ea4018cc1a0dd49bae30650013306837506eb4c1980052f5c06600600660d400460d00026eb8c17c004dd59830000998018019832001183100099b8733301f3756604660b86ea805c1252201044655454c0003f303433301e3756604460b66ea805809401ccdc39bad305d305e305e305e305e305a37540246eb4c174c168dd500819b8f375c60b860ba60ba60ba60b26ea8044010cdc79bae30193058375402000866e240e0c8cdc018009bad301e3058375402060026eb4c07cc160dd50081299982a99b88001480004c0cc0044004cdd79ba601f374c64646466660026666002a6660ac606490000a5eb7bdb1804c8c8cc0040052f5bded8c044a6660ba0022660bc66ec0dd48131ba60034bd6f7b630099191919299982e98049980501500109983119bb037520546e9801c01454ccc174cdc781500109983119bb037520546e9801c00c4cc188cdd81ba9002374c0026600c00c0066eacc17c00cdd7182e8011830801182f8009919800800a5eb7bdb180894ccc1700044cc174cdd81ba90074c010101004bd6f7b630099191919299982e18041980480580109983099bb037520169810101000051533305c3371e0160042660c266ec0dd4805a61010100003133061337606ea4008dd4000998030030019bad305e003375c60b800460c000460bc0020460089001023245044655454c0003c22225333059303200110041323233001001006225333060001133061337606ea4018dd3001a5eb7bdb1804c8c8c8c94ccc180c030cc0340280084cc194cdd81ba900a374c00e00a2a6660c066e3c0280084c94ccc184c0e8c18cdd500089983319bb0375201660ce60c86ea80040104010c94ccc1854ccc1940045288a5014c0103d87a80001303e33066374c00297ae03233001001002225333066001133067337606ea402cdd400525eb7bdb1804c8c8c8c94ccc198c048cc04c03c0084cc1accdd81ba900f375001c00a2a6660cc66e3c03c0084c94ccc19cc100c1a4dd500089983619bb0375202060da60d46ea80040104010c94ccc19cc1000045300103d87a8000130443306c375000297ae03370000201c2660d666ec0dd48011ba800133006006003375a60d00066eb8c198008c1a8008c1a00044cc194cdd81ba9002374c0026600c00c0066eacc18800cdd7183000118320011831000991900119198008008011129998300008a4c264a6660c200229309919299982f981c18309baa3300c375c60c260ca0086eb8c1840084cc014014cc19000800454cc1812401326b65797320696e206173736f63696174697665206c697374206172656e277420696e20617363656e64696e67206f7264657200163065002306300130630013305e337606ea4008dd4000a5eb7bdb180dd7a60103d879800022533305533720004002298103d8798000153330553371e0040022980103d87a800014c103d87b8000330024890550494c4f540030033004375a603660aa6ea804ccc005220104534849500030023003375a603460a86ea804888cdc500100091b9800123732660046ea00052201003001001222533333305700213232323232323300b0020013371491010128000025333053337100069007099b80483c80400c54ccc14ccdc4001a410004266e00cdc0241002800690068a9982a24929576861742061726520796f7520646f696e673f204e6f2049206d65616e2c20736572696f75736c792e001653330560011337149101035b5d2900004133714911035b5f2000375c60aa66600e00266ec1300102415d00375266e292210129000042233760980103422c2000375266601001000466e28dd7182b0009bae3057001375860a80046eb4c148004c8cdd81ba83052001374e60a60026ea80084c94ccc1500044cdc5245027b7d00002133714911037b5f2000375c60a664646600200200644a6660ae00220062664466ec130103422c2000375266601201260ae00466e29221023a20003330090093058002337146eb8c15c004dd7182c000982c80099801001182d00099bb04c10342207d0037520046eac0084c94ccc1500044cdc52441025b5d00002133714911035b5f2000375c60a666600a00266ec1300102415d0037520044466ec1300103422c2000375266600c00c00466e28dd7182a0009bae3055001375800426600a6eb40080044c8c8cdc524410268270000132333001001337006e3400920013371491101270000322253330533371000490000800899191919980300319b8000548004cdc599b80002533305633710004900a0a40c02903719b8b33700002a6660ac66e2000520141481805206e0043370c004901019b8300148080cdc70020011bae00222232330010010042253330540011004133003305600133002002305700122323300100100322533304e30270011337149110130000031533304e337100029000099b8a489012d0033002002302c00113300533708002900a19b8b3370066e1400520144818000cc0040048894ccc12ccdc4801240002002266600600666e1000920143371666e00cdc28012402890300008a9982481f0b180818251baa3011304a375402aa66608c603e60906ea80044c94ccc12c00454cc120110584c8c94ccc13400454cc128118584c8c94ccc13c00454cc130120584c8c94ccc14400454cc138128584c8c94ccc14c00454cc140130584c94ccc150c15c008526153305104d16325333333058001153305104d16153305104d16153305104d161375a0022a660a209a2c60aa00260aa00464a6666660ac0022a6609e0962c2a6609e0962c2a6609e0962c2a6609e0962c26eb8004c14c004c14c008c94cccccc15000454cc1341245854cc1341245854cc1341245854cc134124584dd7000982880098288011929999998290008a998258238b0a998258238b0a998258238b09bad001153304b04716304f001304f0023253333330500011533049045161533049045161533049045161375a0022a6609208a2c609a00260926ea800454cc11c10c594cccccc134004400454cc1181085854cc1181085854cc1181085854cc11810858c128c11cdd50008a99822a493165787065637420496e6c696e65446174756d28736869705f646174756d29203d20736869705f73746174652e646174756d00163007304637540022a6608807e2c6090002646600200202644a66608e002297ae0132325333045325333046302230483754002266e3c04cdd7182618249baa00114a0601c60906ea8c038c120dd50010998250011980200200089980200200098258011824800a999820180c98211baa0011325333045001153304203c16132325333047001153304403e161325333048304b002149854cc1140fc58c94cccccc13000454cc1140fc5854cc1140fc5854cc1140fc5854cc1140fc584dd7000982480098248011929999998250008a9982181e8b0a9982181e8b0a9982181e8b09bad001153304303d163047001304337540022a660820762ca66666608e00220022a660800742c2a660800742c2a660800742c2a660800742c608860826ea800454cc0fd2413e65787065637420496e6c696e65446174756d28617374657269615f646174756d29203d20617374657269615f696e7075742e6f75747075742e646174756d0016300130403754600e60806ea80108c10cc110c110004c06cccc004dd59802981f1baa3005303e37540046eb8c010c0f8dd50141bae3005303e375405044464a66607c603460806ea8004520001375a608860826ea8004c94ccc0f8c068c100dd50008a60103d87a8000132330010013756608a60846ea8008894ccc110004530103d87a8000132323253330433371e00e6eb8c11400c4c080cc120dd4000a5eb804cc014014008dd698228011824001182300099804001801181f981e1baa001153303a03116323300100100c22533303e00114c103d87a800013232533303c32533303d3016303f375400229404cdc79bae304330403754002056600a607e6ea8c014c0fcdd51803181f9baa00213019330410024bd70099802002000982100118200009181f0009181e981f000899299981d8008a9981c01a8b099299981e181f8010a99981c19b8748004dd6981d8008a51153303903616153303903616303d001325333037301330393754002297adef6c6013756607a60746ea8004cc00400800c88c8cc00400400c894ccc0f40045300103d87a80001323232533303c3371e00c6eb8c0f800c4c064cc104dd3000a5eb804cc014014008dd5981f0011820801181f800991980080080211299981d0008a5eb7bdb1804c8c8c8c94ccc0e8cdc7a44100002100313303f337606ea4008dd3000998030030019bab303c003375c6074004607c00460780026eb8c0e4c0d8dd50040a9981a24926657870656374204d696e742873686970796172645f706f6c69637929203d20707572706f7365001630383039303930390023756606e002606e606e0046eb0c0d4004c0d4c0d4008dd6181980098179baa303200230313032001302d37540182930a99815a4811856616c696461746f722072657475726e65642066616c7365001365632533302a30030011533302e302d37540042930a998158148b0a99981518030008a99981718169baa002149854cc0ac0a45854cc0ac0a458c0acdd5000a99999981800588058a998148138b0a998148138b0a998148138b0a998148138b0a99814a491d52756e6e696e672033206172672076616c696461746f72207370656e640013323232232322533302f323232323232323232323232323232323232323232325333045301e30473754002264a66608c604460906ea80584c8c94ccc120c084c128dd5000899192999825181318261baa001132323232323232323232323232325323233305a303300d1323232325333062001153305f05016132533306330660021325333060303b30623754002264646464a6660c8608060cc6ea80044c8c94ccc198c108c1a0dd50008991919192999835182198361baa001132323232323232323232323253330765333076305230263301f044230293035307a3754606c60f46ea80045288a9983ba481236d7573745f7370656e645f6f6e655f7363726970745f696e707574203f2046616c73650014a02a6660eca6660ec054294454cc1dd241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660eca6660ec008294454cc1dd2411d6d7573745f686176655f656e6f7567685f6675656c203f2046616c73650014a02a6660eca6660ec00c294454cc1dd2411e6d7573745f726573706563745f6d61785f7370656564203f2046616c73650014a02a6660eca6660ec00a294454cc1dd241206d7573745f726573706563745f6c61746573745f74696d65203f2046616c73650014a02a6660eca6660ec004294454cc1dd241216d7573745f73756274726163745f6675656c5f746f6b656e73203f2046616c73650014a02a6660eca6660ec010294454cc1dd2411c6d7573745f6275726e5f7370656e745f6675656c203f2046616c73650014a02a6660eca6660ec014294454cc1dd241156d7573745f7570646174655f78203f2046616c73650014a02a6660eca6660ec012294454cc1dd241156d7573745f7570646174655f79203f2046616c73650014a02a6660eca6660ec00e294454cc1dd2411f6d7573745f7570646174655f75707065725f626f756e64203f2046616c73650014a02a6660eca6660ec016294454cc1dd241216d7573745f70726573657276655f70696c6f745f746f6b656e203f2046616c73650014a02a6660eca6660ec006294454cc1dd2411c6d7573745f686f6c645f736869705f746f6b656e203f2046616c73650014a02a6660ec002294454cc1dd24011a6d7573745f686f6c645f335f617373657473203f2046616c73650014a029405280a5014a029405280a5014a029405280a5014a0609e604460486eacc0d0c1e0dd500b99b8733301f3756606660ee6ea80581912201044655454c0033702014018609e66603c6eacc0c8c1d8dd500a81481b19b8900a008301900d32323371266e08dd6983c0011bad30783079001337046eb4c1e0004dd6983c183c801183a1baa02c3073375460ec60e66ea801ccdc39bad307530763076307630763072375401c012602a609800c66e1cdd6981618381baa00c3370006402266e1cdd6981518379baa00b3370006602266e3c0b0dd7183898391839183918371baa00a3330153756605260da6ea8c0a4c1b4dd501102d2441044655454c00153306b4915865787065637420536f6d6528737065656429203d0a20202020202020202020726174696f6e616c2e6e65772864697374616e63652c2074785f6c61746573745f74696d65202d2074785f6561726c696573745f74696d65290016330260023370200600a66e08004140c8cdc0180080698008061299983419b88001480004c1180044004dd6983618349baa001153306705c16302330683754604860d06ea80b8dd6983518339baa001153306505916302130663754604260cc6ea80b0cc0e40041594cccccc1a4004400454cc1881545854cc1881545854cc1881545854cc18815458c198c18cdd50008a998308298b180498311baa00115330600511630640013300602923375e603860c26ea8004c070c184dd5180e98309baa016375a60c460c60046eb4c184004c174dd50170a99982d181b0068991929998300008a9982e8270b09929998309832001099299982f181c98301baa0011323232325333062303b306437540022646464a6660ca608060ce6ea80044c8c8c8c94ccc1a4c114c1acdd5000899191919191919191929998392999839182698111980d820118129818983b1baa303230763754002294454cc1cd2401246d7573745f7370656e645f74776f5f7363726970745f696e70757473203f2046616c73650014a02a6660e4a6660e401e294454cc1cd2411c6d7573745f62655f76616c69645f70656c6c6574203f2046616c73650014a02a6660e4a6660e404c294454cc1cd241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660e4a6660e4010294454cc1cd241216d7573745f686176655f70656c6c65745f706f736974696f6e203f2046616c73650014a02a6660e4a6660e4006294454cc1cd2411c6d7573745f6164645f6675656c5f746f6b656e73203f2046616c73650014a02a6660e4a6660e400e294454cc1cd241206d7573745f6e6f745f6578636565645f6361706163697479203f2046616c73650014a02a6660e4a6660e400a294454cc1cd241206d7573745f726573706563745f6c61746573745f74696d65203f2046616c73650014a02a6660e4a6660e400c294454cc1cd2411b6d7573745f70726573657276655f646174756d203f2046616c73650014a02a6660e4a6660e4008294454cc1cd2411c6d7573745f686f6c645f736869705f746f6b656e203f2046616c73650014a02a6660e4a6660e4004294454cc1cd2411a6d7573745f686f6c645f335f617373657473203f2046616c73650014a02a6660e4002294454cc1cd24011c6d7573745f6e6f745f6d696e745f746f6b656e73203f2046616c73650014a029405280a5014a029405280a5014a0294052819baf374c603a07698101a000304a301d301f3756605e60e66ea8050cdc399980d1bab302e307237540260be911044655454c003370000c02a60946660326eacc0b4c1c4dd5009012018980a9bad30733070375400a66ebc034110cdc499b80002011055533306a3370e0626eb4c0a0c1b4dd5002099b8702f375a605260da6ea801052819980a1bab3028306c3754605060d86ea8084165221044655454c00153306a05e163026306b3754604c60d66ea80c54ccc19cc100c1a4dd500089929998360008a9983482f8b0991929998370008a998358308b0991929998380008a998368318b0992999838983a0010a4c2a660dc0c82c64a6666660ea0022a660dc0c82c2a660dc0c82c2a660dc0c82c2a660dc0c82c26eb8004c1c8004c1c8008c94cccccc1cc00454cc1b01885854cc1b01885854cc1b0188584dd68008a998360310b183800098380011929999998388008a998350300b0a998350300b0a998350300b09bad001153306a06016306e001306a37540022a660d00bc2ca6666660dc00220022a660ce0ba2c2a660ce0ba2c2a660ce0ba2c2a660ce0ba2c60d660d06ea800454cc1992413c65787065637420496e6c696e65446174756d2870656c6c65745f646174756d29203d2070656c6c65745f696e7075742e6f75747075742e646174756d0016300e30673754604660ce6ea8008c10cccc038dd5981118331baa3022306637540026eb8c084c198dd50281bae3022306637540a060d060ca6ea800454cc18d2401ff65787065637420536f6d652870656c6c65745f696e70757429203d0a202020202020202020206c6973742e66696e64280a202020202020202020202020696e707574732c0a202020202020202020202020666e28696e70757429207b0a20202020202020202020202020207768656e20696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c206973207b0a20202020202020202020202020202020566572696669636174696f6e4b657943726564656e7469616c285f29202d3e2046616c73650a2020202020202020202020202020202053637269707443726564656e7469616c28616464725f7061796d656e6b7429202d3e0a202020202020202020202020202020202020616464725f7061796d656e74203d3d2070656c6c65745f76616c696461746f725f616464726573730a20202020202020202020202020207d0a2020202020202020202020207d2c0a202020202020202020202900163301b02f2325333063303c3065375400229404cdc79bae3069306637540020a6604060ca6ea8c080c194dd5181098329baa00133037001054533333306700110011533060053161533060053161533060053161533060053163064306137540022a660be0a22c600e60c06ea800454cc17813c58c188004cc01009c8cdd7980d182f9baa001301a305f3754603660be6ea8050dd69830182e9baa02e1533305a303500d132533305b3034305d375400226464a6660ba607260be6ea80044c8c8c8c94ccc1854ccc184c0f0c044cc0280bc8c050c080c194dd5181098329baa00114a22a660c49201246d7573745f7370656e645f74776f5f7363726970745f696e70757473203f2046616c73650014a02a6660c2a6660c200c294454cc1892411d6d7573745f62655f76616c69645f61737465726961203f2046616c73650014a02a6660c2a6660c202a294454cc189241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660c2a6660c2008294454cc189241226d7573745f686176655f617374657269615f706f736974696f6e203f2046616c73650014a02a6660c2a6660c2006294454cc1892411c6d7573745f6275726e5f736869705f746f6b656e203f2046616c73650014a02a6660c2a6660c2002294454cc1892411d6d7573745f6275726e5f6675656c5f746f6b656e73203f2046616c73650014a02a6660c2004294454cc1892401206d7573745f726573706563745f6c61746573745f74696d65203f2046616c73650014a029405280a5014a029405281803981f1998059bab301f30633754603e60c66ea80601412201044655454c003007375a60ca60c46ea800ccdc4199804980501400a010a400066ebcdd31983119bb037500486ea00892f5bded8c06e98cc1892f7b6301010000010100004bd6f7b6300a9982f0290b180d182f9baa301a305f375404a607666600c6eacc068c178dd5180d182f1baa3061305e37540026eb8c064c178dd50241bae301a305e37540902a660b80a62c66028050464a6660b8606a60bc6ea8004528099b8f375c60c460be6ea8004128c064c178dd5180c982f1baa301a305e375400226464a6660b8a6660b8607060186600a0544601e603660c06ea8c070c180dd50008a51153305d491236d7573745f7370656e645f6f6e655f7363726970745f696e707574203f2046616c73650014a02a6660b8a6660b8020294454cc175241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660b8a6660b8004294454cc1752411c6d7573745f6275726e5f736869705f746f6b656e203f2046616c73650014a02a6660b8002294454cc17524011d6d7573745f6275726e5f6675656c5f746f6b656e73203f2046616c73650014a029405280a50300230393330063756603460bc6ea8c068c178dd5009825a441044655454c003371066600a600c04802003a90001b8733300430050230494881044655454c00371203044646600200200644a6660be002297ae013232533305d300500213306200233004004001133004004001306300230610012305d305e305e0012223253330593035305b37540022900009bad305f305c375400264a6660b2606a60b66ea80045300103d87a800013233001001375660c060ba6ea8008894ccc17c004530103d87a80001323232533305e3371e00e6eb8c18000c4c0eccc18cdd4000a5eb804cc014014008dd6983000118318011830800998068018011191980080080111299982d8008a5eb7bdb1804c8c8c8c94ccc16ccdc7a441000021003133060337606ea4008dd3000998030030019bab305d003375c60b600460be00460ba002600200244a6660b0002290000981999801001182d8009191980080080111299982c0008a5eb804c8ccc888c8cc00400400c894ccc178004400c4c8cc180dd3998301ba90063306037526eb8c174004cc180dd41bad305e0014bd7019801801983100118300009bae3057001375660b00026600600660b800460b4002600200244a6660aa002290000981819801001182c00092999827981418289baa300d30523754002294052898279baa021533304c3025304e37546600c03446466002002646600200264a6660a0605860a46ea800452f5bded8c026eacc158c14cdd5000998021bab300e30523754601c60a46ea800c014894ccc15000452f5c02660aa60a460ac0026600400460ae00244a6660a600229404c94ccc140cdc79bae305600201114a226600600600260ac00229445281119198008008019129998298008a60103d87a8000132323253330523371e00c6eb8c15000c4c0bccc15cdd3000a5eb804cc014014008dd5982a001182b801182a8009bae3050304d37540022a660969215d6578706563742053637269707443726564656e7469616c2873686970796172645f706f6c69637929203d0a202020202020736869705f696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c00163007304c3754600e60986ea8c020c130dd5000982718259baa001153304949013665787065637420536f6d6528736869705f696e70757429203d2066696e645f696e70757428696e707574732c20736869705f726566290016323300201623375e600e60986ea8004008c134c128dd500b9119198008008019129998270008a60103d87a800013232533304c300500213029330510024bd70099802002000982900118280008a99823a48120657870656374205370656e6428736869705f72656629203d20707572706f73650016304b304837540022a6608c92015865787065637420536f6d65286d61785f73706565645f726174696f6e616c29203d0a202020202020726174696f6e616c2e6e6577286d61785f73706565642e64697374616e63652c206d61785f73706565642e74696d6529001633001375a6004608e6ea80c0dd6980198239baa030225333045301e00114c103d87a80001533304533710002900009811198251811198251ba830230023304a3750604600297ae04bd7009811198251811198251ba80023304a375000297ae04bd70118248009182418248009bad30463047002375c608a002608a0046eb8c10c004c10c008dd6982080098208011bad303f001303b3754020607a607c607c607c0046eacc0f0004c0f0c0f0008dd6181d000981d181d0011bac303800130343754606e004606c606e00260646ea8004526153303049011856616c696461746f722072657475726e65642066616c7365001365632533302e30070011325333033001153303002816132325333035001153303202a1613253330363039002149854cc0cc0ac58c94cccccc0e800454cc0cc0ac5854cc0cc0ac5854cc0cc0ac584dd68008a998198158b181b800981b80119299999981c0008a998188148b0a998188148b0a998188148b09bad0011533031029163035001303137540042a66605c6014002264a6660660022a660600502c264a666068606e0042930a998188148b19299999981c0008a998188148b0a998188148b0a998188148b09bad0011533031029163035001303137540042a66605c60120022a66606460626ea8008526153302f027161533302e300800115333032303137540042930a998178138b0a998178138b18179baa00153333330340011001153302d02516153302d02516153302d02516153302d025163300100202422533302b3004302d3754004264a6660600022a6605a0042c26464a6660640022a6605e0082c26464a6660680022a6606200c2c26464a66606c0022a660660102c26464a6660700022a6606a0142c264a66607260780042930a9981b0058b19299999981e8008a9981b0058b0a9981b0058b0a9981b0058b09bad001153303600b16303a001303a00232533333303b0011533034009161533034009161533034009161533034009161375c0026070002607000464a6666660720022a6606400e2c2a6606400e2c2a6606400e2c2a6606400e2c26eb8004c0d8004c0d8008c94cccccc0dc00454cc0c00145854cc0c00145854cc0c0014584dd68008a998180028b181a000981a00119299999981a8008a998170018b0a998170018b0a998170018b09bad001153302e003163032001302e37540042a660580022ca66666606001620162a660520442c2a660520442c2a660520442c2a660520442c605c60566ea8028dc3a40006e1d2006370e90021b8748008dd2a40006e052000370090011b884800054cc0840045854cc0840045854cc0840045854cc08400459240191496e636f72726563742072656465656d6572207479706520666f722076616c696461746f72207370656e642e0a2020202020202020202020202020202020202020446f75626c6520636865636b20796f7520686176652077726170706564207468652072656465656d657220747970652061732073706563696669656420696e20796f757220706c757475732e6a736f6e00375a0026eb4004dd68009bad001375c0026eb800524196657870656374205b736869705f6f75747075745d203d0a202020202020202020206c6973742e66696c746572280a2020202020202020202020206f7574707574732c0a202020202020202020202020666e286f757470757429207b206f75747075742e61646472657373203d3d20736869705f696e7075742e6f75747075742e61646472657373207d2c0a20202020202020202020290049013965787065637420496e6c696e65446174756d28736869705f6f75747075745f646174756d29203d20736869705f6f75747075742e646174756d0049013765787065637420736869705f6f75747075745f646174756d3a2053686970446174756d203d20736869705f6f75747075745f646174756d004901476578706563742046696e6974652874785f6561726c696573745f74696d6529203d2076616c69646974795f72616e67652e6c6f7765725f626f756e642e626f756e645f74797065004901456578706563742046696e6974652874785f6c61746573745f74696d6529203d2076616c69646974795f72616e67652e75707065725f626f756e642e626f756e645f747970650049012f6578706563742070656c6c65745f646174756d3a2050656c6c6574446174756d203d2070656c6c65745f646174756d004901ff65787065637420536f6d6528617374657269615f696e70757429203d0a202020202020202020206c6973742e66696e64280a202020202020202020202020696e707574732c0a202020202020202020202020666e28696e70757429207b0a20202020202020202020202020207768656e20696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c206973207b0a20202020202020202020202020202020566572696669636174696f6e4b657943726564656e7469616c285f29202d3e2046616c73650a2020202020202020202020202020202053637269707443726564656e7469616c28616464725f7061796d656d6e7429202d3e0a202020202020202020202020202020202020616464725f7061796d656e74203d3d20617374657269615f76616c696461746f725f616464726573730a20202020202020202020202020207d0a2020202020202020202020207d2c0a20202020202020202020290049011672656465656d65723a205368697052656465656d657200490110646174756d3a2053686970446174756d0049013265787065637420617374657269615f646174756d3a2041737465726961446174756d203d20617374657269615f646174756d00490159657870656374205b736869705f73746174655d203d0a202020202020202020207472616e73616374696f6e2e66696e645f7363726970745f6f757470757473286f7574707574732c2073686970796172645f706f6c696379290049012965787065637420736869705f646174756d3a2053686970446174756d203d20736869705f646174756d00490120657870656374205b285f2c202d31295d203d206d696e7465645f746f6b656e730049011a72656465656d65723a20536869707961726452656465656d6572005734ae7155ceaab9e5573eae815d0aba257481"; +const PELLET_PARAMETERIZED: &str = "590d270100003232323232323232323232323232222325333333012002153232323233300e30013010375400c2a6601e92011c52756e6e696e672032206172672076616c696461746f72206d696e7400132325333010323232323253330153008301737540082646464a666030601660346ea80284c94ccc0654ccc0654ccc064c030c06cdd5191980080080391299980f8008a60103d87a800013232533301d3013323330073756600260426ea8c004c084dd50019bae3024302137540306eb8c004c084dd500c118121812800898091981100125eb804cc010010004c08c008c0840045288a5014a22a660349201296f7074696f6e2e69735f736f6d652861646d696e5f746f6b656e5f696e70757429203f2046616c73650014a02a666032002294454cc06924011d6d7573745f6d696e745f6675656c5f746f6b656e73203f2046616c73650014a02940c038ccc00400800d2201044655454c001533301833710666002004006911044655454c00480005288a9980ca491d6d7573745f6275726e5f6675656c5f746f6b656e73203f2046616c73650014a044464a666036601e603a6ea8004520001375a6042603c6ea8004c94ccc06cc03cc074dd50008a60103d87a80001323300100137566044603e6ea8008894ccc084004530103d87a8000132323253330203371e00e6eb8c08800c4c054cc094dd4000a5eb804cc014014008dd698110011812801181180099198008008021129998100008a6103d87a80001323232533301f3371e00e6eb8c08400c4c050cc090dd3000a5eb804cc014014008dd5981080118120011811000991980080080191299980e0008a5eb7bdb1804c8c8c8c94ccc070cdc7a441000021003133021337606ea4008dd3000998030030019bab301e003375c60380046040004603c0026eb8c06cc060dd50020a9980b24922657870656374204d696e74286675656c5f706f6c69637929203d20707572706f736500163756603460366036603660360046eb0c064004c054dd5180c001180b980c00098099baa008149854cc0452411856616c696461746f722072657475726e65642066616c73650013656325333010300300115333014301337540042930a998088078b0a99980818020008a99980a18099baa002149854cc04403c5854cc04403c58c044dd5000a99999980b00388038a998078068b0a998078068b0a998078068b0a998078068b0a99807a491d52756e6e696e672033206172672076616c696461746f72207370656e64001332323223232253330153232323232533301a300e301c37540082646464a66603a6020603e6ea80044c8c8c94ccc080c050c088dd5000899191919192999812980c18139baa0131323232533302c001153302902316132533302d3030002132533302a3370e900218161baa00113232323232323253330315333031300d33016019232330010013233001001325333035302930373754002297adef6c6013756607660706ea8004cc04cdd5980b181b9baa30163037375400601c44a666072002297ae013303a3037303b00133002002303c00122533303800114a0264a66606a66e3cccdc6240006e35221045348495000375c607600491104534849500014a22660060060026076002294454cc0c924011d6d7573745f696e7075745f736869705f746f6b656e203f2046616c73650014a02a666062a666062006294454cc0c92411d6d7573745f686f6c645f61646d696e5f746f6b656e203f2046616c73650014a02a666062a666062008294454cc0c92411d6d7573745f686176655f656e6f7567685f6675656c203f2046616c73650014a02a666062a666062004294454cc0c9241206d7573745f70726f766964655f6675656c5f616d6f756e74203f2046616c73650014a02a666062002294454cc0c924011b6d7573745f70726573657276655f646174756d203f2046616c73650014a029405280a5014a066ebc014088cdc39998069bab30113032375400e01e9101044655454c003370201801460446660186eacc040c0c4dd50031bae3014303137540506eb8c040c0c4dd501419b8900800a3301d00102853333330330011001153302c02716153302c02716153302c02716153302c027163030302d37540022a660569213365787065637420496e6c696e65446174756d286f75745f646174756d29203d2070656c6c65745f6f75747075742e646174756d0016302f30303030302c37540022a660540482c605c002646600200201e44a66605a002297ae013232533302b3375e6022605c6ea8008c044c0b8dd5180698171baa00e133030002330040040011330040040013031002302f001375c6058605a605a60526ea8060dd6981598141baa0131533302530013300a00d253330263330265333026301930283754601860526ea8c030c0a4dd5180418149baa00114a02945282511301c3330043756601060526ea8c020c0a4dd50009bae300c302937540406eb8c020c0a4dd50100a5014a22a6604c921296f7074696f6e2e69735f736f6d652861646d696e5f746f6b656e5f696e70757429203f2046616c73650014a04a66604a6030604e6ea80045288a503330013756600a604c6ea8c014c098dd5003001a45044655454c00222325333026301a302837540022900009bad302c3029375400264a66604c603460506ea8004530103d87a8000132330010013756605a60546ea8008894ccc0b0004530103d87a80001323232533302b3371e00e6eb8c0b400c4c080cc0c0dd4000a5eb804cc014014008dd6981680118180011817000998020018011119198008008019129998148008a60103d87a8000132323253330283371e00c6eb8c0a800c4c074cc0b4dd3000a5eb804cc014014008dd59815001181680118158009bae3026302337540022a660429215b6578706563742053637269707443726564656e7469616c286675656c5f706f6c69637929203d0a20202020202070656c6c65745f696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c0016300530223754600a60446ea8c004c088dd5001118129813000981198101baa001153301e49013a65787065637420536f6d652870656c6c65745f696e70757429203d2066696e645f696e70757428696e707574732c2070656c6c65745f726566290016323300200523375e600860426ea8004008c088c07cdd50031119198008008019129998118008a60103d87a8000132325333021300500213016330260024bd7009980200200098138011812800918108008a9980da48122657870656374205370656e642870656c6c65745f72656629203d20707572706f736500163758603e604060400046eb0c078004c068dd5180e801180e180e800980c1baa001149854cc0592411856616c696461746f722072657475726e65642066616c7365001365632533301430070011325333019001153301601216132533301a301d002149854cc05c04c58c94cccccc07800454cc05c04c5854cc05c04c5854cc05c04c584dd68008a9980b8098b180d800980b9baa00215333014300800115333018301737540042930a9980a8088b0a9980a8088b180a9baa001533333301a0011001153301300f16153301300f16153301300f16153301300f163300100200e225333011300430133754004264a66602c0022a660260042c26464a6660300022a6602a0082c26464a6660340022a6602e00c2c264a666036603c0042930a9980c0038b19299999980f8008a9980c0038b0a9980c0038b0a9980c0038b0a9980c0038b09bae001301c001301c00232533333301d0011533016005161533016005161533016005161375a0022a6602c00a2c6034002603400464a6666660360022a660280062c2a660280062c2a660280062c26eb400454cc05000c58c060004c050dd50010a998090008b299999980b00388038a998078060b0a998078060b0a998078060b0a998078060b180a18089baa006370e90001b8748008dd2a40006e212000153300b00116153300b00116153300b00116153300b0011649191496e636f72726563742072656465656d6572207479706520666f722076616c696461746f72207370656e642e0a2020202020202020202020202020202020202020446f75626c6520636865636b20796f7520686176652077726170706564207468652072656465656d657220747970652061732073706563696669656420696e20796f757220706c757475732e6a736f6e00490195657870656374205b70656c6c65745f6f75747075745d203d0a2020202020202020202066696c746572280a2020202020202020202020206f7574707574732c0a202020202020202020202020666e286f757470757429207b206f75747075742e61646472657373203d3d2070656c6c65745f696e7075742e6f75747075742e61646472657373207d2c0a202020202020202020202900490129657870656374206f75745f646174756d3a2050656c6c6574446174756d203d206f75745f646174756d0049011872656465656d65723a2050656c6c657452656465656d657200490112646174756d3a2050656c6c6574446174756d0049011672656465656d65723a204675656c52656465656d6572005734ae7155ceaab9e5573eae815d0aba257481"; + +const ASTERIA_PATH: &str = "game/src/scripts/asteria.txt"; +const SPACETIME_PATH: &str = "game/src/scripts/spacetime.txt"; +const PELLET_PATH: &str = "game/src/scripts/pellet.txt"; +const DEPLOY_PARAMS_PATH: &str = "game/src/deploy_params.json"; + +pub async fn create_ship( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + slot_config: SlotConfig, + args: CreateShipArgs, +) -> anyhow::Result<()> { + log::debug!("The args are:: {:?}", args); + + let params_json: String = std::fs::read_to_string(DEPLOY_PARAMS_PATH)?; + 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(ASTERIA_PATH)?; + let spacetime_script_hex: &str = &std::fs::read_to_string(SPACETIME_PATH)?; + let pellet_script_hex: &str = &std::fs::read_to_string(PELLET_PATH)?; + + let asteria_script: PlutusScript = PlutusScript(hex::decode(asteria_script_hex).unwrap()); + 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)).unwrap()); + + let spacetime_script: PlutusScript = PlutusScript(hex::decode(spacetime_script_hex).unwrap()); + 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)).unwrap()); + + let pellet_script: PlutusScript = PlutusScript(hex::decode(pellet_script_hex).unwrap()); + let pellet_policy: PolicyId = compute_plutus_v2_script_hash(pellet_script.clone()); + + // Construct a template Transaction to push coins into later + let mut transaction = Transaction::from((Vec::new(), Vec::new())); + + if let Some((owner_pubkey, owner_value, _)) = sync::get_unspent(db, &args.input)? { + let asterias = sync::get_outputs_at( + db, + ShowOutputsAtArgs { + address: asteria_address.clone(), + }, + )?; + + if asterias.len() != 1 { + Err(anyhow!( + "There must be exactly one Asteria UTxO, but found {}", + asterias.len() + ))?; + } + let asteria_input = &asterias[0]; + let asteria_datum_option = asteria_input + .datum_option + .clone() + .map(|d| AsteriaDatum::from(d)); + + if let Some(AsteriaDatum::Ok { + ship_counter, + shipyard_policy, + }) = asteria_datum_option + { + // Asset Names + let ship_name = AssetName::from("SHIP".to_string() + &ship_counter.to_string()); + let pilot_name = AssetName::from("PILOT".to_string() + &ship_counter.to_string()); + let fuel_name = AssetName::from("FUEL".to_string()); + + // MINTS + let mint = Some( + Multiasset::from((spacetime_hash, ship_name.clone(), 1)) + + Multiasset::from((spacetime_hash, pilot_name.clone(), 1)) + + Multiasset::from((pellet_policy, fuel_name.clone(), 30)), + ); + + let mut inputs = vec![asteria_input.input.clone(), args.input]; + let ordered_inputs: Vec = { + // Lexicographically order inputs by tx_hash and index + inputs.sort_by(|a, b| { + if a.tx_hash == b.tx_hash { + a.index.cmp(&b.index) + } else { + a.tx_hash.cmp(&b.tx_hash) + } + }); + inputs.clone() + }; + + // BUILD REDEEMERS + let asteria_redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: ordered_inputs + .iter() + .position(|i| *i == asteria_input.input) + .unwrap() as u32, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + let ship_mint_redeemer = Redeemer { + tag: RedeemerTag::Mint, + index: 0, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + let pellet_mint_redeemer = Redeemer { + tag: RedeemerTag::Mint, + index: 1, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + + // BUILD DATUMS + let asteria_output_datum = PallasPlutusData::from(AsteriaDatum::Ok { + ship_counter: ship_counter + 1, + shipyard_policy, + }); + + let ship_datum = PallasPlutusData::from(ShipDatum::Ok { + pos_x: args.pos_x, + pos_y: args.pos_y, + ship_token_name: ship_name.clone(), + pilot_token_name: pilot_name.clone(), + last_move_latest_time: slot_config.zero_time + + args.ttl * slot_config.slot_length as u64, + }); + + transaction.transaction_body.inputs = inputs; + + let outputs = vec![ + Output { + address: owner_pubkey, + value: owner_value + - Value::Coin(params.ship_mint_lovelace_fee) + - Value::Coin(10) + + Value::from((spacetime_hash, pilot_name.clone(), 1)), + datum_option: None, + }, + Output { + address: asteria_address, + value: asteria_input.value.clone() + Value::Coin(params.ship_mint_lovelace_fee), + datum_option: Some(Datum(PlutusData::from(asteria_output_datum.clone()).0)), + }, + Output { + address: spacetime_address, + value: Value::Coin(10) + + Value::from((spacetime_hash, ship_name.clone(), 1)) + + Value::from((pellet_policy, fuel_name.clone(), 30)), + datum_option: Some(Datum(PlutusData::from(ship_datum.clone()).0)), + }, + ]; + + for output in outputs { + transaction.transaction_body.outputs.push(output.clone()); + } + + transaction.transaction_body.mint = mint; + transaction.transaction_body.ttl = Some(args.ttl); + + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + let cbor_bytes: Vec = babbage_tx_to_cbor(&pallas_tx); + let mtx: MintedTx = babbage_minted_tx_from_cbor(&cbor_bytes); + let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref()); + log::debug!("Original tx_body hash is: {:#x?}", tx_hash); + + let vkey: Vec = Vec::from(args.witness.0); + let public = Public::from_h256(args.witness); + let signature: Vec = Vec::from(keystore::sign_with(keystore, &public, tx_hash)?.0); + transaction.transaction_witness_set = + <_>::from(vec![VKeyWitness::from((vkey, signature))]); + + transaction.transaction_witness_set.redeemer = Some(vec![ + asteria_redeemer, + ship_mint_redeemer, + pellet_mint_redeemer, + ]); + transaction.transaction_witness_set.plutus_script = + Some(vec![asteria_script, spacetime_script, pellet_script]); + + log::debug!("Griffin transaction is: {:#x?}", transaction); + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + log::debug!("Babbage transaction is: {:#x?}", pallas_tx); + + // Send the transaction + let genesis_spend_hex = hex::encode(Encode::encode(&transaction)); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + if let Err(_) = genesis_spend_response { + Err(anyhow!("Node did not accept the transaction"))?; + } else { + println!( + "Transaction queued. When accepted, the following UTxOs will become available:" + ); + // Print new output refs for user to check later + let tx_hash = ::hash_of(&Encode::encode(&transaction)); + for (i, output) in transaction.transaction_body.outputs.iter().enumerate() { + let new_value_ref = Input { + tx_hash, + index: i as u32, + }; + let amount = &output.value; + + println!( + "{:?} worth {amount:?}.", + hex::encode(Encode::encode(&new_value_ref)) + ); + } + } + + Ok(()) + } else { + Err(anyhow!("Malformed Asteria Datum"))? + } + } else { + Err(anyhow!( + "Warning: User-specified utxo {:x?} not found in wallet database", + args.input + )) + } +} + +pub async fn gather_fuel( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: GatherFuelArgs, +) -> anyhow::Result<()> { + log::debug!("The args are:: {:?}", args); + + let spacetime_script_hex: &str = &std::fs::read_to_string(SPACETIME_PATH)?; + let pellet_script_hex: &str = &std::fs::read_to_string(PELLET_PATH)?; + + let spacetime_script: PlutusScript = PlutusScript(hex::decode(spacetime_script_hex).unwrap()); + let shipyard_policy: PolicyId = compute_plutus_v2_script_hash(spacetime_script.clone()); + + let pellet_script: PlutusScript = PlutusScript(hex::decode(pellet_script_hex).unwrap()); + let pellet_policy: PolicyId = compute_plutus_v2_script_hash(pellet_script.clone()); + + // Construct a template Transaction to push coins into later + let mut transaction = Transaction::from((Vec::new(), Vec::new())); + + let (spacetime_address, ship_value, ship_datum) = + sync::get_unspent(db, &args.ship)?.expect("Ship UTxO not found"); + let (pellet_address, pellet_value, pellet_datum) = + sync::get_unspent(db, &args.pellet)?.expect("Pellet UTxO not found"); + + let ship_datum_option = ship_datum.clone().map(|d| ShipDatum::from(d)); + + if let Some(ShipDatum::Ok { + pos_x: _, + pos_y: _, + ship_token_name: _, + pilot_token_name, + last_move_latest_time: _, + }) = ship_datum_option + { + let fuel_name = AssetName::from("FUEL".to_string()); + + let pilot_utxos = sync::get_outputs_with_asset( + db, + ShowOutputsWithAssetArgs { + policy: shipyard_policy, + name: pilot_token_name.0, + }, + )?; + + if pilot_utxos.len() == 0 { + Err(anyhow!("Pilot UTxO not found"))?; + } + let pilot_utxo = &pilot_utxos[0]; + + let mut inputs = vec![ + args.ship.clone(), + args.pellet.clone(), + pilot_utxo.input.clone(), + ]; + let ordered_inputs: Vec = { + // Lexicographically order inputs by tx_hash and index + inputs.sort_by(|a, b| { + if a.tx_hash == b.tx_hash { + a.index.cmp(&b.index) + } else { + a.tx_hash.cmp(&b.tx_hash) + } + }); + inputs.clone() + }; + transaction.transaction_body.inputs = inputs; + + // BUILD REDEEMERS + let ship_redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: ordered_inputs.iter().position(|i| *i == args.ship).unwrap() as u32, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef( + [PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef( + [PallasPlutusData::BigInt(BigInt::Int(Int( + minicbor::data::Int::from(args.fuel), + )))] + .to_vec(), + ), + })] + .to_vec(), + ), + })), + }; + + let pellet_redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: ordered_inputs + .iter() + .position(|i| *i == args.pellet) + .unwrap() as u32, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef( + [PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [PallasPlutusData::BigInt(BigInt::Int(Int( + minicbor::data::Int::from(args.fuel), + )))] + .to_vec(), + ), + })] + .to_vec(), + ), + })), + }; + + let outputs = vec![ + Output { + address: pilot_utxo.address.clone(), + value: pilot_utxo.value.clone(), + datum_option: pilot_utxo.datum_option.clone(), + }, + Output { + address: pellet_address, + value: pellet_value - Value::from((pellet_policy, fuel_name.clone(), args.fuel)), + datum_option: pellet_datum, + }, + Output { + address: spacetime_address, + value: ship_value + Value::from((pellet_policy, fuel_name, args.fuel)), + datum_option: ship_datum, + }, + ]; + + for output in outputs { + transaction.transaction_body.outputs.push(output.clone()); + } + + transaction.transaction_body.validity_interval_start = Some(args.validity_interval_start); + + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + let cbor_bytes: Vec = babbage_tx_to_cbor(&pallas_tx); + let mtx: MintedTx = babbage_minted_tx_from_cbor(&cbor_bytes); + let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref()); + log::debug!("Original tx_body hash is: {:#x?}", tx_hash); + + let vkey: Vec = Vec::from(args.witness.0); + let public = Public::from_h256(args.witness); + let signature: Vec = + Vec::from(crate::keystore::sign_with(keystore, &public, tx_hash)?.0); + transaction.transaction_witness_set = <_>::from(vec![VKeyWitness::from((vkey, signature))]); + + transaction.transaction_witness_set.redeemer = Some(vec![ship_redeemer, pellet_redeemer]); + transaction.transaction_witness_set.plutus_script = + Some(vec![spacetime_script, pellet_script]); + + log::debug!("Griffin transaction is: {:#x?}", transaction); + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + log::debug!("Babbage transaction is: {:#x?}", pallas_tx); + + // Send the transaction + let genesis_spend_hex = hex::encode(Encode::encode(&transaction)); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + if let Err(_) = genesis_spend_response { + Err(anyhow!("Node did not accept the transaction"))?; + } else { + println!( + "Transaction queued. When accepted, the following UTxOs will become available:" + ); + // Print new output refs for user to check later + let tx_hash = ::hash_of(&Encode::encode(&transaction)); + for (i, output) in transaction.transaction_body.outputs.iter().enumerate() { + let new_value_ref = Input { + tx_hash, + index: i as u32, + }; + let amount = &output.value; + + println!( + "{:?} worth {amount:?}.", + hex::encode(Encode::encode(&new_value_ref)) + ); + } + } + + Ok(()) + } else { + Err(anyhow!("Malformed Ship Datum"))? + } +} + +pub async fn move_ship( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + slot_config: SlotConfig, + args: MoveShipArgs, +) -> anyhow::Result<()> { + log::debug!("The args are:: {:?}", args); + + let spacetime_script_hex: &str = &std::fs::read_to_string(SPACETIME_PATH)?; + let pellet_script_hex: &str = &std::fs::read_to_string(PELLET_PATH)?; + + let spacetime_script: PlutusScript = PlutusScript(hex::decode(spacetime_script_hex).unwrap()); + let shipyard_policy: PolicyId = compute_plutus_v2_script_hash(spacetime_script.clone()); + + let pellet_script: PlutusScript = PlutusScript(hex::decode(pellet_script_hex).unwrap()); + let pellet_policy: PolicyId = compute_plutus_v2_script_hash(pellet_script.clone()); + + // Construct a template Transaction to push coins into later + let mut transaction = Transaction::from((Vec::new(), Vec::new())); + + let (spacetime_address, ship_value, ship_datum) = + sync::get_unspent(db, &args.ship)?.expect("Ship UTxO not found"); + + let ship_datum_option = ship_datum.clone().map(|d| ShipDatum::from(d)); + + if let Some(ShipDatum::Ok { + pos_x, + pos_y, + ship_token_name, + pilot_token_name, + last_move_latest_time: _, + }) = ship_datum_option + { + // Asset Names + let fuel_name = AssetName::from("FUEL".to_string()); + + let pilot_utxos = sync::get_outputs_with_asset( + db, + ShowOutputsWithAssetArgs { + policy: shipyard_policy, + name: pilot_token_name.clone().0, + }, + )?; + + if pilot_utxos.len() == 0 { + Err(anyhow!("Pilot UTxO not found"))?; + } + let pilot_utxo = &pilot_utxos[0]; + + let mut inputs = vec![args.ship.clone(), pilot_utxo.input.clone()]; + let ordered_inputs: Vec = { + // Lexicographically order inputs by tx_hash and index + inputs.sort_by(|a, b| { + if a.tx_hash == b.tx_hash { + a.index.cmp(&b.index) + } else { + a.tx_hash.cmp(&b.tx_hash) + } + }); + inputs.clone() + }; + transaction.transaction_body.inputs = inputs; + + // BURNS + let moved_manhattan_distance = (pos_x - args.pos_x).abs() + (pos_y - args.pos_y).abs(); + let moved_manhattan_distance_i64: i64 = moved_manhattan_distance.try_into().unwrap(); + let burn_fuel: Option> = Some(Multiasset::from(( + pellet_policy, + fuel_name.clone(), + -moved_manhattan_distance_i64, + ))); + + // BUILD REDEEMERS + let ship_redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: ordered_inputs.iter().position(|i| *i == args.ship).unwrap() as u32, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef( + [PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BigInt(BigInt::Int(Int( + minicbor::data::Int::from(args.pos_x - pos_x), + ))), + PallasPlutusData::BigInt(BigInt::Int(Int( + minicbor::data::Int::from(args.pos_y - pos_y), + ))), + ] + .to_vec(), + ), + })] + .to_vec(), + ), + })), + }; + + let pellet_redeemer = Redeemer { + tag: RedeemerTag::Mint, + index: 0, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + + let ship_output_datum = PallasPlutusData::from(ShipDatum::Ok { + pos_x: args.pos_x, + pos_y: args.pos_y, + ship_token_name, + pilot_token_name, + last_move_latest_time: slot_config.zero_time + + args.ttl * slot_config.slot_length as u64, + }); + + let outputs = vec![ + Output { + address: pilot_utxo.address.clone(), + value: pilot_utxo.value.clone(), + datum_option: pilot_utxo.datum_option.clone(), + }, + Output { + address: spacetime_address, + value: ship_value + - Value::from(( + pellet_policy, + fuel_name, + moved_manhattan_distance.try_into().unwrap(), + )), + datum_option: Some(Datum(PlutusData::from(ship_output_datum.clone()).0)), + }, + ]; + + for output in outputs { + transaction.transaction_body.outputs.push(output.clone()); + } + transaction.transaction_body.mint = burn_fuel; + transaction.transaction_body.validity_interval_start = Some(args.validity_interval_start); + transaction.transaction_body.ttl = Some(args.ttl); + + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + let cbor_bytes: Vec = babbage_tx_to_cbor(&pallas_tx); + let mtx: MintedTx = babbage_minted_tx_from_cbor(&cbor_bytes); + let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref()); + log::debug!("Original tx_body hash is: {:#x?}", tx_hash); + + let vkey: Vec = Vec::from(args.witness.0); + let public = Public::from_h256(args.witness); + let signature: Vec = + Vec::from(crate::keystore::sign_with(keystore, &public, tx_hash)?.0); + transaction.transaction_witness_set = <_>::from(vec![VKeyWitness::from((vkey, signature))]); + + transaction.transaction_witness_set.redeemer = Some(vec![ship_redeemer, pellet_redeemer]); + transaction.transaction_witness_set.plutus_script = + Some(vec![spacetime_script, pellet_script]); + + log::debug!("Griffin transaction is: {:#x?}", transaction); + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + log::debug!("Babbage transaction is: {:#x?}", pallas_tx); + + // Send the transaction + let genesis_spend_hex = hex::encode(Encode::encode(&transaction)); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + if let Err(_) = genesis_spend_response { + Err(anyhow!("Node did not accept the transaction"))?; + } else { + println!( + "Transaction queued. When accepted, the following UTxOs will become available:" + ); + // Print new output refs for user to check later + let tx_hash = ::hash_of(&Encode::encode(&transaction)); + for (i, output) in transaction.transaction_body.outputs.iter().enumerate() { + let new_value_ref = Input { + tx_hash, + index: i as u32, + }; + let amount = &output.value; + + println!( + "{:?} worth {amount:?}.", + hex::encode(Encode::encode(&new_value_ref)) + ); + } + } + + Ok(()) + } else { + Err(anyhow!("Malformed Ship Datum"))? + } +} + +pub async fn mine_asteria( + db: &Db, + client: &HttpClient, + keystore: &LocalKeystore, + args: MineAsteriaArgs, +) -> anyhow::Result<()> { + log::debug!("The args are:: {:?}", args); + + let asteria_script_hex: &str = &std::fs::read_to_string(ASTERIA_PATH)?; + let spacetime_script_hex: &str = &std::fs::read_to_string(SPACETIME_PATH)?; + let pellet_script_hex: &str = &std::fs::read_to_string(PELLET_PATH)?; + + let asteria_script: PlutusScript = PlutusScript(hex::decode(asteria_script_hex).unwrap()); + 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)).unwrap()); + + let spacetime_script: PlutusScript = PlutusScript(hex::decode(spacetime_script_hex).unwrap()); + let shipyard_policy: PolicyId = compute_plutus_v2_script_hash(spacetime_script.clone()); + + let pellet_script: PlutusScript = PlutusScript(hex::decode(pellet_script_hex).unwrap()); + let pellet_policy: PolicyId = compute_plutus_v2_script_hash(pellet_script.clone()); + + // Construct a template Transaction to push coins into later + let mut transaction = Transaction::from((Vec::new(), Vec::new())); + + let asterias = sync::get_outputs_at( + db, + ShowOutputsAtArgs { + address: asteria_address, + }, + )?; + if asterias.len() != 1 { + Err(anyhow!( + "There must be exactly one Asteria UTxO, but found {}", + asterias.len() + ))?; + } + let asteria = &asterias[0]; + + let (_spacetime_address, ship_value, ship_datum) = + sync::get_unspent(db, &args.ship)?.expect("Ship UTxO not found"); + + let ship_datum_option = ship_datum.clone().map(|d| ShipDatum::from(d)); + + if let Some(ShipDatum::Ok { + pos_x: _, + pos_y: _, + ship_token_name, + pilot_token_name, + last_move_latest_time: _, + }) = ship_datum_option + { + // Asset Names + let fuel_name = AssetName::from("FUEL".to_string()); + + let pilot_utxos = sync::get_outputs_with_asset( + db, + ShowOutputsWithAssetArgs { + policy: shipyard_policy, + name: pilot_token_name.clone().0, + }, + )?; + + if pilot_utxos.len() == 0 { + Err(anyhow!("Pilot UTxO not found"))?; + } + let pilot_utxo = &pilot_utxos[0]; + + let mut inputs = vec![ + args.ship.clone(), + asteria.input.clone(), + pilot_utxo.input.clone(), + ]; + let ordered_inputs: Vec = { + // Lexicographically order inputs by tx_hash and index + inputs.sort_by(|a, b| { + if a.tx_hash == b.tx_hash { + a.index.cmp(&b.index) + } else { + a.tx_hash.cmp(&b.tx_hash) + } + }); + inputs.clone() + }; + transaction.transaction_body.inputs = inputs; + + // BURNS + let ship_fuel = quanity_of(&ship_value, &pellet_policy, &fuel_name); + let ship_fuel_i64: i64 = ship_fuel.try_into().unwrap(); + let burns = Some( + Multiasset::from((shipyard_policy, ship_token_name.clone(), -1)) + + Multiasset::from((pellet_policy, fuel_name.clone(), -ship_fuel_i64)), + ); + + // BUILD REDEEMERS + let ship_redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: ordered_inputs.iter().position(|i| *i == args.ship).unwrap() as u32, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef( + [PallasPlutusData::Constr(Constr { + tag: 123, + any_constructor: None, + fields: Indef([].to_vec()), + })] + .to_vec(), + ), + })), + }; + + let asteria_redeemer = Redeemer { + tag: RedeemerTag::Spend, + index: ordered_inputs + .iter() + .position(|i| *i == asteria.input) + .unwrap() as u32, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + + let ship_burn_redeemer = Redeemer { + tag: RedeemerTag::Mint, + index: 0, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + + let pellet_redeemer = Redeemer { + tag: RedeemerTag::Mint, + index: 1, + data: PlutusData::from(PallasPlutusData::Constr(Constr { + tag: 122, + any_constructor: None, + fields: Indef([].to_vec()), + })), + }; + + let outputs = vec![ + Output { + address: pilot_utxo.address.clone(), + value: pilot_utxo.value.clone() + + Value::Coin(args.mine_coin_amount) + + Value::Coin(coin_of(&ship_value)), + datum_option: pilot_utxo.datum_option.clone(), + }, + Output { + address: asteria.address.clone(), + value: asteria.value.clone() - Value::Coin(args.mine_coin_amount), + datum_option: asteria.datum_option.clone(), + }, + ]; + + for output in outputs { + transaction.transaction_body.outputs.push(output.clone()); + } + transaction.transaction_body.mint = burns; + transaction.transaction_body.validity_interval_start = Some(args.validity_interval_start); + + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + let cbor_bytes: Vec = babbage_tx_to_cbor(&pallas_tx); + let mtx: MintedTx = babbage_minted_tx_from_cbor(&cbor_bytes); + let tx_hash: &Vec = &Vec::from(mtx.transaction_body.original_hash().as_ref()); + log::debug!("Original tx_body hash is: {:#x?}", tx_hash); + + let vkey: Vec = Vec::from(args.witness.0); + let public = Public::from_h256(args.witness); + let signature: Vec = + Vec::from(crate::keystore::sign_with(keystore, &public, tx_hash)?.0); + transaction.transaction_witness_set = <_>::from(vec![VKeyWitness::from((vkey, signature))]); + + transaction.transaction_witness_set.redeemer = Some(vec![ + asteria_redeemer, + ship_redeemer, + ship_burn_redeemer, + pellet_redeemer, + ]); + transaction.transaction_witness_set.plutus_script = + Some(vec![asteria_script, spacetime_script, pellet_script]); + + log::debug!("Griffin transaction is: {:#x?}", transaction); + let pallas_tx: PallasTransaction = <_>::from(transaction.clone()); + log::debug!("Babbage transaction is: {:#x?}", pallas_tx); + + // Send the transaction + let genesis_spend_hex = hex::encode(Encode::encode(&transaction)); + let params = rpc_params![genesis_spend_hex]; + let genesis_spend_response: Result = + client.request("author_submitExtrinsic", params).await; + log::info!( + "Node's response to spend transaction: {:?}", + genesis_spend_response + ); + if let Err(_) = genesis_spend_response { + Err(anyhow!("Node did not accept the transaction"))?; + } else { + println!( + "Transaction queued. When accepted, the following UTxOs will become available:" + ); + // Print new output refs for user to check later + let tx_hash = ::hash_of(&Encode::encode(&transaction)); + for (i, output) in transaction.transaction_body.outputs.iter().enumerate() { + let new_value_ref = Input { + tx_hash, + index: i as u32, + }; + let amount = &output.value; + + println!( + "{:?} worth {amount:?}.", + hex::encode(Encode::encode(&new_value_ref)) + ); + } + } + + Ok(()) + } else { + Err(anyhow!("Malformed Ship Datum"))? + } +} + +pub async fn deploy_scripts() -> anyhow::Result<()> { + let params_json: String = std::fs::read_to_string(DEPLOY_PARAMS_PATH)?; + let params: ScriptsParams = + serde_json::from_str(¶ms_json).map_err(|e| anyhow!("Invalid params JSON: {}", e))?; + + let asteria_params = PallasPlutusData::Array(Indef( + [ + PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BoundedBytes(BoundedBytes( + hex::decode(params.admin_policy.clone()).unwrap(), + )), + PallasPlutusData::BoundedBytes(BoundedBytes( + params.admin_name.clone().into(), + )), + ] + .to_vec(), + ), + }), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.ship_mint_lovelace_fee, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.max_asteria_mining, + )))), + ] + .to_vec(), + )); + + let asteria_script = PlutusScript( + apply_params_to_script( + asteria_params.encode_fragment().unwrap().as_slice(), + hex::decode(ASTERIA_PARAMETERIZED).unwrap().as_slice(), + ) + .unwrap(), + ); + let asteria_hash: PolicyId = compute_plutus_v2_script_hash(asteria_script.clone()); + + let pellet_params = PallasPlutusData::Array(Indef( + [PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BoundedBytes(BoundedBytes( + hex::decode(params.admin_policy.clone()).unwrap(), + )), + PallasPlutusData::BoundedBytes(BoundedBytes(params.admin_name.clone().into())), + ] + .to_vec(), + ), + })] + .to_vec(), + )); + + let pellet_script = PlutusScript( + apply_params_to_script( + pellet_params.encode_fragment().unwrap().as_slice(), + hex::decode(PELLET_PARAMETERIZED).unwrap().as_slice(), + ) + .unwrap(), + ); + let pellet_hash: PolicyId = compute_plutus_v2_script_hash(pellet_script.clone()); + + let spacetime_params = PallasPlutusData::Array(Indef( + [ + PallasPlutusData::BoundedBytes(BoundedBytes(pellet_hash.0.to_vec())), + PallasPlutusData::BoundedBytes(BoundedBytes(asteria_hash.0.to_vec())), + PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BoundedBytes(BoundedBytes( + hex::decode(params.admin_policy.clone()).unwrap(), + )), + PallasPlutusData::BoundedBytes(BoundedBytes( + params.admin_name.clone().into(), + )), + ] + .to_vec(), + ), + }), + PallasPlutusData::Constr(Constr { + tag: 121, + any_constructor: None, + fields: Indef( + [ + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.max_speed.distance, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.max_speed.time, + )))), + ] + .to_vec(), + ), + }), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.max_ship_fuel, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.fuel_per_step, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.initial_fuel, + )))), + PallasPlutusData::BigInt(BigInt::Int(Int(minicbor::data::Int::from( + params.min_asteria_distance, + )))), + ] + .to_vec(), + )); + + let spacetime_script = PlutusScript( + apply_params_to_script( + spacetime_params.encode_fragment().unwrap().as_slice(), + hex::decode(SPACETIME_PARAMETERIZED).unwrap().as_slice(), + ) + .unwrap(), + ); + + std::fs::write(PELLET_PATH, hex::encode(pellet_script.0)).unwrap(); + std::fs::write(ASTERIA_PATH, hex::encode(asteria_script.0)).unwrap(); + std::fs::write(SPACETIME_PATH, hex::encode(spacetime_script.0)).unwrap(); + 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, +} + +#[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 new file mode 100644 index 0000000..f134580 --- /dev/null +++ b/game/src/lib.rs @@ -0,0 +1,216 @@ +mod game; + +use clap::{Args, Subcommand}; +use griffin_core::types::Input; +use griffin_wallet::{context::Context, keystore, utils}; +use sp_core::H256; + +#[derive(Clone, Debug, Subcommand)] +pub enum GameCommand { + #[command(subcommand)] + Game(Command), +} + +#[derive(Clone, Debug, Subcommand)] +pub enum Command { + /// Create a ship to enter the game + CreateShip(CreateShipArgs), + /// Gather fuel using a ship and a fuel pellet + GatherFuel(GatherFuelArgs), + /// Move a ship to a new position + MoveShip(MoveShipArgs), + /// Mine Asteria using a ship + MineAsteria(MineAsteriaArgs), + /// Apply parameters and write game scripts + DeployScripts, +} + +impl GameCommand { + pub async fn run(&self) -> sc_cli::Result<()> { + let Context { + cli, + client, + db, + keystore, + slot_config, + .. + } = Context::::load_context().await.unwrap(); + match cli.command { + Some(GameCommand::Game(cmd)) => match cmd { + Command::CreateShip(args) => { + let _ = game::create_ship(&db, &client, &keystore, slot_config, args).await; + Ok(()) + } + Command::GatherFuel(args) => { + let _ = game::gather_fuel(&db, &client, &keystore, args).await; + Ok(()) + } + Command::MoveShip(args) => { + let _ = game::move_ship(&db, &client, &keystore, slot_config, args).await; + Ok(()) + } + Command::MineAsteria(args) => { + let _ = game::mine_asteria(&db, &client, &keystore, args).await; + Ok(()) + } + Command::DeployScripts => { + let _ = game::deploy_scripts().await; + Ok(()) + } + }, + None => { + log::info!(" Asteria game"); + Ok(()) + } + } + } +} + +#[derive(Debug, Args, Clone)] +pub struct CreateShipArgs { + /// An input to be consumed by this transaction. This argument may be specified multiple times. + #[arg(long, short, verbatim_doc_comment, value_parser = utils::input_from_string, required = true, value_name = "WALLET_OUTPUT_REF")] + pub input: Input, + + /// 32-byte H256 public key of an input owner. + /// Their pk/sk pair must be registered in the wallet's keystore. + #[arg(long, verbatim_doc_comment, value_parser = utils::h256_from_string, default_value = keystore::SHAWN_PUB_KEY, value_name = "PUBLIC_KEY")] + pub witness: H256, + + #[arg( + long, + verbatim_doc_comment, + required = true, + allow_negative_numbers = true, + value_name = "POS_X" + )] + pub pos_x: i16, + + #[arg( + long, + verbatim_doc_comment, + required = true, + allow_negative_numbers = true, + value_name = "POS_Y" + )] + pub pos_y: i16, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "TIME_TO_LIVE" + )] + pub ttl: u64, +} + +#[derive(Debug, Args, Clone)] +pub struct GatherFuelArgs { + #[arg(long, short, verbatim_doc_comment, value_parser = utils::input_from_string, required = true, value_name = "SHIP_OUTPUT_REF")] + pub ship: Input, + + #[arg(long, short, verbatim_doc_comment, value_parser = utils::input_from_string, required = true, value_name = "PELLET_OUTPUT_REF")] + pub pellet: Input, + + /// 32-byte H256 public key of an input owner. + /// Their pk/sk pair must be registered in the wallet's keystore. + #[arg(long, short, verbatim_doc_comment, value_parser = utils::h256_from_string, default_value = keystore::SHAWN_PUB_KEY, value_name = "PUBLIC_KEY")] + pub witness: H256, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "FUEL_AMOUNT" + )] + pub fuel: u64, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "VALIDITY_INTERVAL_START" + )] + pub validity_interval_start: u64, +} + +#[derive(Debug, Args, Clone)] +pub struct MoveShipArgs { + #[arg(long, short, verbatim_doc_comment, value_parser = utils::input_from_string, required = true, value_name = "SHIP_OUTPUT_REF")] + pub ship: Input, + + /// 32-byte H256 public key of an input owner. + /// Their pk/sk pair must be registered in the wallet's keystore. + #[arg(long, short, verbatim_doc_comment, value_parser = utils::h256_from_string, default_value = keystore::SHAWN_PUB_KEY, value_name = "PUBLIC_KEY")] + pub witness: H256, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + allow_negative_numbers = true, + value_name = "POS_X" + )] + pub pos_x: i16, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + allow_negative_numbers = true, + value_name = "POS_Y" + )] + pub pos_y: i16, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "VALIDITY_INTERVAL_START" + )] + pub validity_interval_start: u64, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "TIME_TO_LIVE" + )] + pub ttl: u64, +} + +#[derive(Debug, Args, Clone)] +pub struct MineAsteriaArgs { + #[arg(long, short, verbatim_doc_comment, value_parser = utils::input_from_string, required = true, value_name = "SHIP_OUTPUT_REF")] + pub ship: Input, + + /// 32-byte H256 public key of an input owner. + /// Their pk/sk pair must be registered in the wallet's keystore. + #[arg(long, short, verbatim_doc_comment, value_parser = utils::h256_from_string, default_value = keystore::SHAWN_PUB_KEY, value_name = "PUBLIC_KEY")] + pub witness: H256, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "VALIDITY_INTERVAL_START" + )] + pub validity_interval_start: u64, + + #[arg( + long, + short, + verbatim_doc_comment, + required = true, + value_name = "MINE_COIN_AMOUNT" + )] + pub mine_coin_amount: u64, +} diff --git a/game/src/scripts/asteria.txt b/game/src/scripts/asteria.txt new file mode 100644 index 0000000..e4baa9f --- /dev/null +++ b/game/src/scripts/asteria.txt @@ -0,0 +1 @@ +590d3c01000033332323232323232323232323232322232232232323232232323232253330163232323232323232533301e300c3020375400e264646464a666044602a60486ea80044c8c8c8c8c8c8c94ccc0a4c0700184c94ccc0b800454cc0ac094584c94ccc0bcc0c80084c94ccc0b0c064c0b8dd500089919191919192999819299981919baf300b303537540106e98cc894ccc0d0c09c0b040044c8c8cc00400400c894ccc0ec0044cc0f0cdd8261014000374c00697adef6c60132323232533303b3375e66012911000024c103d879800013304033760981014000374c00e00a2a66607666e3d22100002132533303c302f303e375400226608266ec13010140003042303f3754002008200864a666078a66608000229445280a6103d87a80001301d33041374c00297ae03233001001002225333041001133042337609801014000375006a97adef6c6013232323253330413375e6601e911000024c103d879800013304633760981014000375007200a2a66608266e3d22100002132533304230353044375400226608e66ec1301014000304830453754002008200864a666084606a002298103d87a80001302333047375000297ae03370000207226608c66ec0dd48011ba800133006006003375a60860066eb8c104008c114008c10c0044cc100cdd81ba9002374c0026600c00c0066eacc0f400cdd7181d801181f801181e8009919001191980080080111299981d8008a4c264a66607800229309919299981d1816981e1baa33008375c607860800086eb8c0f00084cc014014cc0fc00800454cc0ed2401326b65797320696e206173736f63696174697665206c697374206172656e277420696e20617363656e64696e67206f7264657200163040002303e001303e0013303933760981014000375005897adef6c60225333034337200040022980103d8798000153330343371e0040022980103d87a800014c103d87b800037566016606a6ea8c02cc0d4dd50080a511533033491146d7573745f6164645f666565203f2046616c73650014a02a666064a666064006294454cc0cd2411d6d7573745f686f6c645f61646d696e5f746f6b656e203f2046616c73650014a02a666064a666064004294454cc0cd2411e6d7573745f696e6372656d656e745f636f756e746572203f2046616c73650014a02a666064002294454cc0cd2401256d7573745f70726573657276655f73686970796172645f706f6c696379203f2046616c73650014a029405280a503371e6eb8c028c0d0dd500200a19b87375a602460666ea800ccdc000a2400466e21200033300a3756601060646ea8014dd7180898191baa029375c601060646ea80a4cc0800040ad4cccccc0d4004400454cc0b80a85854cc0b80a85854cc0b80a85854cc0b80a858c0c8c0bcdd50008a998168140b180298171baa001153302c0261630300013300100f23375e6018605a6ea8004c030c0b4dd5180198169baa008132533302a3018007132533302f001153302c0261613253330303033002132533302d301a302f375400226464646464a666064604a60686ea80044c8c8c94ccc0d54ccc0d40145288a9981b24811d6d7573745f696e7075745f736869705f746f6b656e203f2046616c73650014a02a66606aa66606a004294454cc0d92411f6d7573745f726573706563745f6d61785f6d696e696e67203f2046616c73650014a02a66606a002294454cc0d924011b6d7573745f70726573657276655f646174756d203f2046616c73650014a0294052819baf0280063232323371266e08dd6981e0009bad303c303d002337046eb4c0f0008dd6981e181e800981c1baa323230183303c375066e08dd6981e8011bad303d0013303c375066e08dd6981e981f0011bad303d303e0014bd70181c9baa32323232301b3303f375066e04cdc11bad3040004001337046eb4c10000800ccc0fcdd419b820030014bd701bad303f3040001303b3754607c60766ea801cdd6981e981f000981c9baa300348008c0e0dd5180118019bab300e30383754601c60706ea804cc0dcdd5180098011bab300d303737540124602a660726ea0004cc0e530010101004bd701199807000a450048810015330334913f65787065637420536f6d652870657263656e7461676529203d20726174696f6e616c2e6e6577286d61785f617374657269615f6d696e696e672c20313030290016533303130244832004530103d87a80001533303133710906400a4000260246606c60246606c6ea0cdc0a400004e6606c6ea0cdc024000906380a5eb812f5c0260246606c60246606c6ea009ccc0d9301021864004bd7025eb80c01ccc04005c8c8cc004004c8cc004004c94ccc0d0c088c0d8dd50008a5eb7bdb1804dd5981d181b9baa0013300f37566018606c6ea8c030c0d8dd500180b11299981c0008a5eb804cc0e4c0d8c0e8004cc008008c0ec004894ccc0dc004528099299981a19b8f33371890001b8d489045348495000375c607400491104534849500014a2266006006002607400266042002058a66666606c00220022a6605e0562c2a6605e0562c2a6605e0562c2a6605e0562c606660606ea800454cc0b80a458c018c0bcdd50008a998168138b181880099801008119baf300d302e3754002601a605c6ea8c010c0b8dd50048a99981518009980500892999815999815a999815980f18169baa300d302e3754601a605c6ea8c010c0b8dd50008a5014a294128899b8848000ccc018dd5980218171baa3004302e37540026eb8c034c0b8dd50129bae3004302e375404a29405288a99815a481296f7074696f6e2e69735f736f6d652861646d696e5f746f6b656e5f696e70757429203f2046616c73650014a04a666054603a60586ea80045288a5022323300100100322533303000114bd70099192999817180280109981980119802002000899802002000981a00118190009181718178009181698171817000911192999814980b98159baa0011480004dd6981798161baa0013253330293017302b3754002298103d87a80001323300100137566060605a6ea8008894ccc0bc004530103d87a80001323232533302e3371e00e6eb8c0c000c4c03ccc0ccdd4000a5eb804cc014014008dd6981800118198011818800998020018011119198008008019129998160008a60103d87a80001323232533302b3371e00c6eb8c0b400c4c030cc0c0dd3000a5eb804cc014014008dd598168011818001181700098129baa0123028302537540022a6604692013c65787065637420536f6d6528617374657269615f696e70757429203d2066696e645f696e70757428696e707574732c20617374657269615f726566290016323300200923375e600a604c6ea8004008c09cc090dd50051119198008008019129998140008a60103d87a80001323253330263005002130073302b0024bd70099802002000981600118150009ba5480008c09400454cc07d24123657870656374205370656e6428617374657269615f72656629203d20707572706f73650016375c604660480046eb4c088004c078dd50079bac3020302130210023758603e00260366ea8c078008c074c078004c064dd50008a4c2a6602e9211856616c696461746f722072657475726e65642066616c73650013656325333015300800115333019301837540082930a9980b0098b0a99980a98018008a99980c980c1baa004149854cc05804c5854ccc054c00800454ccc064c060dd50020a4c2a6602c0262c2a6602c0262c602c6ea800cdc3a40086e1d200253333330190011001153301200f16153301200f16153301200f16153301200f163300100300e225333010300330123754004264a66602a0022a660240042c26464a66602e0022a660280082c264a66603060360042930a9980a8028b19299999980e0008a9980a8028b0a9980a8028b0a9980a8028b0a9980a8028b09bae0013019001301900232533333301a0011533013003161533013003161533013003161375a0022a660260062c602e00260266ea800854cc04400458dc3a4000a66666602800220022a6601a0162c2a6601a0162c2a6601a0162c2a6601a0162c6eb4004dd6800a499c657870656374205b617374657269615f6f75747075745d203d0a202020202020202020206c6973742e66696c746572280a2020202020202020202020206f7574707574732c0a202020202020202020202020666e286f757470757429207b206f75747075742e61646472657373203d3d20617374657269615f696e7075742e6f75747075742e61646472657373207d2c0a20202020202020202020290049013f65787065637420496e6c696e65446174756d28617374657269615f6f75747075745f646174756d29203d20617374657269615f6f75747075742e646174756d0049014065787065637420617374657269615f6f75747075745f646174756d3a2041737465726961446174756d203d20617374657269615f6f75747075745f646174756d0049011972656465656d65723a204173746572696152656465656d657200490113646174756d3a2041737465726961446174756d005734ae7155ceaab9e5573eae815d0aba257489812fd8799f581c516238dd0a79bac4bebe041c44bad8bf880d74720733d2fc0d255d284c6173746572696141646d696eff004c01051a002dc6c0004c010218320001 \ No newline at end of file diff --git a/game/src/scripts/pellet.txt b/game/src/scripts/pellet.txt new file mode 100644 index 0000000..4241244 --- /dev/null +++ b/game/src/scripts/pellet.txt @@ -0,0 +1 @@ +590d5a01000033232323232323232323232323232222325333333012002153232323233300e30013010375400c2a6601e9211c52756e6e696e672032206172672076616c696461746f72206d696e7400132325333010323232323253330153008301737540082646464a666030601660346ea80284c94ccc0654ccc0654ccc064c030c06cdd5191980080080391299980f8008a60103d87a800013232533301d3013323330073756600260426ea8c004c084dd50019bae3024302137540306eb8c004c084dd500c118121812800898091981100125eb804cc010010004c08c008c0840045288a5014a22a660349201296f7074696f6e2e69735f736f6d652861646d696e5f746f6b656e5f696e70757429203f2046616c73650014a02a666032002294454cc06924011d6d7573745f6d696e745f6675656c5f746f6b656e73203f2046616c73650014a02940c038ccc00400800d2201044655454c001533301833710666002004006911044655454c00480005288a9980ca491d6d7573745f6275726e5f6675656c5f746f6b656e73203f2046616c73650014a044464a666036601e603a6ea8004520001375a6042603c6ea8004c94ccc06cc03cc074dd50008a60103d87a80001323300100137566044603e6ea8008894ccc084004530103d87a8000132323253330203371e00e6eb8c08800c4c054cc094dd4000a5eb804cc014014008dd698110011812801181180099198008008021129998100008a6103d87a80001323232533301f3371e00e6eb8c08400c4c050cc090dd3000a5eb804cc014014008dd5981080118120011811000991980080080191299980e0008a5eb7bdb1804c8c8c8c94ccc070cdc7a441000021003133021337606ea4008dd3000998030030019bab301e003375c60380046040004603c0026eb8c06cc060dd50020a9980b24922657870656374204d696e74286675656c5f706f6c69637929203d20707572706f736500163756603460366036603660360046eb0c064004c054dd5180c001180b980c00098099baa008149854cc0452411856616c696461746f722072657475726e65642066616c73650013656325333010300300115333014301337540042930a998088078b0a99980818020008a99980a18099baa002149854cc04403c5854cc04403c58c044dd5000a99999980b00388038a998078068b0a998078068b0a998078068b0a998078068b0a99807a491d52756e6e696e672033206172672076616c696461746f72207370656e64001332323223232253330153232323232533301a300e301c37540082646464a66603a6020603e6ea80044c8c8c94ccc080c050c088dd5000899191919192999812980c18139baa0131323232533302c001153302902316132533302d3030002132533302a3370e900218161baa00113232323232323253330315333031300d33016019232330010013233001001325333035302930373754002297adef6c6013756607660706ea8004cc04cdd5980b181b9baa30163037375400601c44a666072002297ae013303a3037303b00133002002303c00122533303800114a0264a66606a66e3cccdc6240006e35221045348495000375c607600491104534849500014a22660060060026076002294454cc0c924011d6d7573745f696e7075745f736869705f746f6b656e203f2046616c73650014a02a666062a666062006294454cc0c92411d6d7573745f686f6c645f61646d696e5f746f6b656e203f2046616c73650014a02a666062a666062008294454cc0c92411d6d7573745f686176655f656e6f7567685f6675656c203f2046616c73650014a02a666062a666062004294454cc0c9241206d7573745f70726f766964655f6675656c5f616d6f756e74203f2046616c73650014a02a666062002294454cc0c924011b6d7573745f70726573657276655f646174756d203f2046616c73650014a029405280a5014a066ebc014088cdc39998069bab30113032375400e01e9101044655454c003370201801460446660186eacc040c0c4dd50031bae3014303137540506eb8c040c0c4dd501419b8900800a3301d00102853333330330011001153302c02716153302c02716153302c02716153302c027163030302d37540022a660569213365787065637420496e6c696e65446174756d286f75745f646174756d29203d2070656c6c65745f6f75747075742e646174756d0016302f30303030302c37540022a660540482c605c002646600200201e44a66605a002297ae013232533302b3375e6022605c6ea8008c044c0b8dd5180698171baa00e133030002330040040011330040040013031002302f001375c6058605a605a60526ea8060dd6981598141baa0131533302530013300a00d253330263330265333026301930283754601860526ea8c030c0a4dd5180418149baa00114a02945282511301c3330043756601060526ea8c020c0a4dd50009bae300c302937540406eb8c020c0a4dd50100a5014a22a6604c921296f7074696f6e2e69735f736f6d652861646d696e5f746f6b656e5f696e70757429203f2046616c73650014a04a66604a6030604e6ea80045288a503330013756600a604c6ea8c014c098dd5003001a45044655454c00222325333026301a302837540022900009bad302c3029375400264a66604c603460506ea8004530103d87a8000132330010013756605a60546ea8008894ccc0b0004530103d87a80001323232533302b3371e00e6eb8c0b400c4c080cc0c0dd4000a5eb804cc014014008dd6981680118180011817000998020018011119198008008019129998148008a60103d87a8000132323253330283371e00c6eb8c0a800c4c074cc0b4dd3000a5eb804cc014014008dd59815001181680118158009bae3026302337540022a660429215b6578706563742053637269707443726564656e7469616c286675656c5f706f6c69637929203d0a20202020202070656c6c65745f696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c0016300530223754600a60446ea8c004c088dd5001118129813000981198101baa001153301e49013a65787065637420536f6d652870656c6c65745f696e70757429203d2066696e645f696e70757428696e707574732c2070656c6c65745f726566290016323300200523375e600860426ea8004008c088c07cdd50031119198008008019129998118008a60103d87a8000132325333021300500213016330260024bd7009980200200098138011812800918108008a9980da48122657870656374205370656e642870656c6c65745f72656629203d20707572706f736500163758603e604060400046eb0c078004c068dd5180e801180e180e800980c1baa001149854cc0592411856616c696461746f722072657475726e65642066616c7365001365632533301430070011325333019001153301601216132533301a301d002149854cc05c04c58c94cccccc07800454cc05c04c5854cc05c04c5854cc05c04c584dd68008a9980b8098b180d800980b9baa00215333014300800115333018301737540042930a9980a8088b0a9980a8088b180a9baa001533333301a0011001153301300f16153301300f16153301300f16153301300f163300100200e225333011300430133754004264a66602c0022a660260042c26464a6660300022a6602a0082c26464a6660340022a6602e00c2c264a666036603c0042930a9980c0038b19299999980f8008a9980c0038b0a9980c0038b0a9980c0038b0a9980c0038b09bae001301c001301c00232533333301d0011533016005161533016005161533016005161375a0022a6602c00a2c6034002603400464a6666660360022a660280062c2a660280062c2a660280062c26eb400454cc05000c58c060004c050dd50010a998090008b299999980b00388038a998078060b0a998078060b0a998078060b0a998078060b180a18089baa006370e90001b8748008dd2a40006e212000153300b00116153300b00116153300b00116153300b0011649191496e636f72726563742072656465656d6572207479706520666f722076616c696461746f72207370656e642e0a2020202020202020202020202020202020202020446f75626c6520636865636b20796f7520686176652077726170706564207468652072656465656d657220747970652061732073706563696669656420696e20796f757220706c757475732e6a736f6e00490195657870656374205b70656c6c65745f6f75747075745d203d0a2020202020202020202066696c746572280a2020202020202020202020206f7574707574732c0a202020202020202020202020666e286f757470757429207b206f75747075742e61646472657373203d3d2070656c6c65745f696e7075742e6f75747075742e61646472657373207d2c0a202020202020202020202900490129657870656374206f75745f646174756d3a2050656c6c6574446174756d203d206f75745f646174756d0049011872656465656d65723a2050656c6c657452656465656d657200490112646174756d3a2050656c6c6574446174756d0049011672656465656d65723a204675656c52656465656d6572005734ae7155ceaab9e5573eae815d0aba257489812fd8799f581c516238dd0a79bac4bebe041c44bad8bf880d74720733d2fc0d255d284c6173746572696141646d696eff0001 \ No newline at end of file diff --git a/game/src/scripts/spacetime.txt b/game/src/scripts/spacetime.txt new file mode 100644 index 0000000..8cef1ca --- /dev/null +++ b/game/src/scripts/spacetime.txt @@ -0,0 +1 @@ +592e3101000033333333323232323232323232323232323232323232323232323223223222232232232232223253333330280021532323232323232323330283001302a37540142a660529211c52756e6e696e672032206172672076616c696461746f72206d696e740013232533302a3232323232323232325333033300c303537540102646464a66606c601e60706ea80384c8c8c94ccc0e4c048c0ecdd500089919191919299981f180c98201baa0011323232325333046001153304303e161325333047304a0021325333044301f30463754002264646464a666090604860946ea80044c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c8c94ccc16d4ccc16c0805288a9982e24811d6d7573745f62655f76616c69645f61737465726961203f2046616c73650014a02a6660b6a6660b6010294454cc171241206d7573745f6d696e745f65787065637465645f76616c7565203f2046616c73650014a02a6660b6a6660b600e294454cc171241216d7573745f726573706563745f6d696e5f64697374616e6365203f2046616c73650014a02a6660b6a6660b600c294454cc1712411b6d7573745f686176655f736869705f6e616d65203f2046616c73650014a02a6660b6a6660b600a294454cc1712411c6d7573745f686176655f70696c6f745f6e616d65203f2046616c73650014a02a6660b6a6660b6008294454cc1712411d6d7573745f686176655f6c61746573745f74696d65203f2046616c73650014a02a6660b6a6660b6006294454cc1712411c6d7573745f686f6c645f736869705f746f6b656e203f2046616c73650014a02a6660b6a6660b6004294454cc1712411e6d7573745f686f6c645f696e697469616c5f6675656c203f2046616c73650014a02a6660b6002294454cc17124011a6d7573745f686f6c645f335f617373657473203f2046616c73650014a029405280a5014a029405280a5014a06068664600200244a6660c0002290000981d99801001183180099198008009bab3025305e375403244a6660c0002297ae01323332223233001001003225333066001100313233068374e660d06ea4018cc1a0dd49bae30650013306837506eb4c1980052f5c06600600660d400460d00026eb8c17c004dd59830000998018019832001183100099b8733301f3756604660b86ea805c1252201044655454c0003f303433301e3756604460b66ea805809401ccdc39bad305d305e305e305e305e305a37540246eb4c174c168dd500819b8f375c60b860ba60ba60ba60b26ea8044010cdc79bae30193058375402000866e240e0c8cdc018009bad301e3058375402060026eb4c07cc160dd50081299982a99b88001480004c0cc0044004cdd79ba601f374c64646466660026666002a6660ac606490000a5eb7bdb1804c8c8cc0040052f5bded8c044a6660ba0022660bc66ec0dd48131ba60034bd6f7b630099191919299982e98049980501500109983119bb037520546e9801c01454ccc174cdc781500109983119bb037520546e9801c00c4cc188cdd81ba9002374c0026600c00c0066eacc17c00cdd7182e8011830801182f8009919800800a5eb7bdb180894ccc1700044cc174cdd81ba90074c010101004bd6f7b630099191919299982e18041980480580109983099bb037520169810101000051533305c3371e0160042660c266ec0dd4805a61010100003133061337606ea4008dd4000998030030019bad305e003375c60b800460c000460bc0020460089001023245044655454c0003c22225333059303200110041323233001001006225333060001133061337606ea4018dd3001a5eb7bdb1804c8c8c8c94ccc180c030cc0340280084cc194cdd81ba900a374c00e00a2a6660c066e3c0280084c94ccc184c0e8c18cdd500089983319bb0375201660ce60c86ea80040104010c94ccc1854ccc1940045288a5014c0103d87a80001303e33066374c00297ae03233001001002225333066001133067337606ea402cdd400525eb7bdb1804c8c8c8c94ccc198c048cc04c03c0084cc1accdd81ba900f375001c00a2a6660cc66e3c03c0084c94ccc19cc100c1a4dd500089983619bb0375202060da60d46ea80040104010c94ccc19cc1000045300103d87a8000130443306c375000297ae03370000201c2660d666ec0dd48011ba800133006006003375a60d00066eb8c198008c1a8008c1a00044cc194cdd81ba9002374c0026600c00c0066eacc18800cdd7183000118320011831000991900119198008008011129998300008a4c264a6660c200229309919299982f981c18309baa3300c375c60c260ca0086eb8c1840084cc014014cc19000800454cc1812401326b65797320696e206173736f63696174697665206c697374206172656e277420696e20617363656e64696e67206f7264657200163065002306300130630013305e337606ea4008dd4000a5eb7bdb180dd7a60103d879800022533305533720004002298103d8798000153330553371e0040022980103d87a800014c103d87b8000330024890550494c4f540030033004375a603660aa6ea804ccc005220104534849500030023003375a603460a86ea804888cdc500100091b9800123732660046ea00052201003001001222533333305700213232323232323300b0020013371491010128000025333053337100069007099b80483c80400c54ccc14ccdc4001a410004266e00cdc0241002800690068a9982a24929576861742061726520796f7520646f696e673f204e6f2049206d65616e2c20736572696f75736c792e001653330560011337149101035b5d2900004133714911035b5f2000375c60aa66600e00266ec1300102415d00375266e292210129000042233760980103422c2000375266601001000466e28dd7182b0009bae3057001375860a80046eb4c148004c8cdd81ba83052001374e60a60026ea80084c94ccc1500044cdc5245027b7d00002133714911037b5f2000375c60a664646600200200644a6660ae00220062664466ec130103422c2000375266601201260ae00466e29221023a20003330090093058002337146eb8c15c004dd7182c000982c80099801001182d00099bb04c10342207d0037520046eac0084c94ccc1500044cdc52441025b5d00002133714911035b5f2000375c60a666600a00266ec1300102415d0037520044466ec1300103422c2000375266600c00c00466e28dd7182a0009bae3055001375800426600a6eb40080044c8c8cdc524410268270000132333001001337006e3400920013371491101270000322253330533371000490000800899191919980300319b8000548004cdc599b80002533305633710004900a0a40c02903719b8b33700002a6660ac66e2000520141481805206e0043370c004901019b8300148080cdc70020011bae00222232330010010042253330540011004133003305600133002002305700122323300100100322533304e30270011337149110130000031533304e337100029000099b8a489012d0033002002302c00113300533708002900a19b8b3370066e1400520144818000cc0040048894ccc12ccdc4801240002002266600600666e1000920143371666e00cdc28012402890300008a9982481f0b180818251baa3011304a375402aa66608c603e60906ea80044c94ccc12c00454cc120110584c8c94ccc13400454cc128118584c8c94ccc13c00454cc130120584c8c94ccc14400454cc138128584c8c94ccc14c00454cc140130584c94ccc150c15c008526153305104d16325333333058001153305104d16153305104d16153305104d161375a0022a660a209a2c60aa00260aa00464a6666660ac0022a6609e0962c2a6609e0962c2a6609e0962c2a6609e0962c26eb8004c14c004c14c008c94cccccc15000454cc1341245854cc1341245854cc1341245854cc134124584dd7000982880098288011929999998290008a998258238b0a998258238b0a998258238b09bad001153304b04716304f001304f0023253333330500011533049045161533049045161533049045161375a0022a6609208a2c609a00260926ea800454cc11c10c594cccccc134004400454cc1181085854cc1181085854cc1181085854cc11810858c128c11cdd50008a99822a493165787065637420496e6c696e65446174756d28736869705f646174756d29203d20736869705f73746174652e646174756d00163007304637540022a6608807e2c6090002646600200202644a66608e002297ae0132325333045325333046302230483754002266e3c04cdd7182618249baa00114a0601c60906ea8c038c120dd50010998250011980200200089980200200098258011824800a999820180c98211baa0011325333045001153304203c16132325333047001153304403e161325333048304b002149854cc1140fc58c94cccccc13000454cc1140fc5854cc1140fc5854cc1140fc5854cc1140fc584dd7000982480098248011929999998250008a9982181e8b0a9982181e8b0a9982181e8b09bad001153304303d163047001304337540022a660820762ca66666608e00220022a660800742c2a660800742c2a660800742c2a660800742c608860826ea800454cc0fd2413e65787065637420496e6c696e65446174756d28617374657269615f646174756d29203d20617374657269615f696e7075742e6f75747075742e646174756d0016300130403754600e60806ea80108c10cc110c110004c06cccc004dd59802981f1baa3005303e37540046eb8c010c0f8dd50141bae3005303e375405044464a66607c603460806ea8004520001375a608860826ea8004c94ccc0f8c068c100dd50008a60103d87a8000132330010013756608a60846ea8008894ccc110004530103d87a8000132323253330433371e00e6eb8c11400c4c080cc120dd4000a5eb804cc014014008dd698228011824001182300099804001801181f981e1baa001153303a03116323300100100c22533303e00114c103d87a800013232533303c32533303d3016303f375400229404cdc79bae304330403754002056600a607e6ea8c014c0fcdd51803181f9baa00213019330410024bd70099802002000982100118200009181f0009181e981f000899299981d8008a9981c01a8b099299981e181f8010a99981c19b8748004dd6981d8008a51153303903616153303903616303d001325333037301330393754002297adef6c6013756607a60746ea8004cc00400800c88c8cc00400400c894ccc0f40045300103d87a80001323232533303c3371e00c6eb8c0f800c4c064cc104dd3000a5eb804cc014014008dd5981f0011820801181f800991980080080211299981d0008a5eb7bdb1804c8c8c8c94ccc0e8cdc7a44100002100313303f337606ea4008dd3000998030030019bab303c003375c6074004607c00460780026eb8c0e4c0d8dd50040a9981a24926657870656374204d696e742873686970796172645f706f6c69637929203d20707572706f7365001630383039303930390023756606e002606e606e0046eb0c0d4004c0d4c0d4008dd6181980098179baa303200230313032001302d37540182930a99815a4811856616c696461746f722072657475726e65642066616c7365001365632533302a30030011533302e302d37540042930a998158148b0a99981518030008a99981718169baa002149854cc0ac0a45854cc0ac0a458c0acdd5000a99999981800588058a998148138b0a998148138b0a998148138b0a998148138b0a99814a491d52756e6e696e672033206172672076616c696461746f72207370656e640013323232232322533302f323232323232323232323232323232323232323232325333045301e30473754002264a66608c604460906ea80584c8c94ccc120c084c128dd5000899192999825181318261baa001132323232323232323232323232325323233305a303300d1323232325333062001153305f05016132533306330660021325333060303b30623754002264646464a6660c8608060cc6ea80044c8c94ccc198c108c1a0dd50008991919192999835182198361baa001132323232323232323232323253330765333076305230263301f044230293035307a3754606c60f46ea80045288a9983ba481236d7573745f7370656e645f6f6e655f7363726970745f696e707574203f2046616c73650014a02a6660eca6660ec054294454cc1dd241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660eca6660ec008294454cc1dd2411d6d7573745f686176655f656e6f7567685f6675656c203f2046616c73650014a02a6660eca6660ec00c294454cc1dd2411e6d7573745f726573706563745f6d61785f7370656564203f2046616c73650014a02a6660eca6660ec00a294454cc1dd241206d7573745f726573706563745f6c61746573745f74696d65203f2046616c73650014a02a6660eca6660ec004294454cc1dd241216d7573745f73756274726163745f6675656c5f746f6b656e73203f2046616c73650014a02a6660eca6660ec010294454cc1dd2411c6d7573745f6275726e5f7370656e745f6675656c203f2046616c73650014a02a6660eca6660ec014294454cc1dd241156d7573745f7570646174655f78203f2046616c73650014a02a6660eca6660ec012294454cc1dd241156d7573745f7570646174655f79203f2046616c73650014a02a6660eca6660ec00e294454cc1dd2411f6d7573745f7570646174655f75707065725f626f756e64203f2046616c73650014a02a6660eca6660ec016294454cc1dd241216d7573745f70726573657276655f70696c6f745f746f6b656e203f2046616c73650014a02a6660eca6660ec006294454cc1dd2411c6d7573745f686f6c645f736869705f746f6b656e203f2046616c73650014a02a6660ec002294454cc1dd24011a6d7573745f686f6c645f335f617373657473203f2046616c73650014a029405280a5014a029405280a5014a029405280a5014a0609e604460486eacc0d0c1e0dd500b99b8733301f3756606660ee6ea80581912201044655454c0033702014018609e66603c6eacc0c8c1d8dd500a81481b19b8900a008301900d32323371266e08dd6983c0011bad30783079001337046eb4c1e0004dd6983c183c801183a1baa02c3073375460ec60e66ea801ccdc39bad307530763076307630763072375401c012602a609800c66e1cdd6981618381baa00c3370006402266e1cdd6981518379baa00b3370006602266e3c0b0dd7183898391839183918371baa00a3330153756605260da6ea8c0a4c1b4dd501102d2441044655454c00153306b4915865787065637420536f6d6528737065656429203d0a20202020202020202020726174696f6e616c2e6e65772864697374616e63652c2074785f6c61746573745f74696d65202d2074785f6561726c696573745f74696d65290016330260023370200600a66e08004140c8cdc0180080698008061299983419b88001480004c1180044004dd6983618349baa001153306705c16302330683754604860d06ea80b8dd6983518339baa001153306505916302130663754604260cc6ea80b0cc0e40041594cccccc1a4004400454cc1881545854cc1881545854cc1881545854cc18815458c198c18cdd50008a998308298b180498311baa00115330600511630640013300602923375e603860c26ea8004c070c184dd5180e98309baa016375a60c460c60046eb4c184004c174dd50170a99982d181b0068991929998300008a9982e8270b09929998309832001099299982f181c98301baa0011323232325333062303b306437540022646464a6660ca608060ce6ea80044c8c8c8c94ccc1a4c114c1acdd5000899191919191919191929998392999839182698111980d820118129818983b1baa303230763754002294454cc1cd2401246d7573745f7370656e645f74776f5f7363726970745f696e70757473203f2046616c73650014a02a6660e4a6660e401e294454cc1cd2411c6d7573745f62655f76616c69645f70656c6c6574203f2046616c73650014a02a6660e4a6660e404c294454cc1cd241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660e4a6660e4010294454cc1cd241216d7573745f686176655f70656c6c65745f706f736974696f6e203f2046616c73650014a02a6660e4a6660e4006294454cc1cd2411c6d7573745f6164645f6675656c5f746f6b656e73203f2046616c73650014a02a6660e4a6660e400e294454cc1cd241206d7573745f6e6f745f6578636565645f6361706163697479203f2046616c73650014a02a6660e4a6660e400a294454cc1cd241206d7573745f726573706563745f6c61746573745f74696d65203f2046616c73650014a02a6660e4a6660e400c294454cc1cd2411b6d7573745f70726573657276655f646174756d203f2046616c73650014a02a6660e4a6660e4008294454cc1cd2411c6d7573745f686f6c645f736869705f746f6b656e203f2046616c73650014a02a6660e4a6660e4004294454cc1cd2411a6d7573745f686f6c645f335f617373657473203f2046616c73650014a02a6660e4002294454cc1cd24011c6d7573745f6e6f745f6d696e745f746f6b656e73203f2046616c73650014a029405280a5014a029405280a5014a0294052819baf374c603a07698101a000304a301d301f3756605e60e66ea8050cdc399980d1bab302e307237540260be911044655454c003370000c02a60946660326eacc0b4c1c4dd5009012018980a9bad30733070375400a66ebc034110cdc499b80002011055533306a3370e0626eb4c0a0c1b4dd5002099b8702f375a605260da6ea801052819980a1bab3028306c3754605060d86ea8084165221044655454c00153306a05e163026306b3754604c60d66ea80c54ccc19cc100c1a4dd500089929998360008a9983482f8b0991929998370008a998358308b0991929998380008a998368318b0992999838983a0010a4c2a660dc0c82c64a6666660ea0022a660dc0c82c2a660dc0c82c2a660dc0c82c2a660dc0c82c26eb8004c1c8004c1c8008c94cccccc1cc00454cc1b01885854cc1b01885854cc1b0188584dd68008a998360310b183800098380011929999998388008a998350300b0a998350300b0a998350300b09bad001153306a06016306e001306a37540022a660d00bc2ca6666660dc00220022a660ce0ba2c2a660ce0ba2c2a660ce0ba2c2a660ce0ba2c60d660d06ea800454cc1992413c65787065637420496e6c696e65446174756d2870656c6c65745f646174756d29203d2070656c6c65745f696e7075742e6f75747075742e646174756d0016300e30673754604660ce6ea8008c10cccc038dd5981118331baa3022306637540026eb8c084c198dd50281bae3022306637540a060d060ca6ea800454cc18d2401ff65787065637420536f6d652870656c6c65745f696e70757429203d0a202020202020202020206c6973742e66696e64280a202020202020202020202020696e707574732c0a202020202020202020202020666e28696e70757429207b0a20202020202020202020202020207768656e20696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c206973207b0a20202020202020202020202020202020566572696669636174696f6e4b657943726564656e7469616c285f29202d3e2046616c73650a2020202020202020202020202020202053637269707443726564656e7469616c28616464725f7061796d656e6b7429202d3e0a202020202020202020202020202020202020616464725f7061796d656e74203d3d2070656c6c65745f76616c696461746f725f616464726573730a20202020202020202020202020207d0a2020202020202020202020207d2c0a202020202020202020202900163301b02f2325333063303c3065375400229404cdc79bae3069306637540020a6604060ca6ea8c080c194dd5181098329baa00133037001054533333306700110011533060053161533060053161533060053161533060053163064306137540022a660be0a22c600e60c06ea800454cc17813c58c188004cc01009c8cdd7980d182f9baa001301a305f3754603660be6ea8050dd69830182e9baa02e1533305a303500d132533305b3034305d375400226464a6660ba607260be6ea80044c8c8c8c94ccc1854ccc184c0f0c044cc0280bc8c050c080c194dd5181098329baa00114a22a660c49201246d7573745f7370656e645f74776f5f7363726970745f696e70757473203f2046616c73650014a02a6660c2a6660c200c294454cc1892411d6d7573745f62655f76616c69645f61737465726961203f2046616c73650014a02a6660c2a6660c202a294454cc189241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660c2a6660c2008294454cc189241226d7573745f686176655f617374657269615f706f736974696f6e203f2046616c73650014a02a6660c2a6660c2006294454cc1892411c6d7573745f6275726e5f736869705f746f6b656e203f2046616c73650014a02a6660c2a6660c2002294454cc1892411d6d7573745f6275726e5f6675656c5f746f6b656e73203f2046616c73650014a02a6660c2004294454cc1892401206d7573745f726573706563745f6c61746573745f74696d65203f2046616c73650014a029405280a5014a029405281803981f1998059bab301f30633754603e60c66ea80601412201044655454c003007375a60ca60c46ea800ccdc4199804980501400a010a400066ebcdd31983119bb037500486ea00892f5bded8c06e98cc1892f7b6301010000010100004bd6f7b6300a9982f0290b180d182f9baa301a305f375404a607666600c6eacc068c178dd5180d182f1baa3061305e37540026eb8c064c178dd50241bae301a305e37540902a660b80a62c66028050464a6660b8606a60bc6ea8004528099b8f375c60c460be6ea8004128c064c178dd5180c982f1baa301a305e375400226464a6660b8a6660b8607060186600a0544601e603660c06ea8c070c180dd50008a51153305d491236d7573745f7370656e645f6f6e655f7363726970745f696e707574203f2046616c73650014a02a6660b8a6660b8020294454cc175241206d7573745f696e636c7564655f70696c6f745f746f6b656e203f2046616c73650014a02a6660b8a6660b8004294454cc1752411c6d7573745f6275726e5f736869705f746f6b656e203f2046616c73650014a02a6660b8002294454cc17524011d6d7573745f6275726e5f6675656c5f746f6b656e73203f2046616c73650014a029405280a50300230393330063756603460bc6ea8c068c178dd5009825a441044655454c003371066600a600c04802003a90001b8733300430050230494881044655454c00371203044646600200200644a6660be002297ae013232533305d300500213306200233004004001133004004001306300230610012305d305e305e0012223253330593035305b37540022900009bad305f305c375400264a6660b2606a60b66ea80045300103d87a800013233001001375660c060ba6ea8008894ccc17c004530103d87a80001323232533305e3371e00e6eb8c18000c4c0eccc18cdd4000a5eb804cc014014008dd6983000118318011830800998068018011191980080080111299982d8008a5eb7bdb1804c8c8c8c94ccc16ccdc7a441000021003133060337606ea4008dd3000998030030019bab305d003375c60b600460be00460ba002600200244a6660b0002290000981999801001182d8009191980080080111299982c0008a5eb804c8ccc888c8cc00400400c894ccc178004400c4c8cc180dd3998301ba90063306037526eb8c174004cc180dd41bad305e0014bd7019801801983100118300009bae3057001375660b00026600600660b800460b4002600200244a6660aa002290000981819801001182c00092999827981418289baa300d30523754002294052898279baa021533304c3025304e37546600c03446466002002646600200264a6660a0605860a46ea800452f5bded8c026eacc158c14cdd5000998021bab300e30523754601c60a46ea800c014894ccc15000452f5c02660aa60a460ac0026600400460ae00244a6660a600229404c94ccc140cdc79bae305600201114a226600600600260ac00229445281119198008008019129998298008a60103d87a8000132323253330523371e00c6eb8c15000c4c0bccc15cdd3000a5eb804cc014014008dd5982a001182b801182a8009bae3050304d37540022a660969215d6578706563742053637269707443726564656e7469616c2873686970796172645f706f6c69637929203d0a202020202020736869705f696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c00163007304c3754600e60986ea8c020c130dd5000982718259baa001153304949013665787065637420536f6d6528736869705f696e70757429203d2066696e645f696e70757428696e707574732c20736869705f726566290016323300201623375e600e60986ea8004008c134c128dd500b9119198008008019129998270008a60103d87a800013232533304c300500213029330510024bd70099802002000982900118280008a99823a48120657870656374205370656e6428736869705f72656629203d20707572706f73650016304b304837540022a6608c92015865787065637420536f6d65286d61785f73706565645f726174696f6e616c29203d0a202020202020726174696f6e616c2e6e6577286d61785f73706565642e64697374616e63652c206d61785f73706565642e74696d6529001633001375a6004608e6ea80c0dd6980198239baa030225333045301e00114c103d87a80001533304533710002900009811198251811198251ba830230023304a3750604600297ae04bd7009811198251811198251ba80023304a375000297ae04bd70118248009182418248009bad30463047002375c608a002608a0046eb8c10c004c10c008dd6982080098208011bad303f001303b3754020607a607c607c607c0046eacc0f0004c0f0c0f0008dd6181d000981d181d0011bac303800130343754606e004606c606e00260646ea8004526153303049011856616c696461746f722072657475726e65642066616c7365001365632533302e30070011325333033001153303002816132325333035001153303202a1613253330363039002149854cc0cc0ac58c94cccccc0e800454cc0cc0ac5854cc0cc0ac5854cc0cc0ac584dd68008a998198158b181b800981b80119299999981c0008a998188148b0a998188148b0a998188148b09bad0011533031029163035001303137540042a66605c6014002264a6660660022a660600502c264a666068606e0042930a998188148b19299999981c0008a998188148b0a998188148b0a998188148b09bad0011533031029163035001303137540042a66605c60120022a66606460626ea8008526153302f027161533302e300800115333032303137540042930a998178138b0a998178138b18179baa00153333330340011001153302d02516153302d02516153302d02516153302d025163300100202422533302b3004302d3754004264a6660600022a6605a0042c26464a6660640022a6605e0082c26464a6660680022a6606200c2c26464a66606c0022a660660102c26464a6660700022a6606a0142c264a66607260780042930a9981b0058b19299999981e8008a9981b0058b0a9981b0058b0a9981b0058b09bad001153303600b16303a001303a00232533333303b0011533034009161533034009161533034009161533034009161375c0026070002607000464a6666660720022a6606400e2c2a6606400e2c2a6606400e2c2a6606400e2c26eb8004c0d8004c0d8008c94cccccc0dc00454cc0c00145854cc0c00145854cc0c0014584dd68008a998180028b181a000981a00119299999981a8008a998170018b0a998170018b0a998170018b09bad001153302e003163032001302e37540042a660580022ca66666606001620162a660520442c2a660520442c2a660520442c2a660520442c605c60566ea8028dc3a40006e1d2006370e90021b8748008dd2a40006e052000370090011b884800054cc0840045854cc0840045854cc0840045854cc08400459240191496e636f72726563742072656465656d6572207479706520666f722076616c696461746f72207370656e642e0a2020202020202020202020202020202020202020446f75626c6520636865636b20796f7520686176652077726170706564207468652072656465656d657220747970652061732073706563696669656420696e20796f757220706c757475732e6a736f6e00375a0026eb4004dd68009bad001375c0026eb800524196657870656374205b736869705f6f75747075745d203d0a202020202020202020206c6973742e66696c746572280a2020202020202020202020206f7574707574732c0a202020202020202020202020666e286f757470757429207b206f75747075742e61646472657373203d3d20736869705f696e7075742e6f75747075742e61646472657373207d2c0a20202020202020202020290049013965787065637420496e6c696e65446174756d28736869705f6f75747075745f646174756d29203d20736869705f6f75747075742e646174756d0049013765787065637420736869705f6f75747075745f646174756d3a2053686970446174756d203d20736869705f6f75747075745f646174756d004901476578706563742046696e6974652874785f6561726c696573745f74696d6529203d2076616c69646974795f72616e67652e6c6f7765725f626f756e642e626f756e645f74797065004901456578706563742046696e6974652874785f6c61746573745f74696d6529203d2076616c69646974795f72616e67652e75707065725f626f756e642e626f756e645f747970650049012f6578706563742070656c6c65745f646174756d3a2050656c6c6574446174756d203d2070656c6c65745f646174756d004901ff65787065637420536f6d6528617374657269615f696e70757429203d0a202020202020202020206c6973742e66696e64280a202020202020202020202020696e707574732c0a202020202020202020202020666e28696e70757429207b0a20202020202020202020202020207768656e20696e7075742e6f75747075742e616464726573732e7061796d656e745f63726564656e7469616c206973207b0a20202020202020202020202020202020566572696669636174696f6e4b657943726564656e7469616c285f29202d3e2046616c73650a2020202020202020202020202020202053637269707443726564656e7469616c28616464725f7061796d656d6e7429202d3e0a202020202020202020202020202020202020616464725f7061796d656e74203d3d20617374657269615f76616c696461746f725f616464726573730a20202020202020202020202020207d0a2020202020202020202020207d2c0a20202020202020202020290049011672656465656d65723a205368697052656465656d657200490110646174756d3a2053686970446174756d0049013265787065637420617374657269615f646174756d3a2041737465726961446174756d203d20617374657269615f646174756d00490159657870656374205b736869705f73746174655d203d0a202020202020202020207472616e73616374696f6e2e66696e645f7363726970745f6f757470757473286f7574707574732c2073686970796172645f706f6c696379290049012965787065637420736869705f646174756d3a2053686970446174756d203d20736869705f646174756d00490120657870656374205b285f2c202d31295d203d206d696e7465645f746f6b656e730049011a72656465656d65723a20536869707961726452656465656d6572005734ae7155ceaab9e5573eae815d0aba257489811e581c6a25ad5476105ac4a3784769cb93f92fd67a11932ef9a65a61abd1d6004c011e581cbac1753d5f7e3609c92776371fd0eafa753889e5712858f48fb83981004c012fd8799f581c516238dd0a79bac4bebe041c44bad8bf880d74720733d2fc0d255d284c6173746572696141646d696eff004c0108d8799f01197530ff004c01021864004c010101004c0102181e004c01010a0001 \ No newline at end of file diff --git a/griffin-core/src/executive.rs b/griffin-core/src/executive.rs index 293f2a3..5594668 100644 --- a/griffin-core/src/executive.rs +++ b/griffin-core/src/executive.rs @@ -35,7 +35,7 @@ use crate::{ utxo_set::TransparentUtxoSet, EXTRINSIC_KEY, HEADER_KEY, HEIGHT_KEY, LOG_TARGET, }; -use crate::{MILLI_SECS_PER_SLOT, ZERO_SLOT, ZERO_TIME}; +use crate::{SLOT_LENGTH, ZERO_SLOT, ZERO_TIME}; use alloc::{collections::btree_set::BTreeSet, string::String, vec::Vec}; use log::debug; use parity_scale_codec::{Decode, Encode}; @@ -107,7 +107,7 @@ where let slot_config = SlotConfig { zero_time: Self::zero_time(), zero_slot: Self::zero_slot(), - slot_length: MILLI_SECS_PER_SLOT, + slot_length: Self::slot_length(), }; let phase_two_result = eval_phase_two( &conway_mtx, @@ -321,6 +321,13 @@ where .expect("Failed to read ZERO_SLOT from storage.") } + /// A helper function that allows griffin runtimes to read the millisecs per slot + pub fn slot_length() -> u32 { + sp_io::storage::get(SLOT_LENGTH) + .and_then(|d| u32::decode(&mut &*d).ok()) + .expect("Failed to read SLOT_LENGTH from storage.") + } + // These next three methods are for the block authoring workflow. // Open the block, apply zero or more extrinsics, close the block diff --git a/griffin-core/src/genesis/config_builder.rs b/griffin-core/src/genesis/config_builder.rs index 807e2b9..79ab3f7 100644 --- a/griffin-core/src/genesis/config_builder.rs +++ b/griffin-core/src/genesis/config_builder.rs @@ -7,7 +7,7 @@ use crate::{ types::{ address_from_hex, AssetName, Coin, EncapBTree, Input, Multiasset, Output, Transaction, }, - EXTRINSIC_KEY, UTXO_SET, ZERO_SLOT, ZERO_TIME, + EXTRINSIC_KEY, SLOT_LENGTH, UTXO_SET, ZERO_SLOT, ZERO_TIME, }; use alloc::{collections::BTreeMap, string::String, vec, vec::Vec}; @@ -40,6 +40,7 @@ pub struct TransparentOutput { pub struct GenesisConfig { pub zero_slot: u64, pub zero_time: u64, + pub slot_length: u32, pub outputs: Vec, } @@ -65,6 +66,7 @@ where sp_io::storage::set(EXTRINSIC_KEY, &transactions.encode()); sp_io::storage::set(ZERO_SLOT, &genesis_config.zero_slot.encode()); sp_io::storage::set(ZERO_TIME, &genesis_config.zero_time.encode()); + sp_io::storage::set(SLOT_LENGTH, &genesis_config.slot_length.encode()); for tx in transactions.into_iter() { // Enforce that transactions do not have any inputs. diff --git a/griffin-core/src/lib.rs b/griffin-core/src/lib.rs index dbdb216..fcc1b67 100644 --- a/griffin-core/src/lib.rs +++ b/griffin-core/src/lib.rs @@ -37,7 +37,7 @@ pub mod utxo_set; pub use executive::Executive; /// The Aura slot duration. When things are working well, this will also be the block time. -pub const MILLI_SECS_PER_SLOT: u32 = 3000; +pub const SLOT_LENGTH: &[u8] = b"slot-length"; /// A storage key that will store the slot number of the first block in the chain. pub const ZERO_SLOT: &[u8] = b"zero-slot"; diff --git a/griffin-core/src/types.rs b/griffin-core/src/types.rs index 00c2bff..259c9a7 100644 --- a/griffin-core/src/types.rs +++ b/griffin-core/src/types.rs @@ -857,18 +857,18 @@ impl + Clone> Sub for EncapBTree { impl Sub for Value { type Output = Self; - /// Coordinate-wise subtraction of `Value`s + /// Coordinate-wise subtraction of Values fn sub(self, other: Self) -> Self { use Value::*; match self { Coin(c) => match other { - Coin(d) => Coin(c - d), - Multiasset(d, ma) => Multiasset(c - d, ma), + Coin(d) => Coin(c.saturating_sub(d)), + Multiasset(d, ma) => Multiasset(c.saturating_sub(d), ma), }, Multiasset(c, ma) => match other { - Coin(d) => Multiasset(c - d, ma), - Multiasset(d, mb) => Multiasset(c - d, ma - mb), + Coin(d) => Multiasset(c.saturating_sub(d), ma), + Multiasset(d, mb) => Multiasset(c.saturating_sub(d), ma - mb), }, } .normalize() diff --git a/node/Cargo.toml b/node/Cargo.toml index b71e9e1..a939a52 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -21,6 +21,8 @@ futures-timer = { workspace = true } griffin-core = { workspace = true } griffin-partner-chains-runtime = { workspace = true } griffin-rpc = { workspace = true } +griffin-wallet = { workspace = true } +game = { workspace = true } jsonrpsee = { features = ["server"], workspace = true } sc-basic-authorship = { workspace = true } sc-cli = { workspace = true } @@ -41,6 +43,7 @@ sp-genesis-builder = { workspace = true } sp-io = { workspace = true } sp-timestamp = { workspace = true } sp-runtime = { workspace = true } +tokio = { workspace = true } partner-chains-node-commands = { workspace = true } partner-chains-cli = { workspace = true } diff --git a/node/src/cli.rs b/node/src/cli.rs index f9e929d..5e9cd8e 100644 --- a/node/src/cli.rs +++ b/node/src/cli.rs @@ -1,4 +1,6 @@ +use game::GameCommand; use griffin_partner_chains_runtime::opaque::SessionKeys; +use griffin_wallet::cli::WalletCommand; use partner_chains_cli::{KeyDefinition, AURA, GRANDPA}; use partner_chains_node_commands::{PartnerChainRuntime, PartnerChainsSubcommand}; @@ -104,6 +106,13 @@ pub enum Subcommand { #[command(subcommand)] Key(sc_cli::KeySubcommand), + #[clap(flatten)] + Wallet(WalletCommand), + + /// Commands to play the Asteria game + #[clap(flatten)] + Game(GameCommand), + #[clap(flatten)] PartnerChains(PartnerChainsSubcommand), diff --git a/node/src/command.rs b/node/src/command.rs index 2c3d5eb..b07b78e 100644 --- a/node/src/command.rs +++ b/node/src/command.rs @@ -5,6 +5,7 @@ use crate::{ }; use sc_cli::SubstrateCli; use sc_service::PartialComponents; +use tokio::runtime::Runtime; impl SubstrateCli for Cli { fn impl_name() -> String { @@ -50,6 +51,16 @@ pub fn run() -> sc_cli::Result<()> { match &cli.subcommand { Some(Subcommand::Key(cmd)) => cmd.run(&cli), + Some(Subcommand::Wallet(cmd)) => { + let rt = Runtime::new().unwrap(); + let _ = rt.block_on(cmd.run()); + Ok(()) + } + Some(Subcommand::Game(cmd)) => { + let rt = Runtime::new().unwrap(); + let _ = rt.block_on(cmd.run()); + Ok(()) + } Some(Subcommand::PartnerChains(cmd)) => { partner_chains_node_commands::run::< // _, diff --git a/runtime/src/genesis.rs b/runtime/src/genesis.rs index adcfbb3..c96aab4 100644 --- a/runtime/src/genesis.rs +++ b/runtime/src/genesis.rs @@ -12,6 +12,7 @@ pub const GENESIS_DEFAULT_JSON: &str = r#" { "zero_time": 1747081100000, "zero_slot": 0, + "slot_length": 3000, "outputs": [ { "address": "6101e6301758a6badfab05035cffc8e3438b3aff2a4edc6544b47329c4", @@ -25,15 +26,30 @@ pub const GENESIS_DEFAULT_JSON: &str = r#" "datum": "820080" }, { - "address": "61547932e40a24e2b7deb41f31af21ed57acd125f4ed8a72b626b3d7f6", - "coin": 314150000, + "address": "70bac1753d5f7e3609c92776371fd0eafa753889e5712858f48fb83981", + "coin": 500000000, "value": [ { - "policy": "0298aa99f95e2fe0a0132a6bb794261fb7e7b0d988215da2f2de2005", - "assets": [ ["tokenA", 300000000], ["tokenB", 2000000000] ] + "policy": "516238dd0a79bac4bebe041c44bad8bf880d74720733d2fc0d255d28", + "assets": [ ["asteriaAdmin", 1] ] } ], - "datum": "820080" + "datum": "D8799F00581C7BA97FB6E48018EF131DD08916939350C0CE7050534F8E51B5E0E3A4FF" + }, + { + "address": "706a25ad5476105ac4a3784769cb93f92fd67a11932ef9a65a61abd1d6", + "coin": 2000, + "value": [ + { + "policy": "6a25ad5476105ac4a3784769cb93f92fd67a11932ef9a65a61abd1d6", + "assets": [ ["FUEL", 80] ] + }, + { + "policy": "516238dd0a79bac4bebe041c44bad8bf880d74720733d2fc0d255d28", + "assets": [ ["asteriaAdmin", 1] ] + } + ], + "datum": "d8799f2703581c7ba97fb6e48018ef131dd08916939350c0ce7050534f8e51b5e0e3a4ff" }, { "address": "0000000000000000000000000000000000000000000000000000000000", diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 56a9f8e..4846350 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -14,7 +14,6 @@ use alloc::{string::ToString, vec, vec::Vec}; use griffin_core::genesis::config_builder::GenesisConfig; use griffin_core::types::{Address, AssetName, Input, PolicyId}; use griffin_core::utxo_set::TransparentUtxoSet; -use griffin_core::MILLI_SECS_PER_SLOT; pub use opaque::SessionKeys; use parity_scale_codec::{Decode, Encode}; @@ -253,7 +252,7 @@ impl_runtime_apis! { impl apis::AuraApi for Runtime { fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(MILLI_SECS_PER_SLOT.into()) + sp_consensus_aura::SlotDuration::from_millis(griffin_core::Executive::slot_length() as u64) } fn authorities() -> Vec { diff --git a/wallet/Cargo.toml b/wallet/Cargo.toml index c7de19c..8ac9d70 100644 --- a/wallet/Cargo.toml +++ b/wallet/Cargo.toml @@ -17,6 +17,7 @@ parity-scale-codec = { workspace = true } serde_json = { workspace = true } serde = { workspace = true } +sc-cli = { workspace = true } sp-core = { workspace = true } sp-runtime = { workspace = true } diff --git a/wallet/src/cli.rs b/wallet/src/cli.rs index eecdc20..32f9d73 100644 --- a/wallet/src/cli.rs +++ b/wallet/src/cli.rs @@ -1,19 +1,31 @@ //! Test Wallet's Command Line Interface. +extern crate alloc; + use std::path::PathBuf; use crate::{ - address_from_string, h224_from_string, h256_from_string, input_from_string, + command, + context::{Context, DEFAULT_ENDPOINT}, + keystore, keystore::{SHAWN_ADDRESS, SHAWN_PUB_KEY}, - DEFAULT_ENDPOINT, H256, + sync, utils, + utils::{address_from_string, h224_from_string, h256_from_string, input_from_string}, }; +use alloc::string::String; use clap::{ArgAction::Append, Args, Parser, Subcommand}; -use griffin_core::types::{Address, Coin, Input, PolicyId}; +use griffin_core::{ + pallas_crypto::hash::Hasher as PallasHasher, + types::{Address, Coin, Input, PolicyId, Value}, +}; +use hex::FromHex; +use parity_scale_codec::Encode; +use sp_core::H256; /// The wallet's main CLI struct #[derive(Debug, Parser)] #[command(about, version)] -pub struct Cli { +pub struct Cli { #[arg(long, short, default_value_t = DEFAULT_ENDPOINT.to_string())] /// RPC endpoint of the node that this wallet will connect to. pub endpoint: String, @@ -42,11 +54,151 @@ pub struct Cli { pub purge_db: bool, #[command(subcommand)] - pub command: Option, + pub command: Option, } /// The tasks supported by the wallet -#[derive(Debug, Subcommand)] +#[derive(Clone, Debug, Subcommand)] +pub enum WalletCommand { + #[command(subcommand)] + Wallet(Command), +} + +impl WalletCommand { + pub async fn run(&self) -> anyhow::Result<()> { + let Context { + cli, + client, + db, + keystore, + data_path, + keystore_path, + slot_config: _, + } = Context::::load_context().await.unwrap(); + // Dispatch to proper subcommand + match cli.command { + Some(WalletCommand::Wallet(cmd)) => match cmd { + Command::VerifyUtxo { input } => { + println!("Details of coin {}:", hex::encode(input.encode())); + + // Print the details from storage + let coin_from_storage = utils::get_coin_from_storage(&input, &client).await?; + print!("Found in storage. Value: {:?}, ", coin_from_storage); + + // Print the details from the local db + match sync::get_unspent(&db, &input)? { + Some((owner, amount, _)) => { + println!("Found in local db. Value: {amount:?}, owned by {owner}"); + } + None => { + println!("Not found in local db"); + } + } + + Ok(()) + } + Command::SpendValue(args) => { + command::spend_value(&db, &client, &keystore, args).await + } + Command::InsertKey { seed } => keystore::insert_key(&keystore, &seed), + Command::GenerateKey { password } => { + keystore::generate_key(&keystore, password)?; + Ok(()) + } + Command::ShowKeys => { + keystore::get_keys(&keystore)?.for_each(|pubkey| { + let pk_str: &str = &hex::encode(pubkey); + let hash: String = + PallasHasher::<224>::hash(&<[u8; 32]>::from_hex(pk_str).unwrap()) + .to_string(); + println!("key: 0x{}; addr: 0x61{}", pk_str, hash); + }); + + Ok(()) + } + Command::RemoveKey { pub_key } => { + println!( + "CAUTION!!! About permanently remove {pub_key}. This action CANNOT BE REVERSED. Type \"proceed\" to confirm deletion." + ); + + let mut confirmation = String::new(); + std::io::stdin() + .read_line(&mut confirmation) + .expect("Failed to read line"); + + if confirmation.trim() == "proceed" { + keystore::remove_key(&keystore_path, &pub_key) + } else { + println!("Deletion aborted. That was close."); + Ok(()) + } + } + Command::ShowBalance => { + println!("Balance Summary"); + let mut total = Value::Coin(0); + let balances = sync::get_balances(&db)?; + for (account, balance) in balances { + total += balance.clone(); + println!("{account}: {balance}"); + } + println!("{:-<58}", ""); + println!("Total: {}", total.normalize()); + + Ok(()) + } + Command::ShowAllOutputs => { + println!("###### Unspent outputs ###########"); + sync::show_outputs(sync::print_unspent_tree(&db)?); + println!("To see all details of a particular UTxO, invoke the `verify-utxo` command."); + Ok(()) + } + Command::ShowOutputsAt(args) => { + println!( + "###### Unspent outputs at address {} ###########", + args.address + ); + sync::show_outputs(sync::get_outputs_at(&db, args)?); + println!("To see all details of a particular UTxO, invoke the `verify-utxo` command."); + Ok(()) + } + Command::ShowOutputsWithAsset(args) => { + println!( + "###### Unspent outputs containing asset with name {} and policy ID {} ###########", + args.name, args.policy + ); + sync::show_outputs(sync::get_outputs_with_asset(&db, args)?); + println!("To see all details of a particular UTxO, invoke the `verify-utxo` command."); + Ok(()) + } + Command::ShowAllOrders => { + println!("###### Available Orders ###########"); + sync::print_orders(&db)?; + Ok(()) + } + Command::BuildTx(args) => command::build_tx(&db, &client, &keystore, args).await, + }, + None => { + log::info!("No Wallet Command invoked. Exiting."); + Ok(()) + } + }?; + + if cli.tmp || cli.dev { + // Cleanup the temporary directory. + std::fs::remove_dir_all(data_path.clone()).map_err(|e| { + log::warn!( + "Unable to remove temporary data directory at {}\nPlease remove it manually.", + data_path.to_string_lossy() + ); + e + })?; + } + + Ok(()) + } +} + +#[derive(Clone, Debug, Subcommand)] pub enum Command { /// Verify that a particular output ref exists. /// Show its value and owner address from both chain storage and the local database. @@ -108,7 +260,7 @@ pub enum Command { } /// Arguments for building a complete Griffin transaction. -#[derive(Debug, Args)] +#[derive(Clone, Debug, Args)] pub struct BuildTxArgs { /// Path to the file containing all the transaction information in JSON format. /// There are example contracts and json files for testing this command in the `eutxo_examples` directory. @@ -152,7 +304,7 @@ pub struct BuildTxArgs { } /// Arguments for spending wallet inputs only. -#[derive(Debug, Args)] +#[derive(Clone, Debug, Args)] pub struct SpendValueArgs { /// An input to be consumed by this transaction. This argument may be specified multiple times. #[arg(long, short, verbatim_doc_comment, value_parser = input_from_string, required = true, value_name = "OUTPUT_REF")] @@ -184,14 +336,14 @@ pub struct SpendValueArgs { pub token_amount: Vec, } -#[derive(Debug, Args)] +#[derive(Clone, Debug, Args)] pub struct ShowOutputsAtArgs { /// 29-byte hash-address. #[arg(long, short, verbatim_doc_comment, value_parser = address_from_string, required = true, value_name = "ADDRESS")] pub address: Address, } -#[derive(Debug, Args)] +#[derive(Clone, Debug, Args)] pub struct ShowOutputsWithAssetArgs { /// Policy ID of the asset. #[arg(long, short, verbatim_doc_comment, value_parser = h224_from_string, required = true, value_name = "POLICY_ID")] diff --git a/wallet/src/command.rs b/wallet/src/command.rs index 67ef579..b144ca7 100644 --- a/wallet/src/command.rs +++ b/wallet/src/command.rs @@ -1,7 +1,7 @@ use crate::{ cli::{BuildTxArgs, SpendValueArgs}, keystore::SHAWN_ADDRESS, - sync, H224, H256, + sync, }; use anyhow::anyhow; use griffin_core::{ @@ -9,6 +9,7 @@ use griffin_core::{ genesis::config_builder::{ transp_to_multiasset, transp_to_output, TransparentMultiasset, TransparentOutput, }, + h224::H224, pallas_primitives::babbage::{MintedTx, Tx as PallasTransaction}, pallas_traverse::OriginalHash, types::{ @@ -22,7 +23,7 @@ use parity_scale_codec::Encode; use sc_keystore::LocalKeystore; use serde::{Deserialize, Serialize}; use sled::Db; -use sp_core::ed25519::Public; +use sp_core::{ed25519::Public, H256}; use sp_runtime::traits::{BlakeTwo256, Hash}; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] diff --git a/wallet/src/context.rs b/wallet/src/context.rs new file mode 100644 index 0000000..50dcc39 --- /dev/null +++ b/wallet/src/context.rs @@ -0,0 +1,142 @@ +use crate::{cli::Cli, rpc, sync}; +use clap::Parser; +use griffin_core::uplc::tx::SlotConfig; +use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; +use std::path::PathBuf; + +/// The default RPC endpoint for the wallet to connect to +pub const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; + +pub struct Context { + pub cli: Cli, + pub client: HttpClient, + pub db: sled::Db, + pub keystore: sc_keystore::LocalKeystore, + pub data_path: PathBuf, + pub keystore_path: PathBuf, + pub slot_config: SlotConfig, +} + +impl Context { + pub async fn load_context() -> anyhow::Result> { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); + + // Parse command line args + let cli = Cli::::parse(); + + // If the user specified --tmp or --dev, then use a temporary directory. + let tmp = cli.tmp || cli.dev; + + // Setup the data paths. + let data_path = match tmp { + true => temp_dir(), + _ => cli.base_path.clone().unwrap_or_else(default_data_path), + }; + let keystore_path = data_path.join("keystore"); + let db_path = data_path.join("wallet_database"); + + // Setup the keystore + let keystore = sc_keystore::LocalKeystore::open(keystore_path.clone(), None)?; + + if cli.dev { + // Insert the example Shawn key so example transactions can be signed. + crate::keystore::insert_development_key_for_this_session(&keystore)?; + } + + // Setup jsonrpsee and endpoint-related information. + // https://github.com/paritytech/jsonrpsee/blob/master/examples/examples/http.rs + let client = HttpClientBuilder::default().build(&cli.endpoint)?; + + // Read node's genesis block. + let node_genesis_hash = rpc::node_get_block_hash(0, &client) + .await? + .expect("node should be able to return some genesis hash"); + let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) + .await? + .expect("node should be able to return some genesis block"); + log::debug!("Node's Genesis block::{:?}", node_genesis_hash); + + let zero_time = rpc::node_get_zero_time(&client) + .await? + .expect("node should be able to return zero time"); + let zero_slot = rpc::node_get_zero_slot(&client) + .await? + .expect("node should be able to return zero slot"); + let slot_length = rpc::node_get_slot_length(&client) + .await? + .expect("node should be able to return slot length"); + let slot_config = SlotConfig { + zero_time, + zero_slot, + slot_length, + }; + + if cli.purge_db { + std::fs::remove_dir_all(db_path.clone()).map_err(|e| { + log::warn!( + "Unable to remove database directory at {}\nPlease remove it manually.", + db_path.to_string_lossy() + ); + e + })?; + } + + // Open the local database + let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; + + let num_blocks = + sync::height(&db)?.expect("db should be initialized automatically when opening."); + log::info!("Number of blocks in the db: {num_blocks}"); + + if !sled::Db::was_recovered(&db) { + sync::apply_block(&db, node_genesis_block, node_genesis_hash).await?; + } + + // Synchronize the wallet with attached node unless instructed otherwise. + if cli.no_sync { + log::warn!("Skipping sync with node. Using previously synced information.") + } else { + sync::synchronize(&db, &client).await?; + + log::info!( + "Wallet database synchronized with node to height {:?}", + sync::height(&db)?.expect("We just synced, so there is a height available") + ); + }; + + Ok(Context { + cli, + client, + db, + keystore, + data_path, + keystore_path, + slot_config, + }) + } +} + +/// Generate a plaform-specific temporary directory for the wallet +fn temp_dir() -> PathBuf { + // Since it is only used for testing purpose, we don't need a secure temp dir, just a unique one. + std::env::temp_dir().join(format!( + "griffin-wallet-{}", + std::time::UNIX_EPOCH.elapsed().unwrap().as_millis(), + )) +} + +/// Generate the platform-specific default data path for the wallet +fn default_data_path() -> PathBuf { + // This uses the directories crate. + // https://docs.rs/directories/latest/directories/struct.ProjectDirs.html + + // Application developers may want to put actual qualifiers or organization here + let qualifier = ""; + let organization = ""; + let application = env!("CARGO_PKG_NAME"); + + directories::ProjectDirs::from(qualifier, organization, application) + .expect("app directories exist on all supported platforms; qed") + .data_dir() + .into() +} diff --git a/wallet/src/eutxo_examples/vesting/README.md b/wallet/src/eutxo_examples/vesting/README.md index 23cc2d9..731da8c 100644 --- a/wallet/src/eutxo_examples/vesting/README.md +++ b/wallet/src/eutxo_examples/vesting/README.md @@ -4,5 +4,5 @@ This contract allows an "owner" (indicated in the datum via a public key hash) t Keep in mind that: - the `validity_interval_start` is expressed in slots. -- the initial POSIX time of a slot can be obtained as `(slot_number - zero_slot) * MILLI_SECS_PER_SLOT + zero_time`, where `zero_slot` and `zero_time` are genesis configuration parameters and the constant `MILLI_SECS_PER_SLOT` is set in the `griffin_core` crate. -- with the actual configuration, `zero_slot = 0`, `zero_time = 1747081100000` (i.e., 2025-06-12 14:51:40 UTC), `MILLI_SECS_PER_SLOT = 3000` and a deadline set in the datum of `1747081220000` (i.e., 2025-06-12 14:53:40 UTC), the `validity_interval_start` must be at least `(1747081220000 - 1747081100000) / 3000 = 40`, which means that the owner will be able to unlock the funds 40 slots after the genesis. +- the initial POSIX time of a slot can be obtained as `(slot_number - zero_slot) * slot_length + zero_time`, where `zero_slot`, `zero_time` and `slot_length` are genesis configuration parameters. +- with the actual configuration, `zero_slot = 0`, `zero_time = 1747081100000` (i.e., 2025-06-12 14:51:40 UTC), `slot_length = 3000` and a deadline set in the datum of `1747081220000` (i.e., 2025-06-12 14:53:40 UTC), the `validity_interval_start` must be at least `(1747081220000 - 1747081100000) / 3000 = 40`, which means that the owner will be able to unlock the funds 40 slots after the genesis. diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs new file mode 100644 index 0000000..50ec60a --- /dev/null +++ b/wallet/src/lib.rs @@ -0,0 +1,8 @@ +pub mod cli; +pub mod command; +pub mod context; +pub mod keystore; +pub mod order_book; +pub mod rpc; +pub mod sync; +pub mod utils; diff --git a/wallet/src/main.rs b/wallet/src/main.rs index 9919a10..5f42d78 100644 --- a/wallet/src/main.rs +++ b/wallet/src/main.rs @@ -20,303 +20,17 @@ //! ./target/release/griffin-wallet show-all-outputs //! ``` -extern crate alloc; - -use alloc::{string::String, vec::Vec}; -use clap::Parser; -use griffin_core::{ - h224::H224, - pallas_crypto::hash::Hasher as PallasHasher, - types::{Address, Input, Value}, -}; -use hex::FromHex; -use jsonrpsee::http_client::{HttpClient, HttpClientBuilder}; -use parity_scale_codec::{Decode, Encode}; -use sp_core::H256; -use std::path::PathBuf; - mod cli; mod command; +mod context; mod keystore; mod order_book; mod rpc; mod sync; - -use cli::{Cli, Command}; - -/// The default RPC endpoint for the wallet to connect to -const DEFAULT_ENDPOINT: &str = "http://localhost:9944"; +mod utils; #[tokio::main] async fn main() -> anyhow::Result<()> { - env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init(); - - // Parse command line args - let cli = Cli::parse(); - - // If the user specified --tmp or --dev, then use a temporary directory. - let tmp = cli.tmp || cli.dev; - - // Setup the data paths. - let data_path = match tmp { - true => temp_dir(), - _ => cli.base_path.unwrap_or_else(default_data_path), - }; - let keystore_path = data_path.join("keystore"); - let db_path = data_path.join("wallet_database"); - - // Setup the keystore - let keystore = sc_keystore::LocalKeystore::open(keystore_path.clone(), None)?; - - if cli.dev { - // Insert the example Shawn key so example transactions can be signed. - crate::keystore::insert_development_key_for_this_session(&keystore)?; - } - - // Setup jsonrpsee and endpoint-related information. - // https://github.com/paritytech/jsonrpsee/blob/master/examples/examples/http.rs - let client = HttpClientBuilder::default().build(cli.endpoint)?; - - // Read node's genesis block. - let node_genesis_hash = rpc::node_get_block_hash(0, &client) - .await? - .expect("node should be able to return some genesis hash"); - let node_genesis_block = rpc::node_get_block(node_genesis_hash, &client) - .await? - .expect("node should be able to return some genesis block"); - log::debug!("Node's Genesis block::{:?}", node_genesis_hash); - - if cli.purge_db { - std::fs::remove_dir_all(db_path.clone()).map_err(|e| { - log::warn!( - "Unable to remove database directory at {}\nPlease remove it manually.", - db_path.to_string_lossy() - ); - e - })?; - } - - // Open the local database - let db = sync::open_db(db_path, node_genesis_hash, node_genesis_block.clone())?; - - let num_blocks = - sync::height(&db)?.expect("db should be initialized automatically when opening."); - log::info!("Number of blocks in the db: {num_blocks}"); - - if !sled::Db::was_recovered(&db) { - sync::apply_block(&db, node_genesis_block, node_genesis_hash).await?; - } - - // Synchronize the wallet with attached node unless instructed otherwise. - if cli.no_sync { - log::warn!("Skipping sync with node. Using previously synced information.") - } else { - sync::synchronize(&db, &client).await?; - - log::info!( - "Wallet database synchronized with node to height {:?}", - sync::height(&db)?.expect("We just synced, so there is a height available") - ); - } - - // Dispatch to proper subcommand - match cli.command { - Some(Command::VerifyUtxo { input }) => { - println!("Details of coin {}:", hex::encode(input.encode())); - - // Print the details from storage - let coin_from_storage = get_coin_from_storage(&input, &client).await?; - print!("Found in storage. Value: {:?}, ", coin_from_storage); - - // Print the details from the local db - match sync::get_unspent(&db, &input)? { - Some((owner, amount, _)) => { - println!("Found in local db. Value: {amount:?}, owned by {owner}"); - } - None => { - println!("Not found in local db"); - } - } - - Ok(()) - } - Some(cli::Command::SpendValue(args)) => { - command::spend_value(&db, &client, &keystore, args).await - } - Some(Command::InsertKey { seed }) => crate::keystore::insert_key(&keystore, &seed), - Some(Command::GenerateKey { password }) => { - crate::keystore::generate_key(&keystore, password)?; - Ok(()) - } - Some(Command::ShowKeys) => { - crate::keystore::get_keys(&keystore)?.for_each(|pubkey| { - let pk_str: &str = &hex::encode(pubkey); - let hash: String = - PallasHasher::<224>::hash(&<[u8; 32]>::from_hex(pk_str).unwrap()).to_string(); - println!("key: 0x{}; addr: 0x61{}", pk_str, hash); - }); - - Ok(()) - } - Some(Command::RemoveKey { pub_key }) => { - println!( - "CAUTION!!! About permanently remove {pub_key}. This action CANNOT BE REVERSED. Type \"proceed\" to confirm deletion." - ); - - let mut confirmation = String::new(); - std::io::stdin() - .read_line(&mut confirmation) - .expect("Failed to read line"); - - if confirmation.trim() == "proceed" { - crate::keystore::remove_key(&keystore_path, &pub_key) - } else { - println!("Deletion aborted. That was close."); - Ok(()) - } - } - Some(Command::ShowBalance) => { - println!("Balance Summary"); - let mut total = Value::Coin(0); - let balances = sync::get_balances(&db)?; - for (account, balance) in balances { - total += balance.clone(); - println!("{account}: {balance}"); - } - println!("{:-<58}", ""); - println!("Total: {}", total.normalize()); - - Ok(()) - } - Some(Command::ShowAllOutputs) => { - println!("###### Unspent outputs ###########"); - sync::print_unspent_tree(&db)?; - println!("To see all details of a particular UTxO, invoke the `verify-utxo` command."); - Ok(()) - } - Some(Command::ShowOutputsAt(args)) => { - println!( - "###### Unspent outputs at address {} ###########", - args.address - ); - sync::show_outputs_at(&db, args)?; - println!("To see all details of a particular UTxO, invoke the `verify-utxo` command."); - Ok(()) - } - Some(Command::ShowOutputsWithAsset(args)) => { - println!( - "###### Unspent outputs containing asset with name {} and policy ID {} ###########", - args.name, args.policy - ); - sync::show_outputs_with_asset(&db, args)?; - println!("To see all details of a particular UTxO, invoke the `verify-utxo` command."); - Ok(()) - } - Some(Command::ShowAllOrders) => { - println!("###### Available Orders ###########"); - sync::print_orders(&db)?; - Ok(()) - } - Some(cli::Command::BuildTx(args)) => command::build_tx(&db, &client, &keystore, args).await, - None => { - log::info!("No Wallet Command invoked. Exiting."); - Ok(()) - } - }?; - - if tmp { - // Cleanup the temporary directory. - std::fs::remove_dir_all(data_path.clone()).map_err(|e| { - log::warn!( - "Unable to remove temporary data directory at {}\nPlease remove it manually.", - data_path.to_string_lossy() - ); - e - })?; - } - - Ok(()) -} - -/// Parse a string into an H256 that represents a public key -pub(crate) fn h256_from_string(s: &str) -> anyhow::Result { - let s = strip_0x_prefix(s); - - let mut bytes: [u8; 32] = [0; 32]; - hex::decode_to_slice(s, &mut bytes as &mut [u8]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - Ok(H256::from(bytes)) -} - -/// Parse a string into an H224 that represents a policy ID. -pub(crate) fn h224_from_string(s: &str) -> anyhow::Result { - let s = strip_0x_prefix(s); - - let mut bytes: [u8; 28] = [0; 28]; - hex::decode_to_slice(s, &mut bytes as &mut [u8]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - Ok(H224::from(bytes)) -} - -/// Parse a string into an Address that represents a public key -pub(crate) fn address_from_string(s: &str) -> anyhow::Result
{ - let s = strip_0x_prefix(s); - - let mut bytes: [u8; 29] = [0; 29]; - hex::decode_to_slice(s, &mut bytes as &mut [u8]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - Ok(Address(Vec::from(bytes))) -} - -/// Parse an output ref from a string -fn input_from_string(s: &str) -> Result { - let s = strip_0x_prefix(s); - let bytes = - hex::decode(s).map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; - - Input::decode(&mut &bytes[..]) - .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation)) -} - -/// Takes a string and checks for a 0x prefix. Returns a string without a 0x prefix. -fn strip_0x_prefix(s: &str) -> &str { - if &s[..2] == "0x" { - &s[2..] - } else { - s - } -} - -/// Generate a plaform-specific temporary directory for the wallet -fn temp_dir() -> PathBuf { - // Since it is only used for testing purpose, we don't need a secure temp dir, just a unique one. - std::env::temp_dir().join(format!( - "griffin-wallet-{}", - std::time::UNIX_EPOCH.elapsed().unwrap().as_millis(), - )) -} - -/// Generate the platform-specific default data path for the wallet -fn default_data_path() -> PathBuf { - // This uses the directories crate. - // https://docs.rs/directories/latest/directories/struct.ProjectDirs.html - - // Application developers may want to put actual qualifiers or organization here - let qualifier = ""; - let organization = ""; - let application = env!("CARGO_PKG_NAME"); - - directories::ProjectDirs::from(qualifier, organization, application) - .expect("app directories exist on all supported platforms; qed") - .data_dir() - .into() -} - -/// Given an output ref, fetch the details about its value from the node's -/// storage. -async fn get_coin_from_storage(input: &Input, client: &HttpClient) -> anyhow::Result { - let utxo = rpc::fetch_storage(input, client).await?; - let coin_in_storage: Value = utxo.value; - - Ok(coin_in_storage) + let cmd = cli::WalletCommand::Wallet(cli::Command::ShowAllOutputs); + cmd.run().await } diff --git a/wallet/src/rpc.rs b/wallet/src/rpc.rs index 77235ce..5773f8d 100644 --- a/wallet/src/rpc.rs +++ b/wallet/src/rpc.rs @@ -1,6 +1,7 @@ //! Helper functions for communicating with the Node's RPC endpoint. use griffin_core::types::{Input, OpaqueBlock, Output}; +use griffin_core::{SLOT_LENGTH, ZERO_SLOT, ZERO_TIME}; use jsonrpsee::{core::client::ClientT, http_client::HttpClient, rpc_params}; use parity_scale_codec::Encode; use sp_core::H256; @@ -9,7 +10,7 @@ use sp_core::H256; pub async fn node_get_block_hash(height: u32, client: &HttpClient) -> anyhow::Result> { let params = rpc_params![Some(height)]; let rpc_response: Option = client.request("chain_getBlockHash", params).await?; - let maybe_hash = rpc_response.map(|s| crate::h256_from_string(&s).unwrap()); + let maybe_hash = rpc_response.map(|s| crate::utils::h256_from_string(&s).unwrap()); Ok(maybe_hash) } @@ -39,3 +40,39 @@ pub async fn fetch_storage(input: &Input, client: &HttpClient) -> anyhow::Result let utxo = routput?; Ok(utxo) } + +/// Get the Node's initial POSIX time +pub async fn node_get_zero_time(client: &HttpClient) -> anyhow::Result> { + let params = rpc_params![hex::encode(str::from_utf8(ZERO_TIME).unwrap())]; + let rpc_response: Option = client.request("state_getStorage", params).await?; + let time_bytes: [u8; 8] = hex::decode(rpc_response.unwrap().strip_prefix("0x").unwrap()) + .unwrap() + .try_into() + .unwrap(); + let time = u64::from_le_bytes(time_bytes); + Ok(Some(time)) +} + +/// Get the Node's zero slot +pub async fn node_get_zero_slot(client: &HttpClient) -> anyhow::Result> { + let params = rpc_params![hex::encode(str::from_utf8(ZERO_SLOT).unwrap())]; + let rpc_response: Option = client.request("state_getStorage", params).await?; + let slot_bytes: [u8; 8] = hex::decode(rpc_response.unwrap().strip_prefix("0x").unwrap()) + .unwrap() + .try_into() + .unwrap(); + let slot = u64::from_le_bytes(slot_bytes); + Ok(Some(slot)) +} + +/// Get the Node's slot length +pub async fn node_get_slot_length(client: &HttpClient) -> anyhow::Result> { + let params = rpc_params![hex::encode(str::from_utf8(SLOT_LENGTH).unwrap())]; + let rpc_response: Option = client.request("state_getStorage", params).await?; + let slot_length_bytes: [u8; 4] = hex::decode(rpc_response.unwrap().strip_prefix("0x").unwrap()) + .unwrap() + .try_into() + .unwrap(); + let slot_length = u32::from_le_bytes(slot_length_bytes); + Ok(Some(slot_length)) +} diff --git a/wallet/src/sync.rs b/wallet/src/sync.rs index 58e04c9..8b1e2df 100644 --- a/wallet/src/sync.rs +++ b/wallet/src/sync.rs @@ -167,7 +167,7 @@ pub(crate) async fn synchronize_helper(db: &Db, client: &HttpClient) -> anyhow:: /// Gets the owner and amount associated with an input from the unspent table /// /// Some if the input exists, None if it doesn't -pub(crate) fn get_unspent( +pub fn get_unspent( db: &Db, input: &Input, ) -> anyhow::Result)>> { @@ -377,27 +377,27 @@ pub(crate) fn height(db: &Db) -> anyhow::Result> { } /// Debugging use. Print the entire unspent outputs tree. -pub(crate) fn print_unspent_tree(db: &Db) -> anyhow::Result<()> { - show_outputs_by(|_, _| true, db)?; - Ok(()) +pub(crate) fn print_unspent_tree(db: &Db) -> anyhow::Result> { + get_outputs_by(|_, _| true, db) } /// Print the unspent outputs at a specific address. -pub(crate) fn show_outputs_at(db: &Db, args: crate::cli::ShowOutputsAtArgs) -> anyhow::Result<()> { - show_outputs_by(|owner, _| *owner == args.address, db)?; - Ok(()) +pub fn get_outputs_at( + db: &Db, + args: crate::cli::ShowOutputsAtArgs, +) -> anyhow::Result> { + get_outputs_by(|owner, _| *owner == args.address, db) } /// Print the unspent outputs with a specific asset. -pub(crate) fn show_outputs_with_asset( +pub fn get_outputs_with_asset( db: &Db, args: crate::cli::ShowOutputsWithAssetArgs, -) -> anyhow::Result<()> { - show_outputs_by( +) -> anyhow::Result> { + get_outputs_by( |_, amount| amount.contains_asset(&AssetName::from(args.name.clone()), &args.policy), db, - )?; - Ok(()) + ) } /// Print the available orders. @@ -470,31 +470,45 @@ pub(crate) fn get_balances(db: &Db) -> anyhow::Result(f: F, db: &Db) -> anyhow::Result<()> +/// Print the unspent outputs info. +pub fn show_outputs(outputs_info: Vec) { + for x in outputs_info.iter() { + let input_string = hex::encode(x.input.encode()); + let datum_option_hex = x.datum_option.clone().map(|datum| hex::encode(datum.0)); + + println!( + "{}:\n address: {},\n datum: {:?},\n amount: {}", + input_string.bold(), + color_address(&x.address), + datum_option_hex, + x.value.normalize(), + ); + } +} + +/// Get the unspent outputs that satisfy the predicate `f`. +fn get_outputs_by(f: F, db: &Db) -> anyhow::Result> where F: Fn(&Address, &Value) -> bool, { - let wallet_unspent_tree = db.open_tree(UNSPENT)?; - for x in wallet_unspent_tree.iter() { + let mut result = Vec::new(); + for x in db.open_tree(UNSPENT)?.iter() { let (input_ivec, owner_amount_datum_ivec) = x?; - let input = hex::encode(input_ivec); + let input = ::decode(&mut &input_ivec[..])?; let (owner_pubkey, amount, datum_option) = <(Address, Value, Option)>::decode(&mut &owner_amount_datum_ivec[..])?; - let datum_option_hex = datum_option.map(|datum| hex::encode(datum.0)); if f(&owner_pubkey, &amount) { - println!( - "{}:\n address: {},\n datum: {:?},\n amount: {}", - input.bold(), - color_address(&owner_pubkey), - datum_option_hex, - amount.normalize(), - ); + result.push(ResolvedInputInfo::from(( + input, + owner_pubkey, + amount, + datum_option, + ))) } } - Ok(()) + Ok(result) } /// Color the address red if it's a script address (starts with "70"), green otherwise @@ -505,3 +519,21 @@ fn color_address(address: &Address) -> colored::ColoredString { address.to_string().green() } } + +pub struct ResolvedInputInfo { + pub input: Input, + pub address: Address, + pub value: Value, + pub datum_option: Option, +} + +impl From<(Input, Address, Value, Option)> for ResolvedInputInfo { + fn from((input, address, value, datum_option): (Input, Address, Value, Option)) -> Self { + Self { + input, + address, + value, + datum_option, + } + } +} diff --git a/wallet/src/utils.rs b/wallet/src/utils.rs new file mode 100644 index 0000000..5a43496 --- /dev/null +++ b/wallet/src/utils.rs @@ -0,0 +1,69 @@ +use griffin_core::{ + h224::H224, + types::{Address, Input, Value}, +}; +use parity_scale_codec::Decode; +use sp_core::H256; + +use crate::rpc; + +/// Parse a string into an H256 that represents a public key +pub fn h256_from_string(s: &str) -> anyhow::Result { + let s = strip_0x_prefix(s); + + let mut bytes: [u8; 32] = [0; 32]; + hex::decode_to_slice(s, &mut bytes as &mut [u8]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + Ok(H256::from(bytes)) +} + +/// Parse a string into an H224 that represents a policy ID. +pub(crate) fn h224_from_string(s: &str) -> anyhow::Result { + let s = strip_0x_prefix(s); + + let mut bytes: [u8; 28] = [0; 28]; + hex::decode_to_slice(s, &mut bytes as &mut [u8]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + Ok(H224::from(bytes)) +} + +/// Parse a string into an Address that represents a public key +pub(crate) fn address_from_string(s: &str) -> anyhow::Result
{ + let s = strip_0x_prefix(s); + + let mut bytes: [u8; 29] = [0; 29]; + hex::decode_to_slice(s, &mut bytes as &mut [u8]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + Ok(Address(Vec::from(bytes))) +} + +/// Parse an output ref from a string +pub fn input_from_string(s: &str) -> Result { + let s = strip_0x_prefix(s); + let bytes = + hex::decode(s).map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation))?; + + Input::decode(&mut &bytes[..]) + .map_err(|_| clap::Error::new(clap::error::ErrorKind::ValueValidation)) +} + +/// Takes a string and checks for a 0x prefix. Returns a string without a 0x prefix. +fn strip_0x_prefix(s: &str) -> &str { + if &s[..2] == "0x" { + &s[2..] + } else { + s + } +} + +/// Given an output ref, fetch the details about its value from the node's +/// storage. +pub async fn get_coin_from_storage( + input: &Input, + client: &jsonrpsee::http_client::HttpClient, +) -> anyhow::Result { + let utxo = rpc::fetch_storage(input, client).await?; + let coin_in_storage: Value = utxo.value; + + Ok(coin_in_storage) +}