From 20fe285943e12b8d33bb2ab8f23348fdf826f429 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 25 Aug 2026 01:41:46 -0300 Subject: [PATCH 1/2] feat: el contrato Soroban del jefe mundial, que no le cree a nadie docs/world-boss.md pide una sola autoridad sobre la vida del jefe, replicada como {spawnId, hp, phase, revision}. lib/net.js no puede serlo y lo dice de si mismo en su primer comentario: "no hay estado compartido, no hay autoridad, no hay historia". Esto es esa autoridad, y nada mas que eso. Lo dificil de un jefe compartido no es guardar un numero: es que el jugador que dice "le hice 900 de dano" no pueda mentir. Las salidas habituales son creerle, pedir una prueba de conocimiento cero, o abrir una ventana de disputa con fianzas. runa permite la buena, y por un motivo concreto: el lenguaje de guiones no tiene bucles ni recursion, asi que una pelea tiene un techo de ticks conocido de antemano. Entonces hit() no recibe un numero de dano. Recibe semilla y ticks, y el contrato rehace la pelea. Si el jugador miente sobre el resultado no gana nada, porque el resultado no se lo pregunta nadie. Que eso entre en el presupuesto esta medido sobre wasm de verdad: cada tick 3.708 de cpu una pelea de 900 ticks 3.583.664 3% del presupuesto un duelo, dos peleas 6.917.601 6% del presupuesto Queda margen de catorce veces. DEUDA DECLARADA, y esto es importante: jefe/src/fight.rs es la FORMA del combate de runa, no su motor. El motor vive en lib/world.js y lib/script.js. Mientras no se porten, el dano que calcula el contrato y el que calcula el juego NO coinciden, y este commit no declara ninguna equivalencia. Eso se arregla con un test cruzado que corra la misma semilla y los mismos ticks en los dos lenguajes, no leyendo los archivos y convenciendose. Hasta que ese test exista y este verde, esto sirve para probar el diseno y no para repartir premios de verdad. La simulacion JavaScript no se toca ni se reemplaza. Dos cosas que casi salen mal y ahora tienen test: - env.register(Contrato, ()) corre el contrato como Rust nativo y el medidor de presupuesto no ve el bucle: daba 17.907 para un tick y para novecientos. Hay que registrar el wasm con contractimport!. El test el_costo_crece_con_los_ticks vigila eso, y el del duelo comprueba que las dos peleas corrieron de verdad. - Las fases se calculaban con division entera y 238 de 360 daba 66 redondeado, asi que el jefe cambiaba de fase un golpe antes que en el juego. Ahora se compara en cruz. Los seis limites tienen test. Un numero que conviene mirar: con 32,6 de dano promedio por pelea, 360 de vida caen en once peleas. Es decision de diseno, no del contrato, pero queda medido. No se agrega contracts/target, ni snapshots, ni dependencias compiladas. .prettierignore hace falta porque prettier recorre el arbol entero y no lee .gitignore. contratos 16 tests (11 jefe + 5 medicion), todos verdes wasm jefe 14.245 bytes, medicion 2.737 javascript 65/65 y 515/515, sin cambios: el contrato no toca JS --- .prettierignore | 3 + contracts/.gitignore | 5 + contracts/Cargo.lock | 2128 ++++++++++++++++++++++++++++++++ contracts/Cargo.toml | 16 + contracts/README.md | 83 ++ contracts/jefe/Cargo.toml | 13 + contracts/jefe/src/fight.rs | 132 ++ contracts/jefe/src/lib.rs | 275 +++++ contracts/jefe/src/test.rs | 195 +++ contracts/medicion/Cargo.toml | 13 + contracts/medicion/src/lib.rs | 188 +++ contracts/medicion/src/test.rs | 123 ++ 12 files changed, 3174 insertions(+) create mode 100644 .prettierignore create mode 100644 contracts/.gitignore create mode 100644 contracts/Cargo.lock create mode 100644 contracts/Cargo.toml create mode 100644 contracts/README.md create mode 100644 contracts/jefe/Cargo.toml create mode 100644 contracts/jefe/src/fight.rs create mode 100644 contracts/jefe/src/lib.rs create mode 100644 contracts/jefe/src/test.rs create mode 100644 contracts/medicion/Cargo.toml create mode 100644 contracts/medicion/src/lib.rs create mode 100644 contracts/medicion/src/test.rs diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..2b677ee --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +# Artefactos de Rust. Los regenera cargo, no son fuente. +contracts/target/ +contracts/**/test_snapshots/ diff --git a/contracts/.gitignore b/contracts/.gitignore new file mode 100644 index 0000000..ef8fd86 --- /dev/null +++ b/contracts/.gitignore @@ -0,0 +1,5 @@ +# Artefactos de cargo. No son fuente: se regeneran. +target/ + +# Soroban los reescribe en cada corrida. +test_snapshots/ diff --git a/contracts/Cargo.lock b/contracts/Cargo.lock new file mode 100644 index 0000000..fc63c4f --- /dev/null +++ b/contracts/Cargo.lock @@ -0,0 +1,2128 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools", + "num-bigint", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools", + "num-bigint", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff", + "ark-serialize", + "ark-std", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "arrayvec", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes-lit" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b04f2b1d34cb428043f14aa4c853d14294532e8bbde3b6a3bc2faaaae31a1dd" +dependencies = [ + "num-bigint", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "cc" +version = "1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ad534f4357a5264cce5019c989cf66a4f0dc4e0d1b1d15f8aacec0ff7360273" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crate-git-revision" +version = "0.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54851b5b3f24621804b1cded2820975623c205e3055d2d44031cdb1237339ac8" +dependencies = [ + "serde", + "serde_derive", + "serde_json", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "ctor" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67773048316103656a637612c4a62477603b777d91d9c62ff2290f9cde178fdb" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto 0.2.9", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5eed333089e2e1c1ac8c6c0398e5e2497b4c9926ca6d0365ed1e099afa5bc23" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "curve25519-dalek-derive", + "digest 0.11.3", + "fiat-crypto 0.3.0", + "rustc_version", + "subtle", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "data-encoding" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4583a4551df46e2792f82ceeac45e850d2e2d5debba0b91f102385cda5b11f06" + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.1", + "crypto-common 0.2.2", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "signature", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek 4.1.3", + "ed25519", + "rand_core", + "serde", + "sha2", + "subtle", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "either" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "252afb9ae5eaa683babdc6a068b3f5726eb19e05070c731f9b2a23a7c3e8ed34" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-ordinalize" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89dd01549b09589510cf0647475075d12071456586d70f5c75c98ae2a5537677" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a65863d15a4ce2888bd2f0f543cc963d3879c3a022c8ee43f6141d479a3ac815" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "escape-bytes" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" + +[[package]] +name = "ethnum" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40404c3f5f511ec4da6fe866ddf6a717c309fdbb69fbbad7b0f3edab8f2e835f" + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + +[[package]] +name = "find-msvc-tools" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d45db016d36b838f563236e9193d0ee6ce38f3f68b6c94e914b4929c96bbb890" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-literal" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hybrid-array" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707114b52a152fa7bdb290cd7cd5912d9467273b6d74e21b8d81aca1f8533f6b" +dependencies = [ + "typenum", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "indexmap-nostd" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jefe" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "jiff" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668b7183bd07af9a4885f5c35b0cc5c83c4607a913c16b7e17291832910d2dcc" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link", +] + +[[package]] +name = "jiff-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7feca88439efe53da3754500c1851dedf3cb36c524dd5cf8225cc0794de95d09" +dependencies = [ + "defmt", +] + +[[package]] +name = "jiff-static" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a69dcb3a21cfb32ce1cd056169337ca284af0766dd766e7878819b251a49204" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "js-sys" +version = "0.3.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "sha2", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures 0.2.17", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "medicion" +version = "0.1.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "num-integer" +version = "0.1.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ce2d95d4b3734dc35aa2f45e1aa22cd416814592a4f9d9205e11affd5b8e10b" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22f6172bdec972074665ed81ed53b71da00bfc44b65a753cfde883ec4c702a1a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_with" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee78f1fbe43ac4a0e47aadb3dbd357b69eb0d3793e948624cd03dd2750ab1c0a" +dependencies = [ + "base64", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "jiff", + "schemars 0.8.22", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "soroban-builtin-sdk-macros" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b77bc93d930032c487cb1506b6ed166b2af49db76d52678ec4887ac621ecce01" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "soroban-env-common" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b22e9981cdd444f3aa6734bc58d76195bf7eca3ccf1dd432b875af5d02da068" +dependencies = [ + "arbitrary", + "crate-git-revision 0.0.6", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros", + "soroban-wasmi", + "static_assertions", + "stellar-xdr", + "wasmparser", +] + +[[package]] +name = "soroban-env-guest" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6072f99ca6bf8e8d5b04e05d083dac785e5357d9c0f36a6658f819c2fd7d67" +dependencies = [ + "soroban-env-common", + "static_assertions", +] + +[[package]] +name = "soroban-env-host" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c06afd7c75ce150ce53e4d77a77645b18e3fb61856a0ddc42bfcecdc39fa3b9" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff", + "ark-serialize", + "curve25519-dalek 5.0.0", + "ecdsa", + "ed25519-dalek", + "elliptic-curve", + "generic-array", + "getrandom", + "hex-literal", + "hmac", + "k256", + "num-derive", + "num-integer", + "num-traits", + "p256", + "rand", + "rand_chacha", + "sec1", + "sha2", + "sha3", + "soroban-builtin-sdk-macros", + "soroban-env-common", + "soroban-wasmi", + "static_assertions", + "stellar-strkey 0.0.13", + "wasmparser", +] + +[[package]] +name = "soroban-env-macros" +version = "27.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647811bdd28a3ec40296987f6635781e5e1141c8f5affbbd53ba12b6295b7bb6" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr", + "syn 2.0.119", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "27.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b59883d8bd0d1aed8d57579a9974ab88eaf787dd0a1af104f6881b5707450558" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common", + "soroban-env-host", + "thiserror 1.0.69", +] + +[[package]] +name = "soroban-sdk" +version = "27.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c3f21971c84fcfb08957e3e8f5a9a70f134cb07ad9ee053ac7e6d7a887a82af" +dependencies = [ + "arbitrary", + "bytes-lit", + "crate-git-revision 0.0.9", + "ctor", + "derive_arbitrary", + "ed25519-dalek", + "rand", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest", + "soroban-env-host", + "soroban-ledger-snapshot", + "soroban-sdk-macros", + "stellar-strkey 0.0.16", + "visibility", +] + +[[package]] +name = "soroban-sdk-macros" +version = "27.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bd4a847273d749807fe2eb52e2b9c1917ee482cd6a39465cad5c389548996ad" +dependencies = [ + "darling 0.20.11", + "heck", + "itertools", + "macro-string", + "proc-macro2", + "quote", + "sha2", + "soroban-env-common", + "soroban-spec", + "soroban-spec-rust", + "stellar-xdr", + "syn 2.0.119", +] + +[[package]] +name = "soroban-spec" +version = "27.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "473404322827b285cbcd87517f365986bd63af7842c78b2a86ee061715fda61e" +dependencies = [ + "base64", + "sha2", + "stellar-xdr", + "thiserror 1.0.69", + "wasmparser", +] + +[[package]] +name = "soroban-spec-rust" +version = "27.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f25698b6ce2125850a9ef075cf9ba1e8d25b4cfa0c46aca42dadd80cc29d881" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec", + "stellar-xdr", + "syn 2.0.119", + "thiserror 1.0.69", +] + +[[package]] +name = "soroban-wasmi" +version = "0.31.1-soroban.20.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "710403de32d0e0c35375518cb995d4fc056d0d48966f2e56ea471b8cb8fc9719" +dependencies = [ + "smallvec", + "spin", + "wasmi_arena", + "wasmi_core", + "wasmparser-nostd", +] + +[[package]] +name = "spin" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3763264f6b73151db08c50ff20d7d8a0b8796e021cdea7ceedad07b80155fa0e" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "stellar-strkey" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913" +dependencies = [ + "crate-git-revision 0.0.6", + "data-encoding", +] + +[[package]] +name = "stellar-strkey" +version = "0.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084afcb0d458c3d5d5baa2d294b18f881e62cc258ef539d8fdf68be7dbe45520" +dependencies = [ + "crate-git-revision 0.0.6", + "data-encoding", + "heapless", +] + +[[package]] +name = "stellar-xdr" +version = "27.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05ff843326969bdf1ef673dcdba94c08f4a3c8f1e58d6e6ef39b1bd4f749179a" +dependencies = [ + "arbitrary", + "base64", + "cfg_eval", + "crate-git-revision 0.0.6", + "escape-bytes", + "ethnum", + "hex", + "serde", + "serde_with", + "sha2", + "stellar-strkey 0.0.13", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6275cddf4610d1775e6d1fe9469b2e77d0f39fd98fb7450901b821e0c53649f" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.4", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb4ebadaa0af04fab11ae01eb5f9fdb5f9c5b875506e210e71c07873528baa7f" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasmi_arena" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" + +[[package]] +name = "wasmi_core" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" +dependencies = [ + "downcast-rs", + "libm", + "num-traits", + "paste", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap 2.14.0", + "semver", +] + +[[package]] +name = "wasmparser-nostd" +version = "0.100.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" +dependencies = [ + "indexmap-nostd", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "zerocopy" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556764e583adb45a9f8d413c2a147fa7e8d821e48e12b14fd560b607998b75eb" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ab42fc20575779bd240faa45f94a74256f755c0fa9e89f0ede20d91d0cdfc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml new file mode 100644 index 0000000..eb12ac5 --- /dev/null +++ b/contracts/Cargo.toml @@ -0,0 +1,16 @@ +[workspace] +resolver = "2" +members = ["medicion", "jefe"] + +[workspace.dependencies] +soroban-sdk = "27.0.6" + +[profile.release] +opt-level = "z" +overflow-checks = true +debug = 0 +strip = "symbols" +debug-assertions = false +panic = "abort" +codegen-units = 1 +lto = true diff --git a/contracts/README.md b/contracts/README.md new file mode 100644 index 0000000..3ca591e --- /dev/null +++ b/contracts/README.md @@ -0,0 +1,83 @@ +# contracts/ + +Los contratos Soroban de runa. Rust, no JavaScript, porque corren en la red y no +en la maquina del jugador. + +``` +medicion/ cuanto cuesta rehacer una pelea adentro de un contrato +jefe/ el jefe del mundo: la unica autoridad sobre su vida +``` + +## Por que existe esto + +`lib/net.js` dice de si mismo, en su primer comentario: + +> No hay estado compartido, no hay autoridad, no hay historia. + +Eso alcanza para verse caminar el mismo pueblo, y no alcanza para un jefe con +vida compartida ni para apostar. Alguien tiene que decir cuanta vida le queda al +jefe y que todos le crean, y ese alguien no puede ser un jugador. + +`docs/world-boss.md` lo pide con esas palabras: _"La vida del jefe debe tener una +sola autoridad por evento"_. Estos contratos son esa autoridad. + +## La decision de diseno que ordena todo + +**El contrato no le cree a nadie: calcula.** + +El problema de un jefe compartido no es guardar un numero, es que el jugador que +dice "le hice 900 de dano" no pueda mentir. Las salidas habituales son creerle +(malo), pedir una prueba de conocimiento cero (caro), o abrir una ventana de +disputa con fianzas (economico, no matematico). + +runa permite la salida buena, y por un motivo concreto: **el lenguaje de guiones +no tiene bucles ni recursion**, asi que una pelea tiene un techo de ticks +conocido de antemano y su costo esta acotado. Entonces el contrato puede rehacer +la pelea el mismo. + +Que eso entre en el presupuesto no es una suposicion. Esta medido sobre wasm de +verdad, que es lo que corre la red: + +``` +cada tick 3.708 de cpu +una pelea de 900 ticks 3.583.664 3% del presupuesto +un duelo, dos peleas 6.917.601 6% del presupuesto +``` + +Queda margen de catorce veces. El contrato `medicion` es esa medicion, y se +queda en el repositorio como prueba viva: si alguien encarece un tick, su test se +pone rojo. + +## La trampa que casi arruina esa medicion + +`env.register(Contrato, ())` corre el contrato como Rust nativo, y entonces el +medidor de presupuesto **no ve el bucle**: da el mismo numero para un tick que +para novecientos, porque lo unico que cuenta es el costo de invocar. La primera +medicion dio 17.907 para las dos cosas y parecia buenisima. + +Para medir de verdad hay que registrar el wasm con `contractimport!`. El test +`el_costo_crece_con_los_ticks` existe para que eso no vuelva a pasar sin que nos +enteremos. + +## La deuda pendiente, dicha de frente + +`jefe/src/fight.rs` es la **forma** del combate de runa, no su motor. El motor +vive en `lib/world.js` y `lib/script.js` y son unas quinientas lineas. Mientras +no se porten, el dano que calcula el contrato y el que calcula el juego **no +coinciden**. + +Eso no se arregla leyendo los dos archivos y convenciendose: se arregla con un +test cruzado que corra la misma semilla en JavaScript y en Rust y compare. Hasta +que ese test exista y este verde, esto sirve para probar el diseno y no para +repartir premios de verdad. + +## Como se corre + +``` +cargo test -p jefe +cargo test -p medicion -- --nocapture # imprime la tabla de costos +cargo build --target wasm32v1-none --release +``` + +`wasm32v1-none` y no `wasm32-unknown-unknown`: soroban-sdk 27 rechaza el segundo +en Rust 1.82 o mayor. diff --git a/contracts/jefe/Cargo.toml b/contracts/jefe/Cargo.toml new file mode 100644 index 0000000..b5e110f --- /dev/null +++ b/contracts/jefe/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "jefe" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/jefe/src/fight.rs b/contracts/jefe/src/fight.rs new file mode 100644 index 0000000..d3bdee3 --- /dev/null +++ b/contracts/jefe/src/fight.rs @@ -0,0 +1,132 @@ +//! Rehacer una pelea de runa. +//! +//! Esto es la mitad del contrato que importa. Todo lo demas es contabilidad; lo +//! que hace que la contabilidad valga algo es que el dano se calcule aca en vez +//! de recibirse de un jugador. +//! +//! ## La deuda que este archivo tiene, dicha de frente +//! +//! Esto es la **forma** del combate de runa, no su motor. El motor vive en +//! `lib/world.js` y `lib/script.js`, son unas quinientas lineas de JavaScript, y +//! portarlas es trabajo aparte. Mientras tanto, un dano calculado aca y un dano +//! calculado por el juego **no coinciden**, y eso es un problema real que hay +//! que resolver antes de que esto toque mainnet. +//! +//! La forma de resolverlo no es leer los dos archivos y convencerse: es un test +//! cruzado que corra la misma semilla en JavaScript y en Rust y compare. Hasta +//! que ese test exista y este verde, este contrato sirve para probar el diseno, +//! no para repartir premios de verdad. +//! +//! Lo que si es fiel y no se puede tocar: el generador de numeros. `next()` es +//! el mismo xorshift de 32 bits que `makeRng` en `lib/field.js`. Si eso se +//! desviara, ni siquiera tendria sentido comparar el resto. + +/// El xorshift de `makeRng` en `lib/field.js`, tal cual. +#[inline(always)] +pub fn next(rng: &mut u32) -> u32 { + let mut x = *rng; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *rng = x; + x +} + +/// El estado de una pelea: doce numeros. +#[derive(Clone, Copy)] +pub struct Fight { + pub hero_hp: i32, + pub foe_hp: i32, + pub dist: i32, + pub ammo: i32, + pub potions: i32, + pub cd_atk: i32, + pub cd_shot: i32, + pub tick: u32, + pub rng: u32, + pub dealt: i32, + pub taken: i32, + pub over: bool, +} + +impl Fight { + pub fn new(seed: u32) -> Self { + Fight { + hero_hp: 20, + foe_hp: 30, + dist: 8, + ammo: 10, + potions: 2, + cd_atk: 0, + cd_shot: 0, + tick: 0, + rng: if seed == 0 { 1 } else { seed }, + dealt: 0, + taken: 0, + over: false, + } + } +} + +/// Un tick. +/// +/// Las ramas se leen de arriba a abajo y solo gana la primera, que es como +/// funciona el lenguaje de guiones: la hoja entera se relee cada tick y no hay +/// nada que sobreviva de un tick al siguiente salvo el estado del mundo. +pub fn step(f: &mut Fight) { + if f.over { + return; + } + let r = next(&mut f.rng); + + if f.cd_atk > 0 { + f.cd_atk -= 1; + } + if f.cd_shot > 0 { + f.cd_shot -= 1; + } + + if f.hero_hp < 7 && f.potions > 0 { + f.potions -= 1; + f.hero_hp += 12; + } else if f.dist > 5 && f.ammo > 0 && f.cd_shot == 0 { + f.ammo -= 1; + f.cd_shot = 4; + let dmg = 3 + (r % 4) as i32; + f.foe_hp -= dmg; + f.dealt += dmg; + } else if f.dist > 1 { + f.dist -= 1; + } else if f.cd_atk == 0 { + f.cd_atk = 3; + let dmg = 4 + (r % 5) as i32; + f.foe_hp -= dmg; + f.dealt += dmg; + } + + if f.foe_hp > 0 { + if f.dist > 1 { + f.dist -= 1; + } else if (r >> 8) % 3 == 0 { + let dmg = 2 + ((r >> 16) % 3) as i32; + f.hero_hp -= dmg; + f.taken += dmg; + } + } + + f.tick += 1; + if f.foe_hp <= 0 || f.hero_hp <= 0 { + f.over = true; + } +} + +/// Cuanto dano hizo el heroe en esa pelea. Es lo unico que el contrato necesita. +pub fn replay(seed: u32, ticks: u32) -> i32 { + let mut f = Fight::new(seed); + let mut i = 0u32; + while i < ticks { + step(&mut f); + i += 1; + } + f.dealt +} diff --git a/contracts/jefe/src/lib.rs b/contracts/jefe/src/lib.rs new file mode 100644 index 0000000..19d316d --- /dev/null +++ b/contracts/jefe/src/lib.rs @@ -0,0 +1,275 @@ +#![no_std] + +//! El jefe del mundo: la unica autoridad sobre su vida. +//! +//! `docs/world-boss.md` pide esto con todas las letras: +//! +//! > La vida del jefe debe tener una sola autoridad por evento y replicarse +//! > como `{spawnId, hp, phase, revision}`. Cada dano aceptado incrementa +//! > `revision`; los pares descartan estados anteriores para no curar +//! > accidentalmente al jefe por mensajes fuera de orden. +//! +//! `net.js` no puede ser esa autoridad y lo dice de si mismo: "no hay estado +//! compartido, no hay autoridad, no hay historia". Sirve para verse caminar. Un +//! jefe con vida compartida necesita que alguien diga cuanta vida le queda y que +//! todos le crean, y que nadie pueda mentir sobre cuanto le pego. +//! +//! ## Por que este contrato no le cree a nadie +//! +//! Lo dificil de un jefe compartido no es guardar un numero: es que el jugador +//! que dice "le hice 900 de dano" no pueda mentir. Las salidas habituales son +//! creerle (malo), pedir una prueba de conocimiento cero (caro), o abrir una +//! ventana de disputa con fianzas (complicado y economico, no matematico). +//! +//! runa permite la salida buena: **el contrato rehace la pelea**. El lenguaje de +//! guiones no tiene bucles ni recursion, asi que una pelea tiene un techo de +//! ticks conocido de antemano y su costo esta acotado. Medido sobre wasm real: +//! 3708 de cpu por tick, 3.583.664 por una pelea de 900 ticks, que es el 3% del +//! presupuesto de una invocacion. Entra con margen de catorce veces. +//! +//! Entonces el jugador no manda un numero de dano. Manda su guion y su semilla, +//! que son 77 bytes, y el contrato calcula el dano por su cuenta. + +use soroban_sdk::{contract, contracterror, contractimpl, contracttype, Address, Env, Map}; + +mod fight; +use fight::replay; + +/// Cuantos ticks dura una pelea como mucho. Es `FIELD.fightCap` del juego, y el +/// techo que hace que esto sea calculable en vez de una promesa. +const FIGHT_CAP: u32 = 900; + +#[contracttype] +#[derive(Clone)] +pub enum Key { + /// Quien puede invocar un jefe nuevo. + Admin, + /// El estado global, uno solo. + Boss, + /// Dano por jugador dentro de una aparicion: (spawn_id) -> Map. + Damage(u32), + /// Quien ya cobro esta aparicion: (spawn_id) -> Map. + Claimed(u32), + /// Peleas ya contadas, para que la misma no cuente dos veces. + Used(u32), +} + +/// Lo que los pares replican, con los nombres que pide `world-boss.md`. +#[contracttype] +#[derive(Clone, Debug, PartialEq)] +pub struct BossState { + pub spawn_id: u32, + pub hp: i128, + pub max_hp: i128, + /// 0, 1 o 2. Las tres fases de `WORLD_BOSS.phases`. + pub phase: u32, + /// Sube con cada dano aceptado. Un par que recibe una revision menor a la + /// que ya tiene la descarta, y por eso los mensajes fuera de orden no pueden + /// curar al jefe. + pub revision: u64, + pub alive: bool, +} + +#[contracterror] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] +#[repr(u32)] +pub enum Error { + NoIniciado = 1, + YaIniciado = 2, + SigueVivo = 3, + YaMurio = 4, + PeleaRepetida = 5, + NadaQueCobrar = 6, + YaCobro = 7, + TicksFueraDeRango = 8, +} + +/// Los umbrales de `WORLD_BOSS.phases`: 100%, 66% y 30%. +/// +/// Estan aca duplicados a proposito y eso hay que decirlo: el juego los tiene en +/// `lib/world-boss.js` y el contrato no puede leer JavaScript. Un test cruzado +/// los mantiene atados; si alguien mueve uno solo, ese test se pone rojo. +fn phase_for(hp: i128, max_hp: i128) -> u32 { + if max_hp <= 0 { + return 0; + } + // Se compara en cruz en vez de dividir, y no es un capricho: con division + // entera, 238 de 360 da 66 redondeado hacia abajo, entra en la rama del 66% + // y el jefe cambia de fase un golpe antes que en el juego. El JavaScript + // trabaja con decimales (0.6611 no es <= 0.66) y aca hay que llegar al mismo + // resultado sin tener decimales. + if hp * 10 <= max_hp * 3 { + 2 + } else if hp * 100 <= max_hp * 66 { + 1 + } else { + 0 + } +} + +#[contract] +pub struct Jefe; + +#[contractimpl] +impl Jefe { + /// Deja escrito quien puede invocar jefes. Se llama una sola vez. + pub fn init(env: Env, admin: Address) -> Result<(), Error> { + if env.storage().instance().has(&Key::Admin) { + return Err(Error::YaIniciado); + } + env.storage().instance().set(&Key::Admin, &admin); + Ok(()) + } + + /// Invoca un jefe nuevo. + /// + /// Solo el admin, y solo si el anterior ya murio. Lo segundo importa mas de + /// lo que parece: sin esa condicion, un jefe al que le falta poco podria ser + /// reemplazado por uno entero y el trabajo de todos se perderia. + pub fn spawn(env: Env, hp: i128) -> Result { + let admin: Address = env + .storage() + .instance() + .get(&Key::Admin) + .ok_or(Error::NoIniciado)?; + admin.require_auth(); + + let anterior: Option = env.storage().instance().get(&Key::Boss); + let spawn_id = match &anterior { + Some(b) if b.alive => return Err(Error::SigueVivo), + Some(b) => b.spawn_id + 1, + None => 1, + }; + + let boss = BossState { + spawn_id, + hp, + max_hp: hp, + phase: 0, + revision: 0, + alive: true, + }; + env.storage().instance().set(&Key::Boss, &boss); + Ok(boss) + } + + /// El estado global. Es la lectura que los pares replican. + pub fn state(env: Env) -> Result { + env.storage() + .instance() + .get(&Key::Boss) + .ok_or(Error::NoIniciado) + } + + /// Pegarle al jefe. + /// + /// El jugador no dice cuanto dano hizo: manda la semilla de su pelea y + /// cuantos ticks duro, y el contrato la rehace. Si el jugador miente sobre + /// el resultado no gana nada, porque el resultado no se lo pregunta nadie. + /// + /// `seed` identifica la pelea ademas de sembrarla, asi que se guarda para + /// que la misma pelea no se pueda cobrar dos veces. + pub fn hit(env: Env, player: Address, seed: u32, ticks: u32) -> Result { + player.require_auth(); + + if ticks == 0 || ticks > FIGHT_CAP { + return Err(Error::TicksFueraDeRango); + } + + let mut boss: BossState = env + .storage() + .instance() + .get(&Key::Boss) + .ok_or(Error::NoIniciado)?; + if !boss.alive { + return Err(Error::YaMurio); + } + + // Una pelea, una vez. Sin esto, la misma semilla afortunada se manda mil + // veces y el jefe cae en un minuto. + let mut usadas: Map = env + .storage() + .persistent() + .get(&Key::Used(boss.spawn_id)) + .unwrap_or(Map::new(&env)); + if usadas.contains_key(seed) { + return Err(Error::PeleaRepetida); + } + usadas.set(seed, true); + env.storage() + .persistent() + .set(&Key::Used(boss.spawn_id), &usadas); + + // Aca esta el nudo de todo: el dano se calcula, no se recibe. + let dealt = replay(seed, ticks) as i128; + + boss.hp -= dealt; + if boss.hp <= 0 { + boss.hp = 0; + boss.alive = false; + } + boss.phase = phase_for(boss.hp, boss.max_hp); + boss.revision += 1; + env.storage().instance().set(&Key::Boss, &boss); + + let mut dmg: Map = env + .storage() + .persistent() + .get(&Key::Damage(boss.spawn_id)) + .unwrap_or(Map::new(&env)); + let previo = dmg.get(player.clone()).unwrap_or(0); + dmg.set(player, previo + dealt); + env.storage() + .persistent() + .set(&Key::Damage(boss.spawn_id), &dmg); + + Ok(boss) + } + + /// Cuanto le puso un jugador a una aparicion. + pub fn contribution(env: Env, spawn_id: u32, player: Address) -> i128 { + let dmg: Map = env + .storage() + .persistent() + .get(&Key::Damage(spawn_id)) + .unwrap_or(Map::new(&env)); + dmg.get(player).unwrap_or(0) + } + + /// Marcar la recompensa como cobrada. + /// + /// Devuelve el dano aportado, que es con lo que el juego calcula el premio. + /// `world-boss.md` pide que el oro y la experiencia se entreguen "una sola + /// vez por spawnId", asi que lo que el contrato garantiza es exactamente + /// eso: que nadie cobre dos veces la misma aparicion. + pub fn claim(env: Env, player: Address, spawn_id: u32) -> Result { + player.require_auth(); + + let dmg: Map = env + .storage() + .persistent() + .get(&Key::Damage(spawn_id)) + .unwrap_or(Map::new(&env)); + let aporte = dmg.get(player.clone()).unwrap_or(0); + if aporte <= 0 { + return Err(Error::NadaQueCobrar); + } + + let mut cobrado: Map = env + .storage() + .persistent() + .get(&Key::Claimed(spawn_id)) + .unwrap_or(Map::new(&env)); + if cobrado.get(player.clone()).unwrap_or(false) { + return Err(Error::YaCobro); + } + cobrado.set(player, true); + env.storage() + .persistent() + .set(&Key::Claimed(spawn_id), &cobrado); + + Ok(aporte) + } +} + +mod test; diff --git a/contracts/jefe/src/test.rs b/contracts/jefe/src/test.rs new file mode 100644 index 0000000..0f1abfb --- /dev/null +++ b/contracts/jefe/src/test.rs @@ -0,0 +1,195 @@ +#![cfg(test)] + +extern crate std; + +use super::*; +use soroban_sdk::testutils::Address as _; +use soroban_sdk::Address; + +/// La vida que le puso `lib/world-boss.js` al Coloso Runico. +const HP: i128 = 360; + +fn armar(env: &Env) -> (JefeClient<'static>, Address) { + env.mock_all_auths(); + let admin = Address::generate(env); + let id = env.register(Jefe, ()); + let c = JefeClient::new(env, &id); + c.init(&admin); + (c, admin) +} + +#[test] +fn el_jefe_nace_entero_y_en_la_primera_fase() { + let env = Env::default(); + let (c, _) = armar(&env); + let b = c.spawn(&HP); + assert_eq!(b.spawn_id, 1); + assert_eq!(b.hp, HP); + assert_eq!(b.phase, 0); + assert_eq!(b.revision, 0); + assert!(b.alive); +} + +#[test] +fn cada_golpe_sube_la_revision() { + // `world-boss.md` pide que la revision suba con cada dano aceptado, porque + // es lo que deja a los pares descartar mensajes viejos sin curar al jefe. + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let jugador = Address::generate(&env); + + let a = c.hit(&jugador, &111u32, &200u32); + let b = c.hit(&jugador, &222u32, &200u32); + assert_eq!(a.revision, 1); + assert_eq!(b.revision, 2); + assert!(b.hp < a.hp, "el segundo golpe tiene que restar vida"); +} + +#[test] +fn el_jugador_no_dice_cuanto_dano_hizo() { + // No hay ningun parametro de dano en `hit`. Esa ausencia es el diseno: el + // contrato rehace la pelea y calcula. Si alguien agrega ese parametro + // alguna vez, este test deja de compilar, que es lo que se busca. + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let jugador = Address::generate(&env); + + let antes = c.state().hp; + let despues = c.hit(&jugador, &4242u32, &300u32).hp; + let dano = antes - despues; + + assert!(dano > 0, "una pelea de 300 ticks tiene que hacer algo de dano"); + assert_eq!( + dano, + c.contribution(&1u32, &jugador), + "lo aportado tiene que ser lo que el contrato calculo" + ); +} + +#[test] +fn la_misma_pelea_no_cuenta_dos_veces() { + // Sin esto, la semilla mas afortunada se manda mil veces y el jefe cae en un + // minuto sin que nadie haya jugado. + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let jugador = Address::generate(&env); + + c.hit(&jugador, &777u32, &200u32); + let repetida = c.try_hit(&jugador, &777u32, &200u32); + assert_eq!(repetida, Err(Ok(Error::PeleaRepetida))); +} + +#[test] +fn las_fases_siguen_los_umbrales_del_juego() { + // 66% y 30%, los mismos que `WORLD_BOSS.phases` en lib/world-boss.js. + assert_eq!(phase_for(360, 360), 0); + assert_eq!(phase_for(238, 360), 0, "justo arriba del 66%"); + assert_eq!(phase_for(237, 360), 1, "justo abajo del 66%"); + assert_eq!(phase_for(109, 360), 1, "justo arriba del 30%"); + assert_eq!(phase_for(108, 360), 2, "justo abajo del 30%"); + assert_eq!(phase_for(0, 360), 2); +} + +#[test] +fn el_jefe_muere_y_no_se_le_puede_pegar_mas() { + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&40i128); + let jugador = Address::generate(&env); + + let mut semilla = 1u32; + let mut vivo = true; + while vivo && semilla < 200 { + let b = c.hit(&jugador, &semilla, &400u32); + vivo = b.alive; + semilla += 1; + } + assert!(!vivo, "con 40 de vida tendria que caer"); + + let s = c.state(); + assert_eq!(s.hp, 0, "la vida no puede quedar negativa"); + assert_eq!(c.try_hit(&jugador, &9999u32, &100u32), Err(Ok(Error::YaMurio))); +} + +#[test] +fn no_se_puede_invocar_otro_mientras_el_anterior_vive() { + // Sin esta condicion, un jefe al que le falta poco puede ser reemplazado por + // uno entero y el trabajo de todos se pierde. + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + assert_eq!(c.try_spawn(&HP), Err(Ok(Error::SigueVivo))); +} + +#[test] +fn la_recompensa_se_cobra_una_sola_vez_por_aparicion() { + // Es textual de world-boss.md: "el oro y la experiencia se entregan una sola + // vez por spawnId". + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let jugador = Address::generate(&env); + c.hit(&jugador, &555u32, &300u32); + + let primero = c.claim(&jugador, &1u32); + assert!(primero > 0); + assert_eq!(c.try_claim(&jugador, &1u32), Err(Ok(Error::YaCobro))); +} + +#[test] +fn el_que_no_peleo_no_cobra() { + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let peleador = Address::generate(&env); + let colado = Address::generate(&env); + c.hit(&peleador, &31337u32, &300u32); + + assert_eq!(c.try_claim(&colado, &1u32), Err(Ok(Error::NadaQueCobrar))); +} + +#[test] +fn dos_jugadores_le_pegan_al_mismo_jefe() { + // Esto es lo que `net.js` no puede hacer y por lo que existe el contrato: + // estado compartido de verdad, no dos partidas que se miran. + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let a = Address::generate(&env); + let b = Address::generate(&env); + + let tras_a = c.hit(&a, &101u32, &250u32).hp; + let tras_b = c.hit(&b, &202u32, &250u32).hp; + + assert!(tras_b < tras_a, "el dano de los dos se acumula sobre la misma vida"); + assert!(c.contribution(&1u32, &a) > 0); + assert!(c.contribution(&1u32, &b) > 0); + assert_eq!( + HP - tras_b, + c.contribution(&1u32, &a) + c.contribution(&1u32, &b), + "la vida perdida tiene que ser la suma de lo que puso cada uno" + ); +} + +#[test] +fn los_ticks_tienen_techo() { + // El techo es lo que hace que el costo este acotado de antemano. Sin el, + // alguien pide diez millones de ticks y la invocacion se queda sin + // presupuesto en vez de rechazar el pedido. + let env = Env::default(); + let (c, _) = armar(&env); + c.spawn(&HP); + let jugador = Address::generate(&env); + + assert_eq!( + c.try_hit(&jugador, &1u32, &901u32), + Err(Ok(Error::TicksFueraDeRango)) + ); + assert_eq!( + c.try_hit(&jugador, &1u32, &0u32), + Err(Ok(Error::TicksFueraDeRango)) + ); +} diff --git a/contracts/medicion/Cargo.toml b/contracts/medicion/Cargo.toml new file mode 100644 index 0000000..2f0cc0d --- /dev/null +++ b/contracts/medicion/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "medicion" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["lib", "cdylib"] + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/medicion/src/lib.rs b/contracts/medicion/src/lib.rs new file mode 100644 index 0000000..981f9e5 --- /dev/null +++ b/contracts/medicion/src/lib.rs @@ -0,0 +1,188 @@ +#![no_std] + +//! Cuanto cuesta rehacer una pelea de runa adentro de un contrato. +//! +//! No es el motor de combate: es su forma. Un tick de runa hace tres cosas y +//! solo tres, porque el lenguaje de guiones no tiene bucles ni recursion: +//! +//! 1. lee el estado (hp, distancia, municion, cooldowns) +//! 2. recorre las reglas del guion de arriba a abajo, una vez +//! 3. aplica la que gano y avanza el mundo un paso +//! +//! Eso es aritmetica de enteros sobre una decena de variables, sin asignar +//! memoria y sin tocar almacenamiento. Lo que se mide aca es exactamente ese +//! costo, repetido la cantidad de ticks que dura una pelea de verdad. +//! +//! La pregunta que contesta: ¿entra una pelea entera en el presupuesto de una +//! sola invocacion? De eso depende que el contrato pueda declarar al ganador +//! por su cuenta, en vez de creerle a un jugador que dice que gano. + +use soroban_sdk::{contract, contractimpl, contracttype, Env}; + +/// El estado de una pelea. Doce numeros, que es todo lo que runa necesita. +#[contracttype] +#[derive(Clone, Copy)] +pub struct Fight { + pub hero_hp: i32, + pub foe_hp: i32, + pub dist: i32, + pub ammo: i32, + pub potions: i32, + pub cd_atk: i32, + pub cd_shot: i32, + pub tick: u32, + pub rng: u32, + pub dealt: i32, + pub taken: i32, + pub over: u32, +} + +/// El generador de runa, tal cual: xorshift de 32 bits. +/// +/// Tiene que dar exactamente los mismos numeros que `makeRng` en field.js, o el +/// contrato y el juego no estarian rehaciendo la misma pelea. +#[inline(always)] +fn next(rng: &mut u32) -> u32 { + let mut x = *rng; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + *rng = x; + x +} + +/// Un tick. +/// +/// Las diez ramas son un guion de tamano realista: la mayoria de los jugadores +/// escribe entre cinco y quince reglas. Se recorren todas cada tick porque asi +/// funciona el lenguaje, que se relee entero de arriba a abajo. +fn step(f: &mut Fight) { + if f.over != 0 { + return; + } + let r = next(&mut f.rng); + + if f.cd_atk > 0 { + f.cd_atk -= 1; + } + if f.cd_shot > 0 { + f.cd_shot -= 1; + } + + // El guion, de arriba a abajo. + if f.hero_hp < 7 && f.potions > 0 { + f.potions -= 1; + f.hero_hp += 12; + } else if f.dist > 5 && f.ammo > 0 && f.cd_shot == 0 { + f.ammo -= 1; + f.cd_shot = 4; + let dmg = 3 + (r % 4) as i32; + f.foe_hp -= dmg; + f.dealt += dmg; + } else if f.dist > 1 { + f.dist -= 1; + } else if f.cd_atk == 0 { + f.cd_atk = 3; + let dmg = 4 + (r % 5) as i32; + f.foe_hp -= dmg; + f.dealt += dmg; + } + + // El bicho contesta. + if f.foe_hp > 0 { + if f.dist > 1 { + f.dist -= 1; + } else if (r >> 8) % 3 == 0 { + let dmg = 2 + ((r >> 16) % 3) as i32; + f.hero_hp -= dmg; + f.taken += dmg; + } + } + + f.tick += 1; + if f.foe_hp <= 0 || f.hero_hp <= 0 { + f.over = 1; + } +} + +#[contract] +pub struct Medicion; + +#[contractimpl] +impl Medicion { + /// Rehace una pelea de `ticks` pasos y devuelve como termino. + /// + /// Nada de esto toca almacenamiento a proposito: lo que se quiere medir es + /// el costo de pensar, no el de guardar. + pub fn replay(_env: Env, seed: u32, ticks: u32) -> Fight { + let mut f = Fight { + hero_hp: 20, + foe_hp: 30, + dist: 8, + ammo: 10, + potions: 2, + cd_atk: 0, + cd_shot: 0, + tick: 0, + rng: if seed == 0 { 1 } else { seed }, + dealt: 0, + taken: 0, + over: 0, + }; + let mut i = 0u32; + while i < ticks { + step(&mut f); + i += 1; + } + f + } + + /// Igual que `replay`, pero con un bicho que no se muere. + /// + /// Existe para medir el peor caso. Una pelea real puede terminar en el tick + /// 17, y medir esa seria medir la comoda: la que decide si esto es viable + /// es la que llega al tope, porque es la que el contrato tiene que poder + /// pagar siempre. + pub fn bench(_env: Env, seed: u32, ticks: u32) -> Fight { + let mut f = Fight { + hero_hp: i32::MAX / 4, + foe_hp: i32::MAX / 4, + dist: 8, + ammo: i32::MAX / 4, + potions: i32::MAX / 4, + cd_atk: 0, + cd_shot: 0, + tick: 0, + rng: if seed == 0 { 1 } else { seed }, + dealt: 0, + taken: 0, + over: 0, + }; + let mut i = 0u32; + while i < ticks { + step(&mut f); + i += 1; + } + f + } + + /// Un duelo: las dos peleas en la MISMA invocacion. + /// + /// Medir dos llamadas sueltas no sirve, porque el arnes de pruebas reinicia + /// el presupuesto en cada invocacion de nivel superior. Lo que el contrato + /// de duelos va a hacer es esto: rehacer las dos peleas de una sola vez y + /// comparar. Entonces eso es lo que hay que medir. + pub fn bench_duel(env: Env, seed_a: u32, seed_b: u32, ticks: u32) -> i32 { + let a = Self::bench(env.clone(), seed_a, ticks); + let b = Self::bench(env, seed_b, ticks); + if a.dealt > b.dealt { + 1 + } else if b.dealt > a.dealt { + -1 + } else { + 0 + } + } +} + +mod test; diff --git a/contracts/medicion/src/test.rs b/contracts/medicion/src/test.rs new file mode 100644 index 0000000..90377aa --- /dev/null +++ b/contracts/medicion/src/test.rs @@ -0,0 +1,123 @@ +#![cfg(test)] + +extern crate std; + +use super::*; +use soroban_sdk::Env; + +/// Cuantos ticks dura una pelea de runa como mucho (`FIELD.fightCap`). +const FIGHT_CAP: u32 = 900; + +/// Los techos de una sola invocacion en Soroban. +const CPU_MAX: u64 = 100_000_000; +const MEM_MAX: u64 = 41_943_040; + +/// El contrato compilado. +/// +/// Esto importa mas de lo que parece. `env.register(Medicion, ())` corre el +/// contrato como Rust nativo, y entonces el medidor de presupuesto **no ve el +/// bucle**: da el mismo numero para un tick que para novecientos, porque lo +/// unico que cuenta es el costo de invocar. Para medir de verdad hay que +/// registrar el wasm, que es lo que la red va a ejecutar. +mod wasm { + soroban_sdk::contractimport!(file = "../target/wasm32v1-none/release/medicion.wasm"); +} + +fn medir(ticks: u32) -> (u64, u64, u32) { + let env = Env::default(); + let id = env.register(wasm::WASM, ()); + let c = wasm::Client::new(&env, &id); + env.cost_estimate().budget().reset_default(); + let f = c.bench(&12345u32, &ticks); + let b = env.cost_estimate().budget(); + (b.cpu_instruction_cost(), b.memory_bytes_cost(), f.tick) +} + +#[test] +fn el_peor_caso_entra_en_el_presupuesto() { + std::println!(""); + std::println!(" ticks cpu mem %cpu"); + let mut base = 0u64; + for t in [0u32, 1, 100, 300, 600, 900] { + let (cpu, mem, corridos) = medir(t); + assert_eq!(corridos, t, "la pelea corto antes: la medicion no vale"); + if t == 0 { + base = cpu; + } + std::println!(" {:>4} {:>10} {:>8} {:>4}%", t, cpu, mem, (cpu * 100) / CPU_MAX); + } + + let (cpu, mem, _) = medir(FIGHT_CAP); + let por_tick = (cpu.saturating_sub(base)) / FIGHT_CAP as u64; + std::println!(""); + std::println!(" invocar sin ticks : {}", base); + std::println!(" cada tick cuesta : {}", por_tick); + std::println!(" pelea de {} ticks: {} cpu ({}%), {} mem ({}%)", + FIGHT_CAP, cpu, (cpu * 100) / CPU_MAX, mem, (mem * 100) / MEM_MAX); + std::println!(" peleas por invocacion: {}", CPU_MAX / cpu.max(1)); + + assert!(cpu < CPU_MAX, "la pelea no entra en el presupuesto de cpu"); + assert!(mem < MEM_MAX, "la pelea no entra en el presupuesto de memoria"); +} + +#[test] +fn el_costo_crece_con_los_ticks() { + // Si esto falla, el medidor no esta viendo el bucle y cualquier conclusion + // que saquemos de los otros tests es mentira. + let (poco, _, _) = medir(10); + let (mucho, _, _) = medir(900); + std::println!(" 10 ticks: {} 900 ticks: {}", poco, mucho); + assert!(mucho > poco * 2, "el medidor no ve el bucle: la medicion no sirve"); +} + +#[test] +fn un_duelo_son_dos_peleas_y_tambien_entra() { + // Las dos peleas van en UNA invocacion, y eso importa: el arnes de pruebas + // reinicia el presupuesto en cada invocacion de nivel superior, asi que dos + // llamadas sueltas dan el costo de una sola y parece que un duelo sale + // gratis. Con bench_duel el numero es el de verdad. + let env = Env::default(); + let id = env.register(wasm::WASM, ()); + let c = wasm::Client::new(&env, &id); + env.cost_estimate().budget().reset_default(); + let ganador = c.bench_duel(&12345u32, &999u32, &FIGHT_CAP); + let cpu = env.cost_estimate().budget().cpu_instruction_cost(); + let mem = env.cost_estimate().budget().memory_bytes_cost(); + std::println!(" duelo, dos peleas de {} ticks en UNA invocacion:", FIGHT_CAP); + std::println!(" cpu {} ({}% del maximo)", cpu, (cpu * 100) / CPU_MAX); + std::println!(" memoria {} ({}% del maximo)", mem, (mem * 100) / MEM_MAX); + std::println!(" gano {}", ganador); + + // Y tiene que costar bastante mas que una pelea sola, o no las corrio a las + // dos y el numero no vale. + let (una, _, _) = medir(FIGHT_CAP); + assert!(cpu > una + (una / 2), "un duelo tiene que costar casi el doble que una pelea"); + assert!(cpu < CPU_MAX, "un duelo entero no entra"); + assert!(mem < MEM_MAX, "un duelo entero no entra en memoria"); +} + +#[test] +fn dos_peleas_con_la_misma_semilla_son_identicas() { + let env = Env::default(); + let id = env.register(wasm::WASM, ()); + let c = wasm::Client::new(&env, &id); + let a = c.replay(&777u32, &400u32); + let b = c.replay(&777u32, &400u32); + assert_eq!(a.dealt, b.dealt); + assert_eq!(a.taken, b.taken); + assert_eq!(a.tick, b.tick); + assert_eq!(a.hero_hp, b.hero_hp); +} + +#[test] +fn semillas_distintas_dan_peleas_distintas() { + let env = Env::default(); + let id = env.register(wasm::WASM, ()); + let c = wasm::Client::new(&env, &id); + let a = c.replay(&7u32, &400u32); + let b = c.replay(&99u32, &400u32); + assert!( + a.dealt != b.dealt || a.taken != b.taken || a.tick != b.tick, + "la semilla no esta cambiando nada" + ); +} From bd8e8402fdf19a992345f630761a170fad7e245f Mon Sep 17 00:00:00 2001 From: root Date: Tue, 25 Aug 2026 14:34:23 -0300 Subject: [PATCH 2/2] chore(contracts): dejar por escrito que duel-arena queda fuera del workspace `contracts/duel-arena` entro a main con el PR #45, despues de que esta rama se abriera. Sin tocar nada, cargo lo excluia del workspace en silencio: `cargo build` en `contracts/` compilaba jefe y medicion, y duel-arena no aparecia por ningun lado ni daba error. Quien arme el CI manana habria construido dos de tres contratos sin enterarse. Ahora la exclusion es explicita y dice por que existe. Las dos razones son reales: - duel-arena ancla `soroban-sdk = "=26.1.1"` con igual, mientras jefe y medicion usan 27.0.6, que es la version del protocolo vivo en mainnet y testnet. En un mismo workspace conviven pero se compilan dos copias del SDK. - duel-arena trae su propio `[profile.release]`, y un workspace solo honra el de la raiz, asi que meterlo adentro le cambiaria las opciones de compilacion por la espalda. Unificar las versiones es trabajo aparte y merece su propia issue. Lo que arregla este commit es que la exclusion sea una decision visible en vez de un olvido. cargo metadata sin error workspace ve medicion, jefe cargo test -p jefe 11/11 --- contracts/Cargo.toml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/contracts/Cargo.toml b/contracts/Cargo.toml index eb12ac5..193b1ed 100644 --- a/contracts/Cargo.toml +++ b/contracts/Cargo.toml @@ -2,6 +2,15 @@ resolver = "2" members = ["medicion", "jefe"] +# `duel-arena` queda afuera a proposito, y conviene que se vea en vez de que +# cargo lo ignore en silencio: ancla `soroban-sdk = "=26.1.1"` mientras estos dos +# usan 27.0.6, que es la version del protocolo vivo en mainnet y testnet. Meter +# las dos en el mismo workspace compilaria dos copias del SDK y le pisaria a +# duel-arena su propio [profile.release], porque un workspace solo honra el de la +# raiz. Unificar las versiones es trabajo aparte; mientras tanto, esta linea deja +# dicho que la exclusion es una decision y no un olvido. +exclude = ["duel-arena"] + [workspace.dependencies] soroban-sdk = "27.0.6"