diff --git a/Cargo.lock b/Cargo.lock index 1ca37f7..60a8715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -213,6 +222,38 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "camino" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276a59bf2b2c967788139340c9f0c5b12d7fd6630315c15c217e559de85d2609" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cc" version = "1.2.22" @@ -228,6 +269,17 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[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.101", +] + [[package]] name = "chrono" version = "0.4.41" @@ -305,6 +357,22 @@ dependencies = [ "syn 2.0.101", ] +[[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" @@ -433,6 +501,27 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dtor" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e58a0764cddb55ab28955347b45be00ade43d4d6f3ba4bf3dc354e4ec9432934" +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" @@ -539,7 +628,7 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" name = "fungible-token-interface-example" version = "0.0.1" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-fungible", "stellar-pausable", "stellar-pausable-macros", @@ -584,7 +673,9 @@ dependencies = [ name = "guess-the-number" version = "0.0.1" dependencies = [ - "soroban-sdk", + "soroban-sdk 23.0.3", + "stellar-registry", + "stellar-xdr 23.0.0", ] [[package]] @@ -608,6 +699,12 @@ version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" +[[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" @@ -754,6 +851,17 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[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.101", +] + [[package]] name = "memchr" version = "2.7.4" @@ -764,7 +872,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" name = "nft-enumerable-example" version = "0.0.1" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-default-impl-macro", "stellar-non-fungible", ] @@ -930,6 +1038,35 @@ dependencies = [ "getrandom", ] +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + [[package]] name = "rfc6979" version = "0.4.0" @@ -961,6 +1098,17 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[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 = "sec1" version = "0.7.3" @@ -979,21 +1127,34 @@ name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" +dependencies = [ + "serde", +] [[package]] name = "serde" -version = "1.0.219" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -1023,6 +1184,7 @@ dependencies = [ "hex", "indexmap 1.9.3", "indexmap 2.9.0", + "schemars", "serde", "serde_derive", "serde_json", @@ -1097,6 +1259,18 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "soroban-builtin-sdk-macros" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9336adeabcd6f636a4e0889c8baf494658ef5a3c4e7e227569acd2ce9091e85" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "soroban-env-common" version = "22.1.3" @@ -1109,10 +1283,29 @@ dependencies = [ "num-derive", "num-traits", "serde", - "soroban-env-macros", + "soroban-env-macros 22.1.3", "soroban-wasmi", "static_assertions", - "stellar-xdr", + "stellar-xdr 22.1.0", + "wasmparser", +] + +[[package]] +name = "soroban-env-common" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00067f52e8bbf1abf0de03fe3e2fbb06910893cfbe9a7d9093d6425658833ff3" +dependencies = [ + "arbitrary", + "crate-git-revision", + "ethnum", + "num-derive", + "num-traits", + "serde", + "soroban-env-macros 23.0.1", + "soroban-wasmi", + "static_assertions", + "stellar-xdr 23.0.0", "wasmparser", ] @@ -1122,7 +1315,17 @@ version = "22.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a07dda1ae5220d975979b19ad4fd56bc86ec7ec1b4b25bc1c5d403f934e592e" dependencies = [ - "soroban-env-common", + "soroban-env-common 22.1.3", + "static_assertions", +] + +[[package]] +name = "soroban-env-guest" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd1e40963517b10963a8e404348d3fe6caf9c278ac47a6effd48771297374d6" +dependencies = [ + "soroban-env-common 23.0.1", "static_assertions", ] @@ -1154,11 +1357,47 @@ dependencies = [ "sec1", "sha2", "sha3", - "soroban-builtin-sdk-macros", - "soroban-env-common", + "soroban-builtin-sdk-macros 22.1.3", + "soroban-env-common 22.1.3", "soroban-wasmi", "static_assertions", - "stellar-strkey", + "stellar-strkey 0.0.9", + "wasmparser", +] + +[[package]] +name = "soroban-env-host" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9766c5ad78e9d8ae10afbc076301f7d610c16407a1ebb230766dbe007a48725" +dependencies = [ + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "curve25519-dalek", + "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 23.0.1", + "soroban-env-common 23.0.1", + "soroban-wasmi", + "static_assertions", + "stellar-strkey 0.0.13", "wasmparser", ] @@ -1173,7 +1412,22 @@ dependencies = [ "quote", "serde", "serde_json", - "stellar-xdr", + "stellar-xdr 22.1.0", + "syn 2.0.101", +] + +[[package]] +name = "soroban-env-macros" +version = "23.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0e6a1c5844257ce96f5f54ef976035d5bd0ee6edefaf9f5e0bcb8ea4b34228c" +dependencies = [ + "itertools", + "proc-macro2", + "quote", + "serde", + "serde_json", + "stellar-xdr 23.0.0", "syn 2.0.101", ] @@ -1186,8 +1440,22 @@ dependencies = [ "serde", "serde_json", "serde_with", - "soroban-env-common", - "soroban-env-host", + "soroban-env-common 22.1.3", + "soroban-env-host 22.1.3", + "thiserror", +] + +[[package]] +name = "soroban-ledger-snapshot" +version = "23.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdefc9240bddd3ff4d47fd4d8f8dd44784840e25a18e426c6c987db8572d6df9" +dependencies = [ + "serde", + "serde_json", + "serde_with", + "soroban-env-common 23.0.1", + "soroban-env-host 23.0.1", "thiserror", ] @@ -1199,18 +1467,41 @@ checksum = "c7ac27d7573e62b745513fa1be8dab7a09b9676a7f39db97164f1d458a344749" dependencies = [ "arbitrary", "bytes-lit", - "ctor", + "ctor 0.2.9", "derive_arbitrary", "ed25519-dalek", "rand", "rustc_version", "serde", "serde_json", - "soroban-env-guest", - "soroban-env-host", - "soroban-ledger-snapshot", - "soroban-sdk-macros", - "stellar-strkey", + "soroban-env-guest 22.1.3", + "soroban-env-host 22.1.3", + "soroban-ledger-snapshot 22.0.8", + "soroban-sdk-macros 22.0.8", + "stellar-strkey 0.0.9", +] + +[[package]] +name = "soroban-sdk" +version = "23.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cb0dc3eb3661962cb8833513953b5839df14d589d96f8370b5b0c3870a8b3b5" +dependencies = [ + "arbitrary", + "bytes-lit", + "crate-git-revision", + "ctor 0.5.0", + "derive_arbitrary", + "ed25519-dalek", + "rand", + "rustc_version", + "serde", + "serde_json", + "soroban-env-guest 23.0.1", + "soroban-env-host 23.0.1", + "soroban-ledger-snapshot 23.0.3", + "soroban-sdk-macros 23.0.3", + "stellar-strkey 0.0.13", ] [[package]] @@ -1226,10 +1517,30 @@ dependencies = [ "quote", "rustc_version", "sha2", - "soroban-env-common", - "soroban-spec", - "soroban-spec-rust", - "stellar-xdr", + "soroban-env-common 22.1.3", + "soroban-spec 22.0.8", + "soroban-spec-rust 22.0.8", + "stellar-xdr 22.1.0", + "syn 2.0.101", +] + +[[package]] +name = "soroban-sdk-macros" +version = "23.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eab5f4e5f3836a4b4aeecb2837160e944621b2f8dbad775638a2ab8e10fd5bb" +dependencies = [ + "darling", + "heck", + "itertools", + "macro-string", + "proc-macro2", + "quote", + "sha2", + "soroban-env-common 23.0.1", + "soroban-spec 23.0.3", + "soroban-spec-rust 23.0.3", + "stellar-xdr 23.0.0", "syn 2.0.101", ] @@ -1240,7 +1551,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ad0867aec99770ed614fedbec7ac4591791df162ff9e548ab7ebd07cd23a9c" dependencies = [ "base64 0.13.1", - "stellar-xdr", + "stellar-xdr 22.1.0", + "thiserror", + "wasmparser", +] + +[[package]] +name = "soroban-spec" +version = "23.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd257b0365307e0b8d38040ee0364abcc610fc6e61960ff5e26803922d098921" +dependencies = [ + "base64 0.22.1", + "stellar-xdr 23.0.0", "thiserror", "wasmparser", ] @@ -1255,8 +1578,24 @@ dependencies = [ "proc-macro2", "quote", "sha2", - "soroban-spec", - "stellar-xdr", + "soroban-spec 22.0.8", + "stellar-xdr 22.1.0", + "syn 2.0.101", + "thiserror", +] + +[[package]] +name = "soroban-spec-rust" +version = "23.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec3c72de91fdcf637045f3351df029a98b9de9ad22ced4063f74d0b5873f526" +dependencies = [ + "prettyplease", + "proc-macro2", + "quote", + "sha2", + "soroban-spec 23.0.3", + "stellar-xdr 23.0.0", "syn 2.0.101", "thiserror", ] @@ -1296,12 +1635,24 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "stellar-build" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1b7fde59777ba338f1ab386597e722b88d784ecaa244f75b96a2333643b240c" +dependencies = [ + "cargo_metadata", + "sha2", + "thiserror", + "topological-sort", +] + [[package]] name = "stellar-constants" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", ] [[package]] @@ -1319,7 +1670,7 @@ name = "stellar-fungible" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-constants", ] @@ -1338,7 +1689,7 @@ name = "stellar-non-fungible" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", "stellar-constants", ] @@ -1347,7 +1698,7 @@ name = "stellar-pausable" version = "0.3.0" source = "git+https://github.com/OpenZeppelin/stellar-contracts?tag=v0.3.0#659fcabdc0e4712254dfebe4f45efd54f49826e9" dependencies = [ - "soroban-sdk", + "soroban-sdk 22.0.8", ] [[package]] @@ -1361,6 +1712,31 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "stellar-registry" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcacbce7779ca20863e3f980bc7d3b09865c8ece4bcb7b1b75ed22ca357c4b0" +dependencies = [ + "stellar-scaffold-macro", +] + +[[package]] +name = "stellar-scaffold-macro" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7645bc7241862c2384c9346ffd203150ff1329bdf8b64ed6865064bd5eb70a37" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "sha2", + "stellar-build", + "stellar-strkey 0.0.13", + "stellar-xdr 23.0.0", + "syn 2.0.101", +] + [[package]] name = "stellar-strkey" version = "0.0.9" @@ -1372,6 +1748,16 @@ dependencies = [ "thiserror", ] +[[package]] +name = "stellar-strkey" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee1832fb50c651ad10f734aaf5d31ca5acdfb197a6ecda64d93fcdb8885af913" +dependencies = [ + "crate-git-revision", + "data-encoding", +] + [[package]] name = "stellar-xdr" version = "22.1.0" @@ -1385,7 +1771,26 @@ dependencies = [ "hex", "serde", "serde_with", - "stellar-strkey", + "stellar-strkey 0.0.9", +] + +[[package]] +name = "stellar-xdr" +version = "23.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d2848e1694b0c8db81fd812bfab5ea71ee28073e09ccc45620ef3cf7a75a9b" +dependencies = [ + "arbitrary", + "base64 0.22.1", + "cfg_eval", + "crate-git-revision", + "escape-bytes", + "ethnum", + "hex", + "serde", + "serde_with", + "sha2", + "stellar-strkey 0.0.13", ] [[package]] @@ -1473,6 +1878,12 @@ dependencies = [ "time-core", ] +[[package]] +name = "topological-sort" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea68304e134ecd095ac6c3574494fc62b909f416c4fca77e440530221e549d3d" + [[package]] name = "typenum" version = "1.18.0" diff --git a/contracts/guess-the-number/Cargo.toml b/contracts/guess-the-number/Cargo.toml index cbc09b4..cecf81b 100644 --- a/contracts/guess-the-number/Cargo.toml +++ b/contracts/guess-the-number/Cargo.toml @@ -16,7 +16,9 @@ crate-type = ["cdylib"] doctest = false [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = "23.0.3" +stellar-registry = "0.0.4" [dev-dependencies] -soroban-sdk = { workspace = true, features = ["testutils"] } +stellar-xdr = { version = "23.0.0", features = ["curr", "serde"] } +soroban-sdk = { version = "23.0.3", features = ["testutils"] } diff --git a/contracts/guess-the-number/src/error.rs b/contracts/guess-the-number/src/error.rs new file mode 100644 index 0000000..88544b5 --- /dev/null +++ b/contracts/guess-the-number/src/error.rs @@ -0,0 +1,12 @@ +#[soroban_sdk::contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[repr(u32)] +pub enum Error { + /// The contract failed to transfer XLM to the guesser + FailedToTransferToGuesser = 1, + /// The guesser failed to transfer XLM to the contract + FailedToTransferFromGuesser = 2, + /// The contract has no balance to transfer to the guesser + NoBalanceToTransfer = 3, + +} diff --git a/contracts/guess-the-number/src/lib.rs b/contracts/guess-the-number/src/lib.rs index 07b9b15..8e1229b 100644 --- a/contracts/guess-the-number/src/lib.rs +++ b/contracts/guess-the-number/src/lib.rs @@ -1,28 +1,81 @@ #![no_std] use soroban_sdk::{contract, contractimpl, symbol_short, Address, BytesN, Env, Symbol}; +mod error; +mod xlm; + +use error::Error; + #[contract] pub struct GuessTheNumber; -const THE_NUMBER: Symbol = symbol_short!("n"); +const THE_NUMBER: &Symbol = &symbol_short!("n"); pub const ADMIN_KEY: &Symbol = &symbol_short!("ADMIN"); #[contractimpl] impl GuessTheNumber { + /// Constructor to initialize the contract with an admin and a random number pub fn __constructor(env: &Env, admin: Address) { - Self::set_admin(env, &admin); + // Require auth from the admin to make the transfer + admin.require_auth(); + // This is for testing purposes. Ensures that the XLM contract set up for unit testing and local network + xlm::register(env, &admin); + // Send the contract an amount of XLM to play with + xlm::token_client(env).transfer( + &admin, + env.current_contract_address(), + &xlm::to_stroops(1), + ); + // Set the admin in storage + Self::set_admin(env, admin); + // Set a random number between 1 and 10 + Self::reset_number(env); } /// Update the number. Only callable by admin. pub fn reset(env: &Env) { Self::require_admin(env); + Self::reset_number(env); + } + + // Private function to reset the number to a new random value + // which doesn't require auth from the admin + fn reset_number(env: &Env) { let new_number: u64 = env.prng().gen_range(1..=10); - env.storage().instance().set(&THE_NUMBER, &new_number); + env.storage().instance().set(THE_NUMBER, &new_number); } /// Guess a number between 1 and 10 - pub fn guess(env: &Env, a_number: u64) -> bool { - a_number == env.storage().instance().get::<_, u64>(&THE_NUMBER).unwrap() + pub fn guess(env: &Env, a_number: u64, guesser: Address) -> Result { + let xlm_client = xlm::token_client(env); + let contract_address = env.current_contract_address(); + let guessed_it = a_number == Self::number(env); + if guessed_it { + let balance = xlm_client.balance(&contract_address); + if balance == 0 { + return Err(Error::NoBalanceToTransfer); + } + // Methods `try_*` will return an error if the method fails + // `.map_err` lets us convert the error to our custom Error type + let _ = xlm_client + .try_transfer(&contract_address, &guesser, &balance) + .map_err(|_| Error::FailedToTransferToGuesser)?; + } else { + guesser.require_auth(); + let _ = xlm_client + .try_transfer(&guesser, &contract_address, &xlm::to_stroops(1)) + .map_err(|_| Error::FailedToTransferFromGuesser)?; + } + Ok(guessed_it) + } + + /// Admin can add more funds to the contract + pub fn add_funds(env: &Env, amount: i128) { + Self::require_admin(env); + let contract_address = env.current_contract_address(); + // unwrap here is safe because the admin was set in the constructor + let admin = Self::admin(env).unwrap(); + xlm::token_client(env).transfer(&admin, &contract_address, &amount); } /// Upgrade the contract to new wasm. Only callable by admin. @@ -31,18 +84,29 @@ impl GuessTheNumber { env.deployer().update_current_contract_wasm(new_wasm_hash); } - fn admin(env: &Env) -> Option
{ + /// readonly function to get the current number + /// `pub(crate)` makes it accessible in the same crate, but not outside of it + pub(crate) fn number(env: &Env) -> u64 { + // We can unwrap because the number is set in the constructor + // and then only reset by the admin + unsafe { env.storage().instance().get(THE_NUMBER).unwrap_unchecked() } + } + + /// readonly function to get the current admin + pub fn admin(env: &Env) -> Option
{ env.storage().instance().get(ADMIN_KEY) } - fn set_admin(env: &Env, admin: &Address) { + /// Set a new admin address. This is only callable by the admin. + pub fn set_admin(env: &Env, admin: Address) { // Check if admin is already set if env.storage().instance().has(ADMIN_KEY) { panic!("admin already set"); } - env.storage().instance().set(ADMIN_KEY, admin); + env.storage().instance().set(ADMIN_KEY, &admin); } + // Private helper function to require auth from the admin fn require_admin(env: &Env) { let admin = Self::admin(env).expect("admin not set"); admin.require_auth(); diff --git a/contracts/guess-the-number/src/test.rs b/contracts/guess-the-number/src/test.rs index 8ceb909..c4f7500 100644 --- a/contracts/guess-the-number/src/test.rs +++ b/contracts/guess-the-number/src/test.rs @@ -1,21 +1,43 @@ #![cfg(test)] +// This lets use reference types in the std library for testing extern crate std; use super::*; use soroban_sdk::{ testutils::{Address as _, MockAuth, MockAuthInvoke}, + token::StellarAssetClient, Address, Env, IntoVal, Val, Vec, }; +fn init_test<'a>(env: &'a Env) -> (Address, StellarAssetClient<'a>, GuessTheNumberClient<'a>) { + let admin = Address::generate(env); + let client = generate_client(env, &admin); + // This is needed because we want to call a function from within the context of the contract + // In this case we want to get the address of the XLM contract registered by the constructor + let sac_address = env.as_contract(&client.address, || xlm::contract_id(env)); + (admin, StellarAssetClient::new(env, &sac_address), client) +} + #[test] -fn only_admin_can_reset() { +fn constructed_correctly() { let env = &Env::default(); + let (admin, sac, client) = init_test(env); + // Check that the admin is set correctly + assert_eq!(client.admin(), Some(admin.clone())); + // Check that the contract has a balance of 1 XLM + assert_eq!(sac.balance(&client.address), xlm::to_stroops(1)); + // Need to use `as_contract` to call a function in the context of the contract + // Since the method `number` is not in the client, but is visibile in the crate + let number = env.as_contract(&client.address, || GuessTheNumber::number(env)); + assert_eq!(number, 4); +} - let admin = Address::generate(env); +#[test] +fn only_admin_can_reset() { + let env = &Env::default(); + let (admin, _, client) = init_test(env); let user = Address::generate(env); - let client = generate_client(env, &admin); - set_caller(&client, "reset", &user, ()); assert!(client.try_reset().is_err()); @@ -24,41 +46,113 @@ fn only_admin_can_reset() { } #[test] -fn one_guess_in_ten_returns_true() { +fn guess() { let env = &Env::default(); + let (_, sac, client) = init_test(env); + // This lets you mock all auth when they become complicated when making cross contract calls. + env.mock_all_auths(); - let admin = Address::generate(env); + // Create a user to guess + let alice = Address::generate(env); + // Mint tokens to the user. On testnet you use friendbot to fund the account. + sac.mint(&alice, &xlm::to_stroops(2)); + // Check that alice has the tokens + assert_eq!(sac.balance(&alice), xlm::to_stroops(2)); - let client = generate_client(env, &admin); + // Create another user with no funds + let bob = Address::generate(env); - // set the number - set_caller(&client, "reset", &admin, ()); + // In the testing enviroment the random seed is always the same initially. + // This tests a wrong guess so the balance should go down one XLM + assert!(!client.guess(&3, &alice)); + assert_eq!(sac.balance(&alice), xlm::to_stroops(1)); + + // Now we test a wrong guess but the user has no funds so we get an error + assert_eq!( + client.try_guess(&3, &bob).unwrap_err(), + Ok(Error::FailedToTransferFromGuesser) + ); + + // Now we test a correct guess, the balance should go up by the initial 1 XLM + the 1 XLM from the contract + assert!(client.guess(&4, &alice)); + assert_eq!(sac.balance(&alice), xlm::to_stroops(3)); + + assert_eq!( + client.try_guess(&4, &alice).unwrap_err(), + Ok(Error::NoBalanceToTransfer) + ); +} + +#[test] +fn add_funds() { + let env = &Env::default(); + let (_, sac, client) = init_test(env); + // This lets you mock all auth when they become complicated when making cross contract calls. + env.mock_all_auths(); + + // Create a user to guess + let alice = Address::generate(env); + // Mint tokens to the user. On testnet you use friendbot to fund the account. + sac.mint(&alice, &xlm::to_stroops(2)); + // Now we test a correct guess, the balance should go up by the initial 1 XLM + the 1 XLM from the contract + assert!(client.guess(&4, &alice)); + assert_eq!(sac.balance(&alice), xlm::to_stroops(3)); + assert_eq!(sac.balance(&client.address), 0); + + client.add_funds(&xlm::to_stroops(5)); + assert_eq!(sac.balance(&client.address), xlm::to_stroops(5)); + + // Since we didn't reset the number, the guess should still be correct + assert!(client.guess(&4, &alice)); + assert_eq!(sac.balance(&alice), xlm::to_stroops(8)); + assert_eq!(sac.balance(&client.address), 0); +} + +#[test] +fn reset_and_guess() { + let env = &Env::default(); + let (_, sac, client) = init_test(env); + // This lets you mock all auth when they become complicated when making cross contract calls. + env.mock_all_auths(); + + // Create a user to guess + let alice = Address::generate(env); + // Mint tokens to the user. On testnet you use friendbot to fund the account. + sac.mint(&alice, &xlm::to_stroops(2)); + + // Reset the number client.reset(); - let trues: std::vec::Vec<_> = (1u64..=10).filter(|number| client.guess(number)).collect(); - assert_eq!(trues.len(), 1); + // Guess again, this should be correct now + assert!(client.guess(&10, &alice)); } fn generate_client<'a>(env: &Env, admin: &Address) -> GuessTheNumberClient<'a> { - let contract_id = env.register(GuessTheNumber, (admin,)); + let contract_id = Address::generate(env); + env.mock_all_auths(); + let contract_id = env.register_at(&contract_id, GuessTheNumber, (admin,)); + env.set_auths(&[]); // clear auths GuessTheNumberClient::new(env, &contract_id) } -fn set_caller(client: &GuessTheNumberClient, method: &str, caller: &Address, args: T) +// This lets you mock the auth context for a function call +fn set_caller(client: &GuessTheNumberClient, fn_name: &str, caller: &Address, args: T) where T: IntoVal>, { // clear previous auth mocks client.env.set_auths(&[]); + let invoke = &MockAuthInvoke { + contract: &client.address, + fn_name, + args: args.into_val(&client.env), + sub_invokes: &[], + }; + // mock auth as passed-in address client.env.mock_auths(&[MockAuth { - address: caller, - invoke: &MockAuthInvoke { - contract: &client.address, - fn_name: method, - args: args.into_val(&client.env), - sub_invokes: &[], - }, + address: &caller, + invoke, }]); } diff --git a/contracts/guess-the-number/src/xlm.rs b/contracts/guess-the-number/src/xlm.rs new file mode 100644 index 0000000..006693d --- /dev/null +++ b/contracts/guess-the-number/src/xlm.rs @@ -0,0 +1,60 @@ +#[cfg(test)] +mod xlm { + use super::*; + const XLM_KEY: &soroban_sdk::Symbol = &soroban_sdk::symbol_short!("XLM"); + + pub fn contract_id(env: &soroban_sdk::Env) -> soroban_sdk::Address { + env.storage() + .instance() + .get::<_, soroban_sdk::Address>(XLM_KEY) + .expect("XLM contract not initialized. Please deploy the XLM contract first.") + } + + pub fn register( + env: &soroban_sdk::Env, + admin: &soroban_sdk::Address, + ) -> soroban_sdk::testutils::StellarAssetContract { + let sac = env.register_stellar_asset_contract_v2(admin.clone()); + env.storage().instance().set(XLM_KEY, &sac.address()); + stellar_asset_client(env).mint(admin, &to_stroops(10_000)); + sac + } + + #[allow(unused)] + pub fn stellar_asset_client<'a>( + env: &soroban_sdk::Env, + ) -> soroban_sdk::token::StellarAssetClient<'a> { + soroban_sdk::token::StellarAssetClient::new(&env, &contract_id(env)) + } + /// Create a Stellar Asset Client for the asset which provides an admin interface + pub fn token_client<'a>(env: &soroban_sdk::Env) -> soroban_sdk::token::TokenClient<'a> { + soroban_sdk::token::TokenClient::new(&env, &contract_id(env)) + } +} +const ONE_XLM: i128 = 1_000_000_0; // 1 XLM in stroops; + +pub const fn to_stroops(num: u64) -> i128 { + (num as i128) * ONE_XLM +} + +#[cfg(not(test))] +stellar_registry::import_asset!("xlm"); + +#[allow(unused)] +pub const SERIALIZED_ASSET: [u8; 4] = [0, 0, 0, 0]; + +pub use xlm::*; +mod register { + + #[allow(unused)] + #[cfg(not(test))] + pub fn register(env: &soroban_sdk::Env, admin: &soroban_sdk::Address) { + let balance = super::token_client(env).try_balance(&env.current_contract_address()); + if balance.is_err() { + env.deployer().with_stellar_asset(super::SERIALIZED_ASSET).deploy(); + } + } +} + +#[allow(unused_imports)] +pub use register::*; \ No newline at end of file diff --git a/src/components/GuessTheNumber.tsx b/src/components/GuessTheNumber.tsx index e09a3f1..d319ea0 100644 --- a/src/components/GuessTheNumber.tsx +++ b/src/components/GuessTheNumber.tsx @@ -18,9 +18,16 @@ export const GuessTheNumber = () => { } const submitGuess = async () => { - if (!theGuess) return; - const { result } = await game.guess({ a_number: BigInt(theGuess) }); - setGuessedIt(result); + if (!theGuess || !address) return; + const { result } = await game.guess({ + a_number: BigInt(theGuess), + guesser: address, + }); + if (result.isErr()) { + console.error(result.unwrapErr()); + } else { + setGuessedIt(result.unwrap()); + } }; return (