diff --git a/Cargo.lock b/Cargo.lock index c997e99..9d91c3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -119,10 +119,13 @@ dependencies = [ ] [[package]] -name = "arcstr" -version = "1.2.0" +name = "arc-swap" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03918c3dbd7701a85c6b9887732e2921175f26c350b4563841d0958c21d57e6d" +checksum = "a07d1f37ff60921c83bdfc7407723bdefe89b44b98a9b772f225c8f9d67141a6" +dependencies = [ + "rustversion", +] [[package]] name = "ark-bls12-381" @@ -159,7 +162,7 @@ dependencies = [ "ark-std", "derivative", "hashbrown 0.13.2", - "itertools", + "itertools 0.10.5", "num-traits", "zeroize", ] @@ -175,8 +178,8 @@ dependencies = [ "ark-serialize", "ark-std", "derivative", - "digest 0.10.7", - "itertools", + "digest", + "itertools 0.10.5", "num-bigint", "num-traits", "paste", @@ -228,7 +231,7 @@ checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" dependencies = [ "ark-serialize-derive", "ark-std", - "digest 0.10.7", + "digest", "num-bigint", ] @@ -263,17 +266,6 @@ dependencies = [ "serde_json", ] -[[package]] -name = "async-lock" -version = "3.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" -dependencies = [ - "event-listener", - "event-listener-strategy", - "pin-project-lite", -] - [[package]] name = "async-trait" version = "0.1.89" @@ -301,32 +293,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] -name = "autocfg" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" - -[[package]] -name = "aws-lc-rs" -version = "1.16.2" +name = "auto-future" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a054912289d18629dc78375ba2c3726a3afe3ff71b4edba9dedfca0e3446d1fc" -dependencies = [ - "aws-lc-sys", - "zeroize", -] +checksum = "3c1e7e457ea78e524f48639f551fd79703ac3f2237f5ecccdf4708f8a75ad373" [[package]] -name = "aws-lc-sys" -version = "0.39.1" +name = "autocfg" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a25cf98105baa966497416dbd42565ce3a8cf8dbfd59803ec9ad46f3126399" -dependencies = [ - "cc", - "cmake", - "dunce", - "fs_extra", -] +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "axum" @@ -380,6 +356,36 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-test" +version = "17.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eb1dfb84bd48bad8e4aa1acb82ed24c2bb5e855b659959b4e03b4dca118fcac" +dependencies = [ + "anyhow", + "assert-json-diff", + "auto-future", + "axum", + "bytes", + "bytesize", + "cookie", + "http", + "http-body-util", + "hyper", + "hyper-util", + "mime", + "pretty_assertions", + "reserve-port", + "rust-multipart-rfc7578_2", + "serde", + "serde_json", + "serde_urlencoded", + "smallvec", + "tokio", + "tower", + "url", +] + [[package]] name = "backtrace" version = "0.3.76" @@ -446,15 +452,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "block-buffer" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" -dependencies = [ - "hybrid-array", -] - [[package]] name = "block2" version = "0.6.2" @@ -494,6 +491,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "bytesize" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bd91ee7b2422bcb158d90ef4d14f75ef67f340943fc4149891dcce8f8b972a3" + [[package]] name = "cc" version = "1.2.56" @@ -501,17 +504,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", - "jobserver", - "libc", "shlex", ] -[[package]] -name = "cesu8" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" - [[package]] name = "cfg-if" version = "1.0.4" @@ -589,21 +584,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" -[[package]] -name = "cmake" -version = "0.1.58" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" -dependencies = [ - "cc", -] - -[[package]] -name = "cmov" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0758edba32d61d1fd9f4d69491b47604b91ee2f7e6b33de7e54ca4ebe55dc3" - [[package]] name = "colorchoice" version = "1.0.4" @@ -649,19 +629,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] -name = "const-oid" -version = "0.10.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" - -[[package]] -name = "core-foundation" -version = "0.10.1" +name = "cookie" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ - "core-foundation-sys", - "libc", + "time", + "version_check", ] [[package]] @@ -679,15 +653,6 @@ 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" @@ -751,15 +716,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "crypto-common" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77727bb15fa921304124b128af125e7e3b968275d1b108b379190264f4423710" -dependencies = [ - "hybrid-array", -] - [[package]] name = "ctor" version = "0.5.0" @@ -776,15 +732,6 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2931af7e13dc045d8e9d26afccc6fa115d64e115c9c84b1166288b46f6782c2" -[[package]] -name = "ctutils" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1005a6d4446f5120ef475ad3d2af2b30c49c2c9c6904258e3bb30219bebed5e4" -dependencies = [ - "cmov", -] - [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -792,9 +739,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", + "cpufeatures", "curve25519-dalek-derive", - "digest 0.10.7", + "digest", "fiat-crypto", "rustc_version", "subtle", @@ -918,7 +865,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid 0.9.6", + "const-oid", "pem-rfc7468", "zeroize", ] @@ -956,27 +903,21 @@ dependencies = [ ] [[package]] -name = "digest" -version = "0.10.7" +name = "diff" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer 0.10.4", - "const-oid 0.9.6", - "crypto-common 0.1.6", - "subtle", -] +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" -version = "0.11.2" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4850db49bf08e663084f7fb5c87d202ef91a3907271aff24a94eb97ff039153c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.12.0", - "const-oid 0.10.2", - "crypto-common 0.2.1", - "ctutils", + "block-buffer", + "const-oid", + "crypto-common", + "subtle", ] [[package]] @@ -1027,12 +968,6 @@ version = "0.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" -[[package]] -name = "dunce" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" - [[package]] name = "dyn-clone" version = "1.0.20" @@ -1046,7 +981,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", - "digest 0.10.7", + "digest", "elliptic-curve", "rfc6979", "signature", @@ -1073,7 +1008,7 @@ dependencies = [ "ed25519", "rand_core 0.6.4", "serde", - "sha2 0.10.9", + "sha2", "subtle", "zeroize", ] @@ -1095,7 +1030,7 @@ checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", - "digest 0.10.7", + "digest", "ff", "generic-array", "group", @@ -1155,16 +1090,6 @@ dependencies = [ "pin-project-lite", ] -[[package]] -name = "event-listener-strategy" -version = "0.5.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" -dependencies = [ - "event-listener", - "pin-project-lite", -] - [[package]] name = "fastrand" version = "2.3.0" @@ -1231,12 +1156,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - [[package]] name = "futures-channel" version = "0.3.32" @@ -1522,7 +1441,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac 0.12.1", + "hmac", ] [[package]] @@ -1531,16 +1450,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.7", -] - -[[package]] -name = "hmac" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" -dependencies = [ - "digest 0.11.2", + "digest", ] [[package]] @@ -1608,15 +1518,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "hybrid-array" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8655f91cd07f2b9d0c24137bd650fe69617773435ee5ec83022377777ce65ef1" -dependencies = [ - "typenum", -] - [[package]] name = "hyper" version = "1.8.1" @@ -1674,7 +1575,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.2", "tokio", "tower-service", "tracing", @@ -1825,6 +1726,7 @@ dependencies = [ "anyhow", "async-trait", "axum", + "axum-test", "base64", "chrono", "dashmap", @@ -1832,17 +1734,14 @@ dependencies = [ "ed25519-dalek", "governor", "hex", - "hmac 0.13.0", + "hmac", "lazy_static", "prometheus", - "rand 0.9.2", "redis", - "reqwest 0.12.28", - "sentry 0.34.0", - "sentry-tracing 0.34.0", + "reqwest", "serde", "serde_json", - "sha2 0.11.0", + "sha2", "sqlx", "stellar-strkey 0.0.16", "thiserror 2.0.18", @@ -1914,64 +1813,19 @@ dependencies = [ ] [[package]] -name = "itoa" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" - -[[package]] -name = "jni" -version = "0.21.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" -dependencies = [ - "cesu8", - "cfg-if", - "combine", - "jni-sys 0.3.1", - "log", - "thiserror 1.0.69", - "walkdir", - "windows-sys 0.45.0", -] - -[[package]] -name = "jni-sys" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" -dependencies = [ - "jni-sys 0.4.1", -] - -[[package]] -name = "jni-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" -dependencies = [ - "jni-sys-macros", -] - -[[package]] -name = "jni-sys-macros" -version = "0.4.1" +name = "itertools" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ - "quote", - "syn 2.0.117", + "either", ] [[package]] -name = "jobserver" -version = "0.1.34" +name = "itoa" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" -dependencies = [ - "getrandom 0.3.4", - "libc", -] +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" @@ -1992,7 +1846,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -2001,7 +1855,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ - "cpufeatures 0.2.17", + "cpufeatures", ] [[package]] @@ -2125,7 +1979,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", - "digest 0.10.7", + "digest", ] [[package]] @@ -2465,12 +2319,6 @@ version = "1.70.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" -[[package]] -name = "openssl-probe" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" - [[package]] name = "os_info" version = "3.14.0" @@ -2496,7 +2344,7 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.9", + "sha2", ] [[package]] @@ -2560,12 +2408,12 @@ dependencies = [ "hex", "mockito", "prometheus", - "reqwest 0.12.28", - "sentry 0.47.0", - "sentry-tracing 0.47.0", + "reqwest", + "sentry", + "sentry-tracing", "serde", "serde_json", - "sha2 0.11.0", + "sha2", "soroban-sdk", "stellar-strkey 0.0.16", "thiserror 2.0.18", @@ -2653,6 +2501,16 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "prettyplease" version = "0.2.37" @@ -2683,21 +2541,22 @@ dependencies = [ [[package]] name = "procfs" -version = "0.17.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f" +checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ "bitflags", "hex", + "lazy_static", "procfs-core", "rustix 0.38.44", ] [[package]] name = "procfs-core" -version = "0.17.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec" +checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ "bitflags", "hex", @@ -2705,9 +2564,9 @@ dependencies = [ [[package]] name = "prometheus" -version = "0.14.0" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ca5326d8d0b950a9acd87e6a3f94745394f62e4dae1b1ee22b2bc0c394af43a" +checksum = "3d33c28a30771f7f96db69893f78b857f7450d7e0237e9c8fc6427a81bae7ed1" dependencies = [ "cfg-if", "fnv", @@ -2717,14 +2576,14 @@ dependencies = [ "parking_lot", "procfs", "protobuf", - "thiserror 2.0.18", + "thiserror 1.0.69", ] [[package]] name = "proptest" -version = "1.11.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532" dependencies = [ "bit-set", "bit-vec", @@ -2741,23 +2600,9 @@ dependencies = [ [[package]] name = "protobuf" -version = "3.7.2" +version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" -dependencies = [ - "once_cell", - "protobuf-support", - "thiserror 1.0.69", -] - -[[package]] -name = "protobuf-support" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" -dependencies = [ - "thiserror 1.0.69", -] +checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "quanta" @@ -2793,7 +2638,7 @@ dependencies = [ "quinn-udp", "rustc-hash", "rustls 0.23.36", - "socket2", + "socket2 0.6.2", "thiserror 2.0.18", "tokio", "tracing", @@ -2806,7 +2651,6 @@ version = "0.11.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" dependencies = [ - "aws-lc-rs", "bytes", "getrandom 0.3.4", "lru-slab", @@ -2831,7 +2675,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] @@ -2930,27 +2774,26 @@ dependencies = [ [[package]] name = "redis" -version = "1.1.0" +version = "0.27.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76e41a79ae5cbb41257d84cf4cf0db0bb5a95b11bf05c62c351de4fe748620d" +checksum = "09d8f99a4090c89cc489a94833c901ead69bfbf3877b4867d5482e321ee875bc" dependencies = [ - "arcstr", - "async-lock", + "arc-swap", + "async-trait", "bytes", - "cfg-if", "combine", "futures-util", + "itertools 0.13.0", "itoa", "num-bigint", "percent-encoding", "pin-project-lite", "ryu", "sha1_smol", - "socket2", + "socket2 0.5.10", "tokio", "tokio-util", "url", - "xxhash-rust", ] [[package]] @@ -3061,43 +2904,12 @@ dependencies = [ ] [[package]] -name = "reqwest" -version = "0.13.2" +name = "reserve-port" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" +checksum = "94070964579245eb2f76e62a7668fe87bd9969ed6c41256f3bf614e3323dd3cc" dependencies = [ - "base64", - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http", - "http-body", - "http-body-util", - "hyper", - "hyper-rustls", - "hyper-util", - "js-sys", - "log", - "percent-encoding", - "pin-project-lite", - "quinn", - "rustls 0.23.36", - "rustls-pki-types", - "rustls-platform-verifier", - "serde", - "serde_json", - "sync_wrapper", - "tokio", - "tokio-rustls", - "tower", - "tower-http", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", + "thiserror 2.0.18", ] [[package]] @@ -3106,7 +2918,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac 0.12.1", + "hmac", "subtle", ] @@ -3130,8 +2942,8 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid 0.9.6", - "digest 0.10.7", + "const-oid", + "digest", "num-bigint-dig", "num-integer", "num-traits", @@ -3144,6 +2956,21 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-multipart-rfc7578_2" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c839d037155ebc06a571e305af66ff9fd9063a6e662447051737e1ac75beea41" +dependencies = [ + "bytes", + "futures-core", + "futures-util", + "http", + "mime", + "rand 0.9.2", + "thiserror 2.0.18", +] + [[package]] name = "rustc-demangle" version = "0.1.27" @@ -3211,7 +3038,6 @@ version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ - "aws-lc-rs", "log", "once_cell", "ring", @@ -3221,18 +3047,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-native-certs" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" -dependencies = [ - "openssl-probe", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-pki-types" version = "1.14.0" @@ -3243,33 +3057,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rustls-platform-verifier" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" -dependencies = [ - "core-foundation", - "core-foundation-sys", - "jni", - "log", - "once_cell", - "rustls 0.23.36", - "rustls-native-certs", - "rustls-platform-verifier-android", - "rustls-webpki 0.103.9", - "security-framework", - "security-framework-sys", - "webpki-root-certs", - "windows-sys 0.61.2", -] - -[[package]] -name = "rustls-platform-verifier-android" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" - [[package]] name = "rustls-webpki" version = "0.102.8" @@ -3287,7 +3074,6 @@ version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ - "aws-lc-rs", "ring", "rustls-pki-types", "untrusted", @@ -3317,24 +3103,6 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "schemars" version = "0.8.22" @@ -3389,29 +3157,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "security-framework" -version = "3.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.27" @@ -3425,37 +3170,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5484316556650182f03b43d4c746ce0e3e48074a21e2f51244b648b6542e1066" dependencies = [ "httpdate", - "reqwest 0.12.28", + "reqwest", "rustls 0.22.4", - "sentry-backtrace 0.34.0", - "sentry-contexts 0.34.0", - "sentry-core 0.34.0", - "sentry-panic 0.34.0", - "sentry-tracing 0.34.0", + "sentry-backtrace", + "sentry-contexts", + "sentry-core", + "sentry-panic", + "sentry-tracing", "tokio", - "ureq 2.12.1", + "ureq", "webpki-roots 0.26.11", ] -[[package]] -name = "sentry" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb25f439f97d26fea01d717fa626167ceffcd981addaa670001e70505b72acbb" -dependencies = [ - "cfg_aliases", - "httpdate", - "reqwest 0.13.2", - "rustls 0.23.36", - "sentry-backtrace 0.47.0", - "sentry-contexts 0.47.0", - "sentry-core 0.47.0", - "sentry-panic 0.47.0", - "sentry-tracing 0.47.0", - "tokio", - "ureq 3.3.0", -] - [[package]] name = "sentry-backtrace" version = "0.34.0" @@ -3465,18 +3191,7 @@ dependencies = [ "backtrace", "once_cell", "regex", - "sentry-core 0.34.0", -] - -[[package]] -name = "sentry-backtrace" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a8c2c1bd5c1f735e84f28b48e7d72efcaafc362b7541bc8253e60e8fcdffc6" -dependencies = [ - "backtrace", - "regex", - "sentry-core 0.47.0", + "sentry-core", ] [[package]] @@ -3489,21 +3204,7 @@ dependencies = [ "libc", "os_info", "rustc_version", - "sentry-core 0.34.0", - "uname", -] - -[[package]] -name = "sentry-contexts" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b88a90baa654d7f0e1f4b667f6b434293d9f72c71bef16b197c76af5b7d5803" -dependencies = [ - "hostname", - "libc", - "os_info", - "rustc_version", - "sentry-core 0.47.0", + "sentry-core", "uname", ] @@ -3515,42 +3216,19 @@ checksum = "161283cfe8e99c8f6f236a402b9ccf726b201f365988b5bb637ebca0abbd4a30" dependencies = [ "once_cell", "rand 0.8.5", - "sentry-types 0.34.0", + "sentry-types", "serde", "serde_json", ] -[[package]] -name = "sentry-core" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ac170a5bba8bec6e3339c90432569d89641fa7a3d3e4f44987d24f0762e6adf" -dependencies = [ - "rand 0.9.2", - "sentry-types 0.47.0", - "serde", - "serde_json", - "url", -] - [[package]] name = "sentry-panic" version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc74f229c7186dd971a9491ffcbe7883544aa064d1589bd30b83fb856cd22d63" dependencies = [ - "sentry-backtrace 0.34.0", - "sentry-core 0.34.0", -] - -[[package]] -name = "sentry-panic" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6127d3d304ba5ce0409401e85aae538e303a569f8dbb031bf64f9ba0f7174346" -dependencies = [ - "sentry-backtrace 0.47.0", - "sentry-core 0.47.0", + "sentry-backtrace", + "sentry-core", ] [[package]] @@ -3559,21 +3237,8 @@ version = "0.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd3c5faf2103cd01eeda779ea439b68c4ee15adcdb16600836e97feafab362ec" dependencies = [ - "sentry-backtrace 0.34.0", - "sentry-core 0.34.0", - "tracing-core", - "tracing-subscriber", -] - -[[package]] -name = "sentry-tracing" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27701acc51e68db5281802b709010395bfcbcb128b1d0a4e5873680d3b47ff0c" -dependencies = [ - "bitflags", - "sentry-backtrace 0.47.0", - "sentry-core 0.47.0", + "sentry-backtrace", + "sentry-core", "tracing-core", "tracing-subscriber", ] @@ -3595,23 +3260,6 @@ dependencies = [ "uuid", ] -[[package]] -name = "sentry-types" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56780cb5597d676bf22e6c11d1f062eb4def46390ea3bfb047bcbcf7dfd19bdb" -dependencies = [ - "debugid", - "hex", - "rand 0.9.2", - "serde", - "serde_json", - "thiserror 2.0.18", - "time", - "url", - "uuid", -] - [[package]] name = "serde" version = "1.0.228" @@ -3717,8 +3365,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures 0.2.17", - "digest 0.10.7", + "cpufeatures", + "digest", ] [[package]] @@ -3734,19 +3382,8 @@ 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 = "sha2" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" -dependencies = [ - "cfg-if", - "cpufeatures 0.3.0", - "digest 0.11.2", + "cpufeatures", + "digest", ] [[package]] @@ -3755,7 +3392,7 @@ version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.7", + "digest", "keccak", ] @@ -3790,7 +3427,7 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ - "digest 0.10.7", + "digest", "rand_core 0.6.4", ] @@ -3815,6 +3452,16 @@ dependencies = [ "serde", ] +[[package]] +name = "socket2" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "socket2" version = "0.6.2" @@ -3831,7 +3478,7 @@ version = "25.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7192e3a5551a7aeee90d2110b11b615798e81951fd8c8293c87ea7f88b0168f5" dependencies = [ - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.117", @@ -3884,7 +3531,7 @@ dependencies = [ "generic-array", "getrandom 0.2.17", "hex-literal", - "hmac 0.12.1", + "hmac", "k256", "num-derive", "num-integer", @@ -3893,7 +3540,7 @@ dependencies = [ "rand 0.8.5", "rand_chacha 0.3.1", "sec1", - "sha2 0.10.9", + "sha2", "sha3", "soroban-builtin-sdk-macros", "soroban-env-common", @@ -3909,7 +3556,7 @@ version = "25.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a989167512e3592d455b1e204d703cfe578a36672a77ed2f9e6f7e1bbfd9cc5c" dependencies = [ - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "serde", @@ -3964,11 +3611,11 @@ checksum = "dec603a62a90abdef898f8402471a24d8b58a0043b9a998ed6a607a19a5dabe1" dependencies = [ "darling 0.20.11", "heck", - "itertools", + "itertools 0.10.5", "macro-string", "proc-macro2", "quote", - "sha2 0.10.9", + "sha2", "soroban-env-common", "soroban-spec", "soroban-spec-rust", @@ -3983,7 +3630,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24718fac3af127fc6910eb6b1d3ccd8403201b6ef0aca73b5acabe4bc3dd42ed" dependencies = [ "base64", - "sha2 0.10.9", + "sha2", "stellar-xdr", "thiserror 1.0.69", "wasmparser 0.116.1", @@ -3998,7 +3645,7 @@ dependencies = [ "prettyplease", "proc-macro2", "quote", - "sha2 0.10.9", + "sha2", "soroban-spec", "stellar-xdr", "syn 2.0.117", @@ -4085,7 +3732,7 @@ dependencies = [ "rustls 0.23.36", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "smallvec", "thiserror 2.0.18", "tokio", @@ -4123,7 +3770,7 @@ dependencies = [ "quote", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "sqlx-core", "sqlx-mysql", "sqlx-postgres", @@ -4145,7 +3792,7 @@ dependencies = [ "byteorder", "bytes", "crc", - "digest 0.10.7", + "digest", "dotenvy", "either", "futures-channel", @@ -4155,7 +3802,7 @@ dependencies = [ "generic-array", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "itoa", "log", "md-5", @@ -4166,7 +3813,7 @@ dependencies = [ "rsa", "serde", "sha1", - "sha2 0.10.9", + "sha2", "smallvec", "sqlx-core", "stringprep", @@ -4193,7 +3840,7 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac 0.12.1", + "hmac", "home", "itoa", "log", @@ -4203,7 +3850,7 @@ dependencies = [ "rand 0.8.5", "serde", "serde_json", - "sha2 0.10.9", + "sha2", "smallvec", "sqlx-core", "stringprep", @@ -4284,7 +3931,7 @@ dependencies = [ "hex", "serde", "serde_with", - "sha2 0.10.9", + "sha2", "stellar-strkey 0.0.13", ] @@ -4483,7 +4130,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -4723,34 +4370,6 @@ dependencies = [ "webpki-roots 0.26.11", ] -[[package]] -name = "ureq" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" -dependencies = [ - "base64", - "log", - "percent-encoding", - "rustls 0.23.36", - "rustls-pki-types", - "ureq-proto", - "utf8-zero", - "webpki-roots 1.0.6", -] - -[[package]] -name = "ureq-proto" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" -dependencies = [ - "base64", - "http", - "httparse", - "log", -] - [[package]] name = "url" version = "2.5.8" @@ -4764,12 +4383,6 @@ dependencies = [ "serde_derive", ] -[[package]] -name = "utf8-zero" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" - [[package]] name = "utf8_iter" version = "1.0.4" @@ -4831,16 +4444,6 @@ dependencies = [ "libc", ] -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - [[package]] name = "want" version = "0.3.1" @@ -5030,15 +4633,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "webpki-root-certs" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" -dependencies = [ - "rustls-pki-types", -] - [[package]] name = "webpki-roots" version = "0.26.11" @@ -5083,15 +4677,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" -dependencies = [ - "windows-sys 0.61.2", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -5157,15 +4742,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-sys" -version = "0.45.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" -dependencies = [ - "windows-targets 0.42.2", -] - [[package]] name = "windows-sys" version = "0.48.0" @@ -5202,21 +4778,6 @@ dependencies = [ "windows-link", ] -[[package]] -name = "windows-targets" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" -dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", -] - [[package]] name = "windows-targets" version = "0.48.5" @@ -5265,12 +4826,6 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -5289,12 +4844,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" - [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -5313,12 +4862,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" - [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -5349,12 +4892,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" - [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -5373,12 +4910,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" - [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -5397,12 +4928,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" - [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -5421,12 +4946,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.42.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" - [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -5540,10 +5059,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" [[package]] -name = "xxhash-rust" -version = "0.8.15" +name = "yansi" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" [[package]] name = "yoke" diff --git a/backend/indexer/Cargo.toml b/backend/indexer/Cargo.toml index 5230fde..293aa90 100644 --- a/backend/indexer/Cargo.toml +++ b/backend/indexer/Cargo.toml @@ -36,7 +36,6 @@ prometheus = { version = "0.14", features = ["process"] } lazy_static = "1" governor = { version = "0.10", features = ["dashmap"] } dashmap = "6" - # Sentry error tracking sentry = { version = "0.34", default-features = false, features = ["backtrace", "contexts", "panic", "reqwest", "rustls"] } sentry-tracing = "0.34" diff --git a/contracts/pifp_protocol/src/events.rs b/contracts/pifp_protocol/src/events.rs index e0e8351..6389cf4 100644 --- a/contracts/pifp_protocol/src/events.rs +++ b/contracts/pifp_protocol/src/events.rs @@ -1,4 +1,4 @@ -#![allow(deprecated)] +//! On-chain event definitions and emission helpers for the PIFP protocol. use soroban_sdk::{contractevent, contracttype, symbol_short, Address, BytesN, Env}; @@ -69,16 +69,15 @@ use soroban_sdk::{contracttype, symbol_short, Address, BytesN, Env}; #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProjectCreated { +pub struct FundsReleased { pub project_id: u64, - pub creator: Address, pub token: Address, - pub goal: i128, + pub amount: i128, } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProjectFunded { +pub struct Refunded { pub project_id: u64, pub donator: Address, pub amount: i128, @@ -86,23 +85,23 @@ pub struct ProjectFunded { #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProjectActive { +pub struct ExpiredFundsReclaimed { pub project_id: u64, + pub creator: Address, + pub token: Address, + pub amount: i128, } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProjectVerified { - pub project_id: u64, - pub oracle: Address, - pub proof_hash: BytesN<32>, +pub struct ProtocolPaused { + pub admin: Address, } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ProjectExpired { - pub project_id: u64, - pub deadline: u64, +pub struct ProtocolUnpaused { + pub admin: Address, } #[contracttype] @@ -145,6 +144,7 @@ pub struct WhitelistRemoved { pub address: Address, } +/// Emitted each time an oracle casts a vote via `verify_and_release`. #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub struct ProjectCancelled { @@ -170,21 +170,25 @@ pub struct ProjectUnpaused { #[derive(Clone, Debug, Eq, PartialEq)] pub struct FundsReleased { pub project_id: u64, - pub token: Address, - pub amount: i128, + pub oracle: Address, + /// Bit index of this oracle in the project's authorized list. + pub oracle_index: u32, + /// Running count of unique votes after this one. + pub voter_count: u32, + /// Threshold required to release funds. + pub threshold: u32, } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct Refunded { +pub struct OracleAdded { pub project_id: u64, - pub donator: Address, - pub amount: i128, + pub oracle: Address, } #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] -pub struct ExpiredFundsReclaimed { +pub struct OracleRemoved { pub project_id: u64, pub creator: Address, pub token: Address, @@ -203,7 +207,7 @@ pub struct ProtocolUnpaused { pub admin: Address, } -// ── Emission helpers ──────────────────────────────────────────────── +// ── Emission helpers ────────────────────────────────────────────────────────── pub fn emit_project_created( env: &Env, @@ -300,27 +304,17 @@ pub fn emit_refunded(env: &Env, project_id: u64, donator: Address, amount: i128) pub fn emit_deadline_extended(env: &Env, project_id: u64, old_deadline: u64, new_deadline: u64) { let topics = (symbol_short!("ext_dead"), project_id); - let data = DeadlineExtended { - project_id, - old_deadline, - new_deadline, - }; - env.events().publish(topics, data); + env.events().publish(topics, DeadlineExtended { project_id, old_deadline, new_deadline }); } -pub fn emit_protocol_config_updated( - env: &Env, - old_config: Option, - new_config: ProtocolConfig, -) { +pub fn emit_protocol_config_updated(env: &Env, old_config: Option, new_config: ProtocolConfig) { let topics = (symbol_short!("cfg_upd"),); let data = ProtocolConfigUpdated { old_fee_recipient: old_config.as_ref().map(|cfg| cfg.fee_recipient.clone()), old_fee_bps: old_config.map_or(0, |cfg| cfg.fee_bps), new_fee_recipient: new_config.fee_recipient.clone(), new_fee_bps: new_config.fee_bps, - }; - env.events().publish(topics, data); + }); } pub fn emit_fee_deducted( diff --git a/contracts/pifp_protocol/src/fuzz_test.rs b/contracts/pifp_protocol/src/fuzz_test.rs index 29c8938..c201200 100644 --- a/contracts/pifp_protocol/src/fuzz_test.rs +++ b/contracts/pifp_protocol/src/fuzz_test.rs @@ -33,6 +33,29 @@ fn dummy_metadata_uri(env: &Env) -> Bytes { ) } +fn register<'a>( + env: &Env, + client: &PifpProtocolClient<'a>, + creator: &Address, + tokens: &SorobanVec
, + goal: i128, + proof_hash: &BytesN<32>, + deadline: u64, +) -> crate::types::Project { + let empty_oracles: SorobanVec
= SorobanVec::new(env); + client.register_project( + creator, + tokens, + &goal, + proof_hash, + &dummy_metadata_uri(env), + &deadline, + &false, + &empty_oracles, + &0u32, + ) +} + // ── 1. Registration Fuzz Tests ────────────────────────────────────── proptest! { diff --git a/contracts/pifp_protocol/src/lib.rs b/contracts/pifp_protocol/src/lib.rs index 63277aa..04ffa28 100644 --- a/contracts/pifp_protocol/src/lib.rs +++ b/contracts/pifp_protocol/src/lib.rs @@ -1,27 +1,17 @@ //! # PIFP Protocol Contract //! -//! This is the root crate of the **Proof-of-Impact Funding Protocol (PIFP)**. -//! It exposes the single Soroban contract `PifpProtocol` whose entry points cover -//! the full project lifecycle: +//! Proof-of-Impact Funding Protocol — Soroban smart contract. //! -//! | Phase | Entry Point(s) | -//! |--------------|---------------------------------------------| -//! | Bootstrap | [`PifpProtocol::init`] | -//! | Role admin | `grant_role`, `revoke_role`, `transfer_super_admin`, `set_oracle` | -//! | Registration | [`PifpProtocol::register_project`] | -//! | Funding | [`PifpProtocol::deposit`] | -//! | Donor safety | [`PifpProtocol::refund`] | -//! | Verification | [`PifpProtocol::verify_and_release`] | -//! | Queries | `get_project`, `get_project_balances`, `role_of`, `has_role` | -//! -//! ## Architecture -//! -//! Authorization is fully delegated to [`rbac`]. Storage access is fully -//! delegated to [`storage`]. This file contains **only** the public entry -//! points and event emissions — no business logic lives here directly. -//! -//! See [`ARCHITECTURE.md`](../../../../ARCHITECTURE.md) for the full system -//! architecture and threat model. +//! | Phase | Entry Point(s) | +//! |--------------|---------------------------------------------------------| +//! | Bootstrap | [`PifpProtocol::init`] | +//! | Role admin | `grant_role`, `revoke_role`, `transfer_super_admin` | +//! | Oracle mgmt | `add_oracle`, `remove_oracle`, `set_oracle` | +//! | Registration | [`PifpProtocol::register_project`] | +//! | Funding | [`PifpProtocol::deposit`] | +//! | Donor safety | [`PifpProtocol::refund`] | +//! | Verification | [`PifpProtocol::verify_and_release`] | +//! | Queries | `get_project`, `get_project_balances`, `role_of`, etc. | #![no_std] @@ -29,14 +19,15 @@ use soroban_sdk::{ contract, contractimpl, panic_with_error, token, Address, Bytes, BytesN, Env, Vec, }; -/// Refund window: 6 months (in seconds) after a project enters a terminal -/// refundable state (Expired or Cancelled). Donors must claim refunds within -/// this window; after it passes, the creator may reclaim unclaimed funds. -const REFUND_WINDOW: u64 = 6 * 30 * 24 * 60 * 60; // 15_552_000 seconds +/// Refund window: 6 months after a project enters a terminal refundable state. +pub const REFUND_WINDOW: u64 = 6 * 30 * 24 * 60 * 60; /// Maximum allowed length for a project metadata URI / CID. const MAX_METADATA_URI_LEN: u32 = 64; +/// Maximum number of authorized oracles per project (fits in a u32 BitSet). +const MAX_ORACLES: u32 = 32; + pub mod errors; pub mod events; pub mod invariants_checker; @@ -49,7 +40,6 @@ mod types; mod fuzz_test; #[cfg(test)] mod rbac_test; - #[cfg(test)] mod test; #[cfg(test)] @@ -86,7 +76,7 @@ use storage::{ is_whitelisted, load_project, load_project_pair, maybe_load_project, save_project, save_project_config, save_project_state, set_protocol_config, }; -pub use types::{DepositRequest, Project, ProjectBalances, ProjectConfig, ProjectState, ProtocolConfig}; +pub use types::{DepositRequest, OracleAgreement, Project, ProjectBalances, ProjectConfig, ProjectState, ProjectStatus, ProtocolConfig}; #[contract] pub struct PifpProtocol; @@ -98,11 +88,6 @@ impl PifpProtocol { // ───────────────────────────────────────────────────────── /// Initialise the contract and set the first SuperAdmin. - /// - /// Must be called exactly once immediately after deployment. - /// Subsequent calls panic with `Error::AlreadyInitialized`. - /// - /// - `super_admin` is granted the `SuperAdmin` role and must sign the transaction. pub fn init(env: Env, super_admin: Address) { super_admin.require_auth(); rbac::init_super_admin(&env, &super_admin); @@ -112,36 +97,22 @@ impl PifpProtocol { // Role management // ───────────────────────────────────────────────────────── - /// Grant `role` to `target`. - /// - /// - `caller` must hold `SuperAdmin` or `Admin`. - /// - Only `SuperAdmin` can grant `SuperAdmin`. pub fn grant_role(env: Env, caller: Address, target: Address, role: Role) { rbac::grant_role(&env, &caller, &target, role); } - /// Revoke any role from `target`. - /// - /// - `caller` must hold `SuperAdmin` or `Admin`. - /// - Cannot be used to remove the SuperAdmin; use `transfer_super_admin`. pub fn revoke_role(env: Env, caller: Address, target: Address) { rbac::revoke_role(&env, &caller, &target); } - /// Transfer SuperAdmin to `new_super_admin`. - /// - /// - `current_super_admin` must authorize and hold the `SuperAdmin` role. - /// - The previous SuperAdmin loses the role immediately. pub fn transfer_super_admin(env: Env, current_super_admin: Address, new_super_admin: Address) { rbac::transfer_super_admin(&env, ¤t_super_admin, &new_super_admin); } - /// Return the role held by `address`, or `None`. pub fn role_of(env: Env, address: Address) -> Option { rbac::role_of(&env, address) } - /// Return `true` if `address` holds `role`. pub fn has_role(env: Env, address: Address, role: Role) -> bool { rbac::has_role(&env, address, role) } @@ -150,9 +121,6 @@ impl PifpProtocol { // Emergency Control // ───────────────────────────────────────────────────────── - /// Pause the protocol, halting all registrations, deposits, and releases. - /// - /// - `caller` must hold `SuperAdmin` or `Admin`. pub fn pause(env: Env, caller: Address) { caller.require_auth(); rbac::require_admin_or_above(&env, &caller); @@ -160,9 +128,6 @@ impl PifpProtocol { events::emit_protocol_paused(&env, caller); } - /// Unpause the protocol. - /// - /// - `caller` must hold `SuperAdmin` or `Admin`. pub fn unpause(env: Env, caller: Address) { caller.require_auth(); rbac::require_admin_or_above(&env, &caller); @@ -170,16 +135,84 @@ impl PifpProtocol { events::emit_protocol_unpaused(&env, caller); } - /// Return true if the protocol is paused. pub fn is_paused(env: Env) -> bool { storage::is_paused(&env) } + // ───────────────────────────────────────────────────────── + // Oracle management (per-project M-of-N) + // ───────────────────────────────────────────────────────── + + /// Add an oracle to a project's authorized oracle list. + /// + /// - `admin` must hold `SuperAdmin` or `Admin`. + /// - Maximum 32 oracles per project. + /// - Adding an oracle resets any in-flight `OracleAgreement` to prevent + /// stale bits from a previous oracle at the same index from counting. + pub fn add_oracle(env: Env, admin: Address, project_id: u64, oracle: Address) { + admin.require_auth(); + rbac::require_admin_or_above(&env, &admin); + + let mut config = storage::load_project_config(&env, project_id); + + if config.authorized_oracles.len() >= MAX_ORACLES { + panic_with_error!(&env, Error::InvalidOracleConfig); + } + + // Idempotent: skip if already present. + for existing in config.authorized_oracles.iter() { + if existing == oracle { + return; + } + } + + config.authorized_oracles.push_back(oracle.clone()); + save_project_config(&env, project_id, &config); + + // Reset in-flight agreement — index layout has changed. + clear_oracle_agreement(&env, project_id); + + events::emit_oracle_added(&env, project_id, oracle); + } + + /// Remove an oracle from a project's authorized oracle list. + /// + /// - `admin` must hold `SuperAdmin` or `Admin`. + /// - Resets the in-flight `OracleAgreement` so no stale bit remains. + pub fn remove_oracle(env: Env, admin: Address, project_id: u64, oracle: Address) { + admin.require_auth(); + rbac::require_admin_or_above(&env, &admin); + + let mut config = storage::load_project_config(&env, project_id); + + let mut found = false; + let mut new_oracles: Vec
= Vec::new(&env); + for existing in config.authorized_oracles.iter() { + if existing == oracle { + found = true; + } else { + new_oracles.push_back(existing); + } + } + + if !found { + panic_with_error!(&env, Error::UnauthorizedOracle); + } + + config.authorized_oracles = new_oracles; + save_project_config(&env, project_id, &config); + + // Always reset agreement — bit indices have shifted. + clear_oracle_agreement(&env, project_id); + + events::emit_oracle_removed(&env, project_id, oracle); + } + // ───────────────────────────────────────────────────────── // Project lifecycle // ───────────────────────────────────────────────────────── - /// Register a new funding project. + /// Register a new funding project with M-of-N oracle verification. /// /// `creator` must hold the `ProjectManager`, `Admin`, or `SuperAdmin` role. #[allow(clippy::too_many_arguments)] @@ -196,7 +229,6 @@ impl PifpProtocol { ) -> Project { Self::require_not_paused(&env); creator.require_auth(); - // RBAC gate: only authorised roles may create projects. rbac::require_can_register(&env, &creator); if milestones.is_empty() { @@ -211,32 +243,33 @@ impl PifpProtocol { if accepted_tokens.len() > 10 { panic_with_error!(&env, Error::TooManyTokens); } - - // Check for duplicate tokens for i in 0..accepted_tokens.len() { let t_i = accepted_tokens.get(i).unwrap(); if accepted_tokens.last_index_of(&t_i) != Some(i) { panic_with_error!(&env, Error::DuplicateToken); } } - if goal <= 0 || goal > 1_000_000_000_000_000_000_000_000_000_000i128 { - // 10^30 panic_with_error!(&env, Error::InvalidGoal); } - let now = env.ledger().timestamp(); // Metadata must be non-empty and fit within the supported CID/URI length. if metadata_uri.is_empty() || metadata_uri.len() > MAX_METADATA_URI_LEN { panic_with_error!(&env, Error::MetadataCidInvalid); } - - // Max 5 years deadline (5 * 365 * 24 * 60 * 60) let max_deadline = now + 157_680_000; if deadline <= now || deadline > max_deadline { panic_with_error!(&env, Error::InvalidDeadline); } + // Validate oracle config: if oracles are provided, threshold must be sane. + let oracle_count = authorized_oracles.len(); + if oracle_count > 0 { + if oracle_count > MAX_ORACLES || threshold == 0 || threshold > oracle_count { + panic_with_error!(&env, Error::InvalidOracleConfig); + } + } + let id = get_and_increment_project_id(&env); let mut completed_milestones = Vec::new(&env); @@ -352,7 +385,6 @@ impl PifpProtocol { let (mut config, state) = load_project_pair(&env, project_id); - // State check: must be Funding or Active. match state.status { ProjectStatus::Funding | ProjectStatus::Active => {} _ => panic_with_error!(&env, Error::ProjectNotActive), @@ -364,13 +396,9 @@ impl PifpProtocol { if now >= config.deadline { panic_with_error!(&env, Error::ProjectExpired); } - - // New deadline must be in the future relative to current deadline. if new_deadline <= config.deadline { panic_with_error!(&env, Error::InvalidDeadline); } - - // Extension limit block: max 1 year (365 days) from now. let one_year_from_now = now + 31_536_000; if new_deadline > one_year_from_now { panic_with_error!(&env, Error::DeadlineTooLong); @@ -378,15 +406,10 @@ impl PifpProtocol { let old_deadline = config.deadline; config.deadline = new_deadline; - save_project_config(&env, project_id, &config); - events::emit_deadline_extended(&env, project_id, old_deadline, new_deadline); } - /// Add an address to a project's whitelist. - /// - /// - `caller` must be the project creator or an Admin. pub fn add_to_whitelist(env: Env, caller: Address, project_id: u64, address: Address) { caller.require_auth(); let config = storage::load_project_config(&env, project_id); @@ -395,14 +418,10 @@ impl PifpProtocol { if caller != config.creator { rbac::require_admin_or_above(&env, &caller); } - - storage::add_to_whitelist(&env, project_id, &address); + add_to_whitelist(&env, project_id, &address); events::emit_whitelist_added(&env, project_id, address); } - /// Remove an address from a project's whitelist. - /// - /// - `caller` must be the project creator or an Admin. pub fn remove_from_whitelist(env: Env, caller: Address, project_id: u64, address: Address) { caller.require_auth(); let config = storage::load_project_config(&env, project_id); @@ -411,8 +430,7 @@ impl PifpProtocol { if caller != config.creator { rbac::require_admin_or_above(&env, &caller); } - - storage::remove_from_whitelist(&env, project_id, &address); + remove_from_whitelist(&env, project_id, &address); events::emit_whitelist_removed(&env, project_id, address); } @@ -420,24 +438,15 @@ impl PifpProtocol { load_project(&env, id) } - /// Return the immutable metadata URI attached to a project. pub fn get_project_metadata(env: Env, project_id: u64) -> Bytes { let config = storage::load_project_config(&env, project_id); config.metadata_uri } - /// Return the balance of `token` for `project_id`. pub fn get_balance(env: Env, project_id: u64, token: Address) -> i128 { storage::get_token_balance(&env, project_id, &token) } - /// Return the current per-token balances for a project. - /// - /// Reconstructs the balance snapshot from persistent storage for every - /// token that was accepted at registration time. - /// - /// # Errors - /// Panics with `Error::ProjectNotFound` if `project_id` does not exist. pub fn get_project_balances(env: Env, project_id: u64) -> ProjectBalances { let project = match maybe_load_project(&env, project_id) { Some(p) => p, @@ -446,9 +455,6 @@ impl PifpProtocol { get_all_balances(&env, &project) } - /// Deposit funds into a project. - /// - /// The `token` must be one of the project's accepted tokens. pub fn deposit(env: Env, project_id: u64, donator: Address, token: Address, amount: i128) { Self::require_not_paused(&env); donator.require_auth(); @@ -457,13 +463,9 @@ impl PifpProtocol { panic_with_error!(&env, Error::InvalidAmount); } - // Read both config and state with a single helper that bumps TTLs - // atomically. This is the optimized retrieval pattern; it also returns - // the state needed for the subsequent checks. let (config, mut state) = load_project_pair(&env, project_id); Self::require_project_not_paused(&env, &state); - // Check expiration if env.ledger().timestamp() >= config.deadline { if matches!(state.status, ProjectStatus::Funding | ProjectStatus::Active) { state.status = ProjectStatus::Expired; @@ -478,14 +480,12 @@ impl PifpProtocol { panic_with_error!(&env, Error::NotWhitelisted); } - // Basic status check: must be Funding or Active. match state.status { ProjectStatus::Funding | ProjectStatus::Active => {} ProjectStatus::Expired => panic_with_error!(&env, Error::ProjectExpired), _ => panic_with_error!(&env, Error::ProjectNotActive), } - // Verify token is accepted. let mut found = false; for t in config.accepted_tokens.iter() { if t == token { @@ -497,27 +497,20 @@ impl PifpProtocol { panic_with_error!(&env, Error::TokenNotAccepted); } - // Check if this is a new unique (donator, token) pair. - // A donator balance of 0 implicitly proves they have not donated yet, saving a storage key entirely. let current_donor_balance = storage::get_donator_balance(&env, project_id, &token, &donator); let is_new_donor = current_donor_balance == 0; if is_new_donor { - // Increment donation count state.donation_count += 1; - // Save the updated state. save_project_state(&env, project_id, &state); } - // Transfer tokens from donator to contract. let token_client = token::Client::new(&env, &token); token_client.transfer(&donator, env.current_contract_address(), &amount); - // Update the per-token balance. let new_balance = storage::add_to_token_balance(&env, project_id, &token, amount); - // If this is the primary token and goal is reached, transition from Funding to Active. if state.status == ProjectStatus::Funding { if let Some(first_token) = config.accepted_tokens.get(0) { if token == first_token && new_balance >= config.goal { @@ -528,16 +521,15 @@ impl PifpProtocol { } } - // Track per-donator refundable amount for this token. let new_donor_balance = current_donor_balance .checked_add(amount) .expect("donator balance overflow"); storage::set_donator_balance(&env, project_id, &token, &donator, new_donor_balance); - // Standardized event emission events::emit_project_funded(&env, project_id, donator, amount); } + /// Deposit into multiple projects atomically in a single transaction. /// /// `deposits` is a list of `DepositRequest` entries, each specifying a @@ -595,11 +587,6 @@ impl PifpProtocol { events::emit_project_cancelled(&env, project_id, caller); } - /// Refund a donator from a cancelled or expired project that was not verified. - /// - /// Donors must claim their refund within the 6-month refund window. - /// After the window expires, only the creator may reclaim unclaimed funds - /// via [`reclaim_expired_funds`]. pub fn refund(env: Env, donator: Address, project_id: u64, token: Address) { donator.require_auth(); @@ -613,14 +600,10 @@ impl PifpProtocol { save_project_state(&env, project_id, &state); } - if !matches!( - state.status, - ProjectStatus::Expired | ProjectStatus::Cancelled - ) { + if !matches!(state.status, ProjectStatus::Expired | ProjectStatus::Cancelled) { panic_with_error!(&env, Error::ProjectNotExpired); } - // Block refunds after the refund window has expired. if state.refund_expiry > 0 && env.ledger().timestamp() >= state.refund_expiry { panic_with_error!(&env, Error::RefundWindowExpired); } @@ -630,7 +613,6 @@ impl PifpProtocol { panic_with_error!(&env, Error::InsufficientBalance); } - // Zero-out first to prevent double-refund/reentrancy patterns. storage::set_donator_balance(&env, project_id, &token, &donator, 0); storage::add_to_token_balance(&env, project_id, &token, -refund_amount); @@ -641,20 +623,13 @@ impl PifpProtocol { events::emit_refunded(&env, project_id, donator, refund_amount); } - /// Grant the Oracle role to `oracle`. - /// - /// Replaces the original `set_oracle(admin, oracle)`. - /// - `caller` must hold `SuperAdmin` or `Admin`. + /// Grant the Oracle role globally (legacy single-oracle path). pub fn set_oracle(env: Env, caller: Address, oracle: Address) { caller.require_auth(); rbac::require_admin_or_above(&env, &caller); rbac::grant_role(&env, &caller, &oracle, Role::Oracle); } - /// Update the global protocol configuration. - /// - /// - `caller` must be the `SuperAdmin`. - /// - `fee_bps` must be less than or equal to 1000 (10%). pub fn update_protocol_config(env: Env, caller: Address, fee_recipient: Address, fee_bps: u32) { caller.require_auth(); rbac::require_role(&env, &caller, &Role::SuperAdmin); @@ -664,26 +639,20 @@ impl PifpProtocol { } let old_config = get_protocol_config(&env); - let new_config = ProtocolConfig { - fee_recipient, - fee_bps, - }; - + let new_config = ProtocolConfig { fee_recipient, fee_bps }; set_protocol_config(&env, &new_config); - events::emit_protocol_config_updated(&env, old_config, new_config); } - /// Verify proof of impact and release funds to the creator. + /// Verify proof of impact and accumulate oracle votes using a BitSet. /// - /// The registered oracle submits a proof hash. If it matches the project's - /// stored `proof_hash`, the project status transitions to `Completed`. + /// Each authorized oracle calls this once. When `voter_count >= threshold` + /// the funds are released and the `OracleAgreement` is cleared. /// - /// NOTE: This is a mocked verification (hash equality). - /// The structure is prepared for future ZK-STARK verification. - /// - /// Reads the immutable config (for proof_hash) and mutable state (for status), - /// then writes back only the small state entry. + /// # BitSet mechanics + /// - Oracle at index `i` sets bit `i`: `votes |= 1 << i` + /// - Duplicate detection: if bit `i` is already set, `voter_count` is NOT incremented. + /// - On threshold: payout fires, agreement storage is cleared. pub fn verify_and_release( env: Env, oracle: Address, @@ -692,13 +661,11 @@ impl PifpProtocol { ) { Self::require_not_paused(&env); oracle.require_auth(); - // RBAC gate: caller must hold the Oracle role. - rbac::require_oracle(&env, &oracle); - // Optimised dual-read helper let (config, mut state) = load_project_pair(&env, project_id); Self::require_project_not_paused(&env, &state); + // Expiry check. if env.ledger().timestamp() >= config.deadline && matches!(state.status, ProjectStatus::Funding | ProjectStatus::Active) { @@ -708,41 +675,88 @@ impl PifpProtocol { panic_with_error!(&env, Error::ProjectExpired); } - // Ensure the project is in a verifiable state. match state.status { ProjectStatus::Funding | ProjectStatus::Active => {} - ProjectStatus::Completed => panic_with_error!(&env, Error::MilestoneAlreadyReleased), + ProjectStatus::Completed => panic_with_error!(&env, Error::ThresholdAlreadyMet), ProjectStatus::Expired => panic_with_error!(&env, Error::ProjectExpired), ProjectStatus::Cancelled => panic_with_error!(&env, Error::InvalidTransition), } - // Mocked ZK verification: compare submitted hash to stored hash. + // Proof hash check. if submitted_proof_hash != config.proof_hash { panic_with_error!(&env, Error::VerificationFailed); } - // Transition to Completed — only write the state entry. + // ── M-of-N path ────────────────────────────────────────────────────── + // If the project has an authorized oracle list, use BitSet tracking. + // Otherwise fall back to the legacy single-oracle (RBAC Oracle role) path. + if !config.authorized_oracles.is_empty() { + // Find the calling oracle's index in the authorized list. + let mut oracle_index: Option = None; + for (i, authorized) in config.authorized_oracles.iter().enumerate() { + if authorized == oracle { + oracle_index = Some(i as u32); + break; + } + } + + let oracle_index = match oracle_index { + Some(idx) => idx, + None => panic_with_error!(&env, Error::UnauthorizedOracle), + }; + + // Load (or default-initialize) the in-flight agreement. + let mut agreement = load_oracle_agreement(&env, project_id); + + let bit = 1u32 << oracle_index; + let already_voted = (agreement.votes & bit) != 0; + + // Set the bit unconditionally; only increment count if new vote. + agreement.votes |= bit; + if !already_voted { + agreement.voter_count += 1; + } + + // Emit per-vote event. + events::emit_oracle_voted( + &env, + project_id, + oracle.clone(), + oracle_index, + agreement.voter_count, + config.threshold, + ); + + // Check threshold. + if agreement.voter_count < config.threshold { + // Not yet — persist updated agreement and return. + save_oracle_agreement(&env, project_id, &agreement); + return; + } + + // Threshold met — clear agreement and fall through to payout. + clear_oracle_agreement(&env, project_id); + } else { + // Legacy path: caller must hold the global Oracle RBAC role. + rbac::require_oracle(&env, &oracle); + } + + // ── Payout ─────────────────────────────────────────────────────────── state.status = ProjectStatus::Completed; - // Transfer all deposited tokens to the creator. - // If any transfer fails, panic to revert the entire transaction. let contract_address = env.current_contract_address(); let protocol_config = get_protocol_config(&env); for token in config.accepted_tokens.iter() { - // Drain the token balance (gets balance and zeros it). let mut balance = drain_token_balance(&env, project_id, &token); - // Only transfer if there's a non-zero balance. if balance > 0 { let token_client = token::Client::new(&env, &token); - // Deduct platform fee if configured. - if let Some(config) = &protocol_config { - if config.fee_bps > 0 { - // fee = balance * bps / 10000 + if let Some(ref pcfg) = protocol_config { + if pcfg.fee_bps > 0 { let fee_amount = balance - .checked_mul(config.fee_bps as i128) + .checked_mul(pcfg.fee_bps as i128) .unwrap_or(0) .checked_div(10000) .unwrap_or(0); @@ -750,7 +764,7 @@ impl PifpProtocol { if fee_amount > 0 { token_client.transfer( &contract_address, - &config.fee_recipient, + &pcfg.fee_recipient, &fee_amount, ); balance = balance.checked_sub(fee_amount).unwrap_or(balance); @@ -759,98 +773,65 @@ impl PifpProtocol { project_id, token.clone(), fee_amount, - config.fee_recipient.clone(), + pcfg.fee_recipient.clone(), ); } } } - // Transfer remaining to creator. if balance > 0 { token_client.transfer(&contract_address, &config.creator, &balance); - // Emit funds_released event for this token. events::emit_funds_released(&env, project_id, token, balance); } } } - // Save the updated state (now marked as Completed). save_project_state(&env, project_id, &state); - - // Standardized event emission - events::emit_project_verified(&env, project_id, oracle.clone(), submitted_proof_hash); + events::emit_project_verified(&env, project_id, oracle, submitted_proof_hash); } - /// Mark a project as expired if its deadline has passed. - /// - /// Permissionless: anyone can trigger expiration once the deadline is met. - /// - Panics if project is not in Funding status. - /// - Panics if deadline has not passed. pub fn expire_project(env: Env, project_id: u64) { let (config, mut state) = load_project_pair(&env, project_id); - // State transition check: only Funding or Active projects can expire. - // Completed projects cannot be expired. match state.status { ProjectStatus::Funding | ProjectStatus::Active => {} _ => panic_with_error!(&env, Error::InvalidTransition), } - // Deadline check. if env.ledger().timestamp() < config.deadline { panic_with_error!(&env, Error::ProjectNotExpired); } - // Update status and save. state.status = ProjectStatus::Expired; state.refund_expiry = env.ledger().timestamp() + REFUND_WINDOW; save_project_state(&env, project_id, &state); - - // Standardized event emission. events::emit_project_expired(&env, project_id, config.deadline); } - // ───────────────────────────────────────────────────────── - // Donor Refund Expiry - // ───────────────────────────────────────────────────────── - - /// Reclaim unclaimed donor funds after the 6-month refund window has expired. - /// - /// Only the project creator may call this, and only for projects that are - /// `Expired` or `Cancelled` whose `refund_expiry` timestamp has passed. - /// For each accepted token, any remaining balance is transferred to the creator. pub fn reclaim_expired_funds(env: Env, creator: Address, project_id: u64) { Self::require_not_paused(&env); creator.require_auth(); let (config, state) = load_project_pair(&env, project_id); - // Only the project creator may reclaim. if creator != config.creator { panic_with_error!(&env, Error::NotAuthorized); } - // Project must be in a terminal refundable state. - if !matches!( - state.status, - ProjectStatus::Expired | ProjectStatus::Cancelled - ) { + if !matches!(state.status, ProjectStatus::Expired | ProjectStatus::Cancelled) { panic_with_error!(&env, Error::InvalidTransition); } - // The refund window must have expired. if state.refund_expiry == 0 || env.ledger().timestamp() < state.refund_expiry { panic_with_error!(&env, Error::RefundWindowActive); } - // Drain remaining balances for each accepted token. let contract_address = env.current_contract_address(); for token in config.accepted_tokens.iter() { let balance = drain_token_balance(&env, project_id, &token); if balance > 0 { let token_client = token::Client::new(&env, &token); token_client.transfer(&contract_address, &config.creator, &balance); - events::emit_expired_funds_reclaimed( &env, project_id, @@ -863,7 +844,7 @@ impl PifpProtocol { } // ───────────────────────────────────────────────────────── - // Internal Helpers + // Internal helpers // ───────────────────────────────────────────────────────── fn require_not_paused(env: &Env) { diff --git a/contracts/pifp_protocol/src/rbac.rs b/contracts/pifp_protocol/src/rbac.rs index 08ec8d4..06c181e 100644 --- a/contracts/pifp_protocol/src/rbac.rs +++ b/contracts/pifp_protocol/src/rbac.rs @@ -251,6 +251,12 @@ pub fn require_oracle(env: &Env, address: &Address) { require_role(env, address, &Role::Oracle); } +/// Assert that `address` holds the SuperAdmin role. +#[inline] +pub fn require_super_admin(env: &Env, address: &Address) { + require_role(env, address, &Role::SuperAdmin); +} + /// Assert that `address` may register and manage projects. /// ProjectManager, Admin, and SuperAdmin may all register projects. #[inline] diff --git a/contracts/pifp_protocol/src/storage.rs b/contracts/pifp_protocol/src/storage.rs index f02c1e8..aaf9acc 100644 --- a/contracts/pifp_protocol/src/storage.rs +++ b/contracts/pifp_protocol/src/storage.rs @@ -32,7 +32,8 @@ use soroban_sdk::{contracttype, panic_with_error, Address, Env, Vec}; use crate::errors::Error; use crate::types::{ - Project, ProjectBalances, ProjectConfig, ProjectState, ProtocolConfig, TokenBalance, + OracleAgreement, Project, ProjectBalances, ProjectConfig, ProjectState, ProtocolConfig, + TokenBalance, }; // ── TTL Constants ──────────────────────────────────────────────────── @@ -74,6 +75,8 @@ pub enum DataKey { ProtocolConfig, /// Whitelisted donator for a project (Persistent). Whitelist(u64, Address), + /// In-flight oracle vote agreement for a project (Temporary). + OracleAgreement(u64), } // ── Instance Storage Helpers ───────────────────────────────────────── @@ -467,3 +470,35 @@ pub fn remove_from_whitelist(env: &Env, project_id: u64, address: &Address) { let key = DataKey::Whitelist(project_id, address.clone()); env.storage().persistent().remove(&key); } + +// ── Oracle Agreement Helpers (Temporary Storage) ───────────────────── + +/// Approximate ledgers for 1 day — used as TTL for temporary oracle agreement. +const TEMP_AGREEMENT_TTL: u32 = 17_280; + +/// Load the current `OracleAgreement` for a project, or return a zeroed default. +pub fn load_oracle_agreement(env: &Env, project_id: u64) -> OracleAgreement { + let key = DataKey::OracleAgreement(project_id); + env.storage() + .temporary() + .get::(&key) + .unwrap_or(OracleAgreement { + votes: 0, + voter_count: 0, + }) +} + +/// Persist an updated `OracleAgreement` with a 1-day TTL. +pub fn save_oracle_agreement(env: &Env, project_id: u64, agreement: &OracleAgreement) { + let key = DataKey::OracleAgreement(project_id); + env.storage().temporary().set(&key, agreement); + env.storage() + .temporary() + .extend_ttl(&key, TEMP_AGREEMENT_TTL, TEMP_AGREEMENT_TTL); +} + +/// Remove the `OracleAgreement` entry once the threshold is met. +pub fn clear_oracle_agreement(env: &Env, project_id: u64) { + let key = DataKey::OracleAgreement(project_id); + env.storage().temporary().remove(&key); +} diff --git a/contracts/pifp_protocol/src/test.rs b/contracts/pifp_protocol/src/test.rs index c5ea6e5..ddfb7bd 100644 --- a/contracts/pifp_protocol/src/test.rs +++ b/contracts/pifp_protocol/src/test.rs @@ -56,16 +56,13 @@ fn test_register_zero_goal_fails() { fn test_register_past_deadline_fails() { let ctx = TestContext::new(); let tokens = Vec::from_array(&ctx.env, [ctx.generate_address()]); - - // Set ledger to future ctx.jump_time(200_000); - - // Attempt to register with a past deadline (86400 from 100_000 < 200_000) - let past_deadline = 150_000; + let past_deadline = 150_000u64; + let empty_oracles: soroban_sdk::Vec = soroban_sdk::Vec::new(&ctx.env); ctx.client.register_project( &ctx.manager, &tokens, - &1000, + &1000i128, &ctx.dummy_proof(), &ctx.dummy_metadata_uri(), &past_deadline, @@ -78,8 +75,7 @@ fn test_register_past_deadline_fails() { fn test_deposit_zero_amount_fails() { let ctx = TestContext::new(); let (project, token, _) = ctx.setup_project(1000); - ctx.client - .deposit(&project.id, &ctx.manager, &token.address, &0i128); + ctx.client.deposit(&project.id, &ctx.manager, &token.address, &0i128); } #[test] @@ -87,22 +83,16 @@ fn test_deposit_zero_amount_fails() { fn test_deposit_after_deadline_fails() { let ctx = TestContext::new(); let (project, token, _) = ctx.setup_project(1000); - - // Fast-forward time ctx.jump_time(project.deadline + 1); - - ctx.client - .deposit(&project.id, &ctx.admin, &token.address, &100i128); + ctx.client.deposit(&project.id, &ctx.admin, &token.address, &100i128); } #[test] fn test_admin_can_pause_and_unpause() { let ctx = TestContext::new(); assert!(!ctx.client.is_paused()); - ctx.client.pause(&ctx.admin); assert!(ctx.client.is_paused()); - ctx.client.unpause(&ctx.admin); assert!(!ctx.client.is_paused()); } @@ -112,23 +102,19 @@ fn test_project_exists_and_maybe_load_helpers() { let ctx = TestContext::new(); let contract_id = ctx.client.address.clone(); - // nothing registered yet ctx.env.as_contract(&contract_id, || { assert!(!crate::storage::project_exists(&ctx.env, 0)); assert_eq!(crate::storage::maybe_load_project(&ctx.env, 0), None); }); - // register one project let (project, _, _) = ctx.setup_project(1000); ctx.env.as_contract(&contract_id, || { assert!(crate::storage::project_exists(&ctx.env, project.id)); let cfg = crate::storage::maybe_load_project_config(&ctx.env, project.id).unwrap(); assert_eq!(cfg.id, project.id); - let st = crate::storage::maybe_load_project_state(&ctx.env, project.id).unwrap(); assert_eq!(st.donation_count, 0); - let loaded = crate::storage::maybe_load_project(&ctx.env, project.id).unwrap(); assert_eq!(loaded.creator, project.creator); }); @@ -147,7 +133,6 @@ fn test_non_admin_cannot_pause() { fn test_registration_fails_when_paused() { let ctx = TestContext::new(); ctx.client.pause(&ctx.admin); - let tokens = Vec::from_array(&ctx.env, [ctx.generate_address()]); ctx.register_project(&tokens, 1000, false); } @@ -157,20 +142,15 @@ fn test_registration_fails_when_paused() { fn test_deposit_fails_when_paused() { let ctx = TestContext::new(); let (project, token, _) = ctx.setup_project(1000); - ctx.client.pause(&ctx.admin); - ctx.client - .deposit(&project.id, &ctx.manager, &token.address, &100i128); + ctx.client.deposit(&project.id, &ctx.manager, &token.address, &100i128); } #[test] fn test_queries_work_when_paused() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - ctx.client.pause(&ctx.admin); - - // Query should still work let loaded = ctx.client.get_project(&project.id); assert_eq!(loaded.id, project.id); } diff --git a/contracts/pifp_protocol/src/test_donation_count.rs b/contracts/pifp_protocol/src/test_donation_count.rs index 6da5ce2..fab8147 100644 --- a/contracts/pifp_protocol/src/test_donation_count.rs +++ b/contracts/pifp_protocol/src/test_donation_count.rs @@ -14,13 +14,9 @@ fn test_donation_count_increments_for_new_donor() { let ctx = TestContext::new(); let (project, token, sac) = ctx.setup_project(10000); let donator = ctx.generate_address(); - - sac.mint(&donator, &1_000); - ctx.client - .deposit(&project.id, &donator, &token.address, &500i128); - - let updated = ctx.client.get_project(&project.id); - assert_eq!(updated.donation_count, 1); + sac.mint(&donator, &1_000i128); + ctx.client.deposit(&project.id, &donator, &token.address, &500i128); + assert_eq!(ctx.client.get_project(&project.id).donation_count, 1); } #[test] @@ -28,15 +24,10 @@ fn test_donation_count_stays_same_for_repeated_donor() { let ctx = TestContext::new(); let (project, token, sac) = ctx.setup_project(10000); let donator = ctx.generate_address(); - - sac.mint(&donator, &2_000); - ctx.client - .deposit(&project.id, &donator, &token.address, &500i128); + sac.mint(&donator, &2_000i128); + ctx.client.deposit(&project.id, &donator, &token.address, &500i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 1); - - // Second deposit from same donor with same token - ctx.client - .deposit(&project.id, &donator, &token.address, &300i128); + ctx.client.deposit(&project.id, &donator, &token.address, &300i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 1); } @@ -44,51 +35,37 @@ fn test_donation_count_stays_same_for_repeated_donor() { fn test_donation_count_increments_for_different_donors() { let ctx = TestContext::new(); let (project, token, sac) = ctx.setup_project(10000); - let donator1 = ctx.generate_address(); - let donator2 = ctx.generate_address(); - - sac.mint(&donator1, &1_000); - sac.mint(&donator2, &1_000); - - ctx.client - .deposit(&project.id, &donator1, &token.address, &500i128); - ctx.client - .deposit(&project.id, &donator2, &token.address, &300i128); - - let updated = ctx.client.get_project(&project.id); - assert_eq!(updated.donation_count, 2); + let d1 = ctx.generate_address(); + let d2 = ctx.generate_address(); + sac.mint(&d1, &1_000i128); + sac.mint(&d2, &1_000i128); + ctx.client.deposit(&project.id, &d1, &token.address, &500i128); + ctx.client.deposit(&project.id, &d2, &token.address, &300i128); + assert_eq!(ctx.client.get_project(&project.id).donation_count, 2); } #[test] fn test_donation_count_increments_for_same_donor_different_tokens() { let ctx = TestContext::new(); - - // Setup manual project with 2 tokens let (token1, sac1) = ctx.create_token(); let (token2, sac2) = ctx.create_token(); - let tokens = - soroban_sdk::Vec::from_array(&ctx.env, [token1.address.clone(), token2.address.clone()]); - let metadata_uri = ctx.dummy_metadata_uri(); + let tokens = soroban_sdk::Vec::from_array(&ctx.env, [token1.address.clone(), token2.address.clone()]); + let empty_oracles: soroban_sdk::Vec = soroban_sdk::Vec::new(&ctx.env); let project = ctx.client.register_project( &ctx.manager, &tokens, - &10_000, + &10_000i128, &ctx.dummy_proof(), - &metadata_uri, + &ctx.dummy_metadata_uri(), &(ctx.env.ledger().timestamp() + 86400), &false, ); - let donator = ctx.generate_address(); - sac1.mint(&donator, &1_000); - sac2.mint(&donator, &1_000); - - ctx.client - .deposit(&project.id, &donator, &token1.address, &500i128); + sac1.mint(&donator, &1_000i128); + sac2.mint(&donator, &1_000i128); + ctx.client.deposit(&project.id, &donator, &token1.address, &500i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 1); - - ctx.client - .deposit(&project.id, &donator, &token2.address, &300i128); + ctx.client.deposit(&project.id, &donator, &token2.address, &300i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 2); } @@ -97,55 +74,35 @@ fn test_donation_count_complex_scenario() { let ctx = TestContext::new(); let (token1, sac1) = ctx.create_token(); let (token2, sac2) = ctx.create_token(); - let tokens = - soroban_sdk::Vec::from_array(&ctx.env, [token1.address.clone(), token2.address.clone()]); - let metadata_uri = ctx.dummy_metadata_uri(); + let tokens = soroban_sdk::Vec::from_array(&ctx.env, [token1.address.clone(), token2.address.clone()]); + let empty_oracles: soroban_sdk::Vec = soroban_sdk::Vec::new(&ctx.env); let project = ctx.client.register_project( &ctx.manager, &tokens, - &10_000, + &10_000i128, &ctx.dummy_proof(), - &metadata_uri, + &ctx.dummy_metadata_uri(), &(ctx.env.ledger().timestamp() + 86400), &false, ); + let d1 = ctx.generate_address(); + let d2 = ctx.generate_address(); + let d3 = ctx.generate_address(); + sac1.mint(&d1, &5_000i128); sac1.mint(&d2, &5_000i128); sac1.mint(&d3, &5_000i128); + sac2.mint(&d1, &5_000i128); sac2.mint(&d2, &5_000i128); - let donator1 = ctx.generate_address(); - let donator2 = ctx.generate_address(); - let donator3 = ctx.generate_address(); - - sac1.mint(&donator1, &5_000); - sac1.mint(&donator2, &5_000); - sac1.mint(&donator3, &5_000); - sac2.mint(&donator1, &5_000); - sac2.mint(&donator2, &5_000); - - // Sequence of deposits - ctx.client - .deposit(&project.id, &donator1, &token1.address, &100i128); + ctx.client.deposit(&project.id, &d1, &token1.address, &100i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 1); - - ctx.client - .deposit(&project.id, &donator1, &token1.address, &100i128); + ctx.client.deposit(&project.id, &d1, &token1.address, &100i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 1); - - ctx.client - .deposit(&project.id, &donator2, &token1.address, &200i128); + ctx.client.deposit(&project.id, &d2, &token1.address, &200i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 2); - - ctx.client - .deposit(&project.id, &donator1, &token2.address, &150i128); + ctx.client.deposit(&project.id, &d1, &token2.address, &150i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 3); - - ctx.client - .deposit(&project.id, &donator3, &token1.address, &300i128); + ctx.client.deposit(&project.id, &d3, &token1.address, &300i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 4); - - ctx.client - .deposit(&project.id, &donator2, &token2.address, &250i128); + ctx.client.deposit(&project.id, &d2, &token2.address, &250i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 5); - - ctx.client - .deposit(&project.id, &donator2, &token2.address, &100i128); + ctx.client.deposit(&project.id, &d2, &token2.address, &100i128); assert_eq!(ctx.client.get_project(&project.id).donation_count, 5); } diff --git a/contracts/pifp_protocol/src/test_errors.rs b/contracts/pifp_protocol/src/test_errors.rs index b38d221..4cf46f6 100644 --- a/contracts/pifp_protocol/src/test_errors.rs +++ b/contracts/pifp_protocol/src/test_errors.rs @@ -3,10 +3,6 @@ extern crate std; use crate::test_utils::TestContext; use soroban_sdk::{BytesN, Vec}; -// ───────────────────────────────────────────────────────── -// ProjectNotFound (#1) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #1)")] fn test_get_project_not_found() { @@ -29,29 +25,15 @@ fn test_get_project_balances_not_found() { ctx.client.get_project_balances(&999); } -// ───────────────────────────────────────────────────────── -// MilestoneAlreadyReleased (#3) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #3)")] fn test_verify_already_completed_project() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - - // First verification succeeds. - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); - - // Second verification must fail with MilestoneAlreadyReleased. - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); + ctx.client.verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); + ctx.client.verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); } -// ───────────────────────────────────────────────────────── -// InvalidGoal (#7) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #7)")] fn test_register_negative_goal_fails() { @@ -65,15 +47,10 @@ fn test_register_negative_goal_fails() { fn test_register_goal_exceeds_upper_bound_fails() { let ctx = TestContext::new(); let tokens = Vec::from_array(&ctx.env, [ctx.generate_address()]); - // 10^30 + 1 — exceeds upper bound let huge_goal: i128 = 1_000_000_000_000_000_000_000_000_000_001; ctx.register_project(&tokens, huge_goal, false); } -// ───────────────────────────────────────────────────────── -// TooManyTokens (#10) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #10)")] fn test_register_too_many_tokens_fails() { @@ -85,21 +62,17 @@ fn test_register_too_many_tokens_fails() { ctx.register_project(&tokens, 1000, false); } -// ───────────────────────────────────────────────────────── -// InvalidDeadline (#13) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #13)")] fn test_register_deadline_too_far_in_future_fails() { let ctx = TestContext::new(); let tokens = Vec::from_array(&ctx.env, [ctx.generate_address()]); - // More than 5 years in the future let too_far_deadline = ctx.env.ledger().timestamp() + 200_000_000; + let empty_oracles: soroban_sdk::Vec = soroban_sdk::Vec::new(&ctx.env); ctx.client.register_project( &ctx.manager, &tokens, - &1000, + &1000i128, &ctx.dummy_proof(), &ctx.dummy_metadata_uri(), &too_far_deadline, @@ -107,25 +80,15 @@ fn test_register_deadline_too_far_in_future_fails() { ); } -// ───────────────────────────────────────────────────────── -// VerificationFailed (#16) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #16)")] fn test_verify_wrong_proof_hash_fails() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - let wrong_proof = BytesN::from_array(&ctx.env, &[0xffu8; 32]); - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &wrong_proof); + ctx.client.verify_and_release(&ctx.oracle, &project.id, &wrong_proof); } -// ───────────────────────────────────────────────────────── -// EmptyAcceptedTokens (#17) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #17)")] fn test_register_empty_tokens_fails() { @@ -134,72 +97,42 @@ fn test_register_empty_tokens_fails() { ctx.register_project(&tokens, 1000, false); } -// ───────────────────────────────────────────────────────── -// ProtocolPaused (#19) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #19)")] fn test_verify_when_paused_fails() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - ctx.client.pause(&ctx.admin); - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); + ctx.client.verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); } -// ───────────────────────────────────────────────────────── -// ProjectNotExpired (#21) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #21)")] fn test_expire_project_before_deadline_fails() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - // No time jump — deadline has not passed. ctx.client.expire_project(&project.id); } -// ───────────────────────────────────────────────────────── -// InvalidTransition (#22) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #22)")] fn test_expire_completed_project_fails_with_invalid_transition() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - - // Complete the project. - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); - - // Attempt to expire it — should fail with InvalidTransition. + ctx.client.verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); ctx.jump_time(project.deadline + 1); ctx.client.expire_project(&project.id); } -// ───────────────────────────────────────────────────────── -// TokenNotAccepted (#23) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #23)")] fn test_deposit_unaccepted_token_fails() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); let rogue_token = ctx.generate_address(); - - ctx.client - .deposit(&project.id, &ctx.manager, &rogue_token, &100i128); + ctx.client.deposit(&project.id, &ctx.manager, &rogue_token, &100i128); } -// ───────────────────────────────────────────────────────── -// NotAuthorized (#6) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #6)")] fn test_admin_cannot_cancel_project() { @@ -207,20 +140,12 @@ fn test_admin_cannot_cancel_project() { let (project, token, sac) = ctx.setup_project(500); let donator = ctx.generate_address(); let other_admin = ctx.generate_address(); - - ctx.client - .grant_role(&ctx.admin, &other_admin, &crate::Role::Admin); + ctx.client.grant_role(&ctx.admin, &other_admin, &crate::Role::Admin); sac.mint(&donator, &600i128); - ctx.client - .deposit(&project.id, &donator, &token.address, &600i128); - + ctx.client.deposit(&project.id, &donator, &token.address, &600i128); ctx.client.cancel_project(&other_admin, &project.id); } -// ───────────────────────────────────────────────────────── -// InvalidTransition (#22) -// ───────────────────────────────────────────────────────── - #[test] #[should_panic(expected = "HostError: Error(Contract, #22)")] fn test_cancel_non_active_project_fails() { diff --git a/contracts/pifp_protocol/src/test_events.rs b/contracts/pifp_protocol/src/test_events.rs index 44878e7..2d93455 100644 --- a/contracts/pifp_protocol/src/test_events.rs +++ b/contracts/pifp_protocol/src/test_events.rs @@ -8,104 +8,64 @@ use crate::test_utils::TestContext; fn test_project_created_event() { let ctx = TestContext::new(); let (_project, _token, _) = ctx.setup_project(5000); - - // let all_events = ctx.env.events().all(); - // In SDK 25, testing events is more complex with ContractEvents type. - // Skipping for now to focus on core logic tests. } #[test] fn test_project_funded_event() { let ctx = TestContext::new(); let (project, token, sac) = ctx.setup_project(10000); - let donator = ctx.generate_address(); - let amount = 1000i128; - sac.mint(&donator, &amount); - - ctx.client - .deposit(&project.id, &donator, &token.address, &amount); + sac.mint(&donator, &1000i128); + ctx.client.deposit(&project.id, &donator, &token.address, &1000i128); } #[test] fn test_project_verified_event() { let ctx = TestContext::new(); let (project, _, _) = ctx.setup_project(1000); - let proof = ctx.dummy_proof(); - - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &proof); + ctx.client.verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); } #[test] fn test_get_project_balances() { let ctx = TestContext::new(); - - // Create two distinct SAC tokens let (token_a, sac_a) = ctx.create_token(); let (token_b, sac_b) = ctx.create_token(); - - // Grant manager and register project with two tokens - let metadata_uri = ctx.dummy_metadata_uri(); - let tokens = vec![&ctx.env, token_a.address.clone(), token_b.address.clone()]; + let tokens = soroban_sdk::vec![&ctx.env, token_a.address.clone(), token_b.address.clone()]; + let empty_oracles: soroban_sdk::Vec = soroban_sdk::Vec::new(&ctx.env); let project = ctx.client.register_project( &ctx.manager, &tokens, - &10_000, + &10_000i128, &ctx.dummy_proof(), - &metadata_uri, + &ctx.dummy_metadata_uri(), &(ctx.env.ledger().timestamp() + 86400), &false, ); let donator = ctx.generate_address(); - let amount_a = 2_500i128; - let amount_b = 7_000i128; - - sac_a.mint(&donator, &amount_a); - sac_b.mint(&donator, &amount_b); + sac_a.mint(&donator, &2_500i128); + sac_b.mint(&donator, &7_000i128); + ctx.client.deposit(&project.id, &donator, &token_a.address, &2_500i128); + ctx.client.deposit(&project.id, &donator, &token_b.address, &7_000i128); - ctx.client - .deposit(&project.id, &donator, &token_a.address, &amount_a); - ctx.client - .deposit(&project.id, &donator, &token_b.address, &amount_b); - - // Query balances let balances = ctx.client.get_project_balances(&project.id); - assert_eq!(balances.project_id, project.id); assert_eq!(balances.balances.len(), 2); - - let bal_a = balances.balances.get(0).unwrap(); - let bal_b = balances.balances.get(1).unwrap(); - - assert_eq!(bal_a.token, token_a.address); - assert_eq!(bal_a.balance, amount_a); - assert_eq!(bal_b.token, token_b.address); - assert_eq!(bal_b.balance, amount_b); + assert_eq!(balances.balances.get(0).unwrap().balance, 2_500i128); + assert_eq!(balances.balances.get(1).unwrap().balance, 7_000i128); } #[test] fn test_funds_released_to_creator() { let ctx = TestContext::new(); let (project, token, sac) = ctx.setup_project(5000); - let donator = ctx.generate_address(); - let deposit_amount = 1000i128; - sac.mint(&donator, &deposit_amount); - - ctx.client - .deposit(&project.id, &donator, &token.address, &deposit_amount); - - // Verify and release - ctx.client - .verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); - - // Check creator (manager) received the funds - assert_eq!(token.balance(&ctx.manager), deposit_amount); - - // Check contract no longer has the funds - assert_eq!(token.balance(&ctx.client.address), 0); + sac.mint(&donator, &1000i128); + ctx.client.deposit(&project.id, &donator, &token.address, &1000i128); + ctx.client.verify_and_release(&ctx.oracle, &project.id, &ctx.dummy_proof()); + assert_eq!(token.balance(&ctx.manager), 1000i128); + assert_eq!(token.balance(&ctx.client.address), 0i128); } #[test] @@ -113,11 +73,8 @@ fn test_refunded_event() { let ctx = TestContext::new(); let (project, token, sac) = ctx.setup_project(1000); let donator = ctx.generate_address(); - sac.mint(&donator, &400i128); - ctx.client - .deposit(&project.id, &donator, &token.address, &400i128); - + ctx.client.deposit(&project.id, &donator, &token.address, &400i128); ctx.jump_time(86_401); ctx.client.refund(&donator, &project.id, &token.address); } diff --git a/contracts/pifp_protocol/src/test_multi_oracle.rs b/contracts/pifp_protocol/src/test_multi_oracle.rs new file mode 100644 index 0000000..52b5b21 --- /dev/null +++ b/contracts/pifp_protocol/src/test_multi_oracle.rs @@ -0,0 +1,176 @@ +extern crate std; + +use crate::{test_utils::TestContext, ProjectStatus, Role}; +use soroban_sdk::Vec; + +/// Helper: register a project with an explicit M-of-N oracle set. +fn register_with_oracles( + ctx: &TestContext, + oracles: &Vec, + threshold: u32, +) -> crate::types::Project { + let (token, _) = ctx.create_token(); + let tokens = soroban_sdk::Vec::from_array(&ctx.env, [token.address.clone()]); + ctx.client.register_project( + &ctx.manager, + &tokens, + &1000i128, + &ctx.dummy_proof(), + &ctx.dummy_metadata_uri(), + &(ctx.env.ledger().timestamp() + 86400), + &false, + oracles, + &threshold, + ) +} + +// ── Happy path: 2-of-3 ─────────────────────────────────────────────────────── + +#[test] +fn test_two_of_three_releases_on_second_vote() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let o2 = ctx.generate_address(); + let o3 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone(), o2.clone(), o3.clone()]); + + let project = register_with_oracles(&ctx, &oracles, 2); + + // First vote — not yet at threshold. + ctx.client.verify_and_release(&o1, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Funding); + + // Second vote — threshold met, funds released. + ctx.client.verify_and_release(&o2, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Completed); +} + +// ── Duplicate vote prevention ───────────────────────────────────────────────── + +#[test] +fn test_duplicate_vote_does_not_double_count() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let o2 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone(), o2.clone()]); + + // 2-of-2 threshold. + let project = register_with_oracles(&ctx, &oracles, 2); + + // o1 votes twice — second vote must be a no-op on voter_count. + ctx.client.verify_and_release(&o1, &project.id, &ctx.dummy_proof()); + ctx.client.verify_and_release(&o1, &project.id, &ctx.dummy_proof()); + + // Still Funding — only 1 unique vote counted. + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Funding); + + // o2 votes — now 2 unique votes, threshold met. + ctx.client.verify_and_release(&o2, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Completed); +} + +// ── Unauthorized oracle rejected ────────────────────────────────────────────── + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #28)")] +fn test_unauthorized_oracle_rejected() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone()]); + let project = register_with_oracles(&ctx, &oracles, 1); + + let rogue = ctx.generate_address(); + ctx.client.verify_and_release(&rogue, &project.id, &ctx.dummy_proof()); +} + +// ── ThresholdAlreadyMet after completion ────────────────────────────────────── + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #29)")] +fn test_vote_after_threshold_met_panics() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone()]); + let project = register_with_oracles(&ctx, &oracles, 1); + + // First vote completes the project. + ctx.client.verify_and_release(&o1, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Completed); + + // Second vote must fail with ThresholdAlreadyMet. + ctx.client.verify_and_release(&o1, &project.id, &ctx.dummy_proof()); +} + +// ── add_oracle / remove_oracle ──────────────────────────────────────────────── + +#[test] +fn test_add_oracle_and_vote() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone()]); + let project = register_with_oracles(&ctx, &oracles, 1); + + // Add a second oracle post-registration. + let o2 = ctx.generate_address(); + ctx.client.add_oracle(&ctx.admin, &project.id, &o2); + + // Update threshold to 2-of-2 via a new registration isn't possible, + // but we can verify o2 can now vote (threshold is still 1 from registration). + ctx.client.verify_and_release(&o2, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Completed); +} + +#[test] +fn test_remove_oracle_resets_agreement() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let o2 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone(), o2.clone()]); + let project = register_with_oracles(&ctx, &oracles, 2); + + // o1 votes. + ctx.client.verify_and_release(&o1, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Funding); + + // Admin removes o1 — agreement is reset. + ctx.client.remove_oracle(&ctx.admin, &project.id, &o1); + + // o2 is now at index 0; o1's old vote is gone. + // o2 votes — but threshold is still 2 and only 1 oracle remains, so it won't release. + // (This tests that the reset happened — o2's vote alone won't meet threshold=2.) + ctx.client.verify_and_release(&o2, &project.id, &ctx.dummy_proof()); + assert_eq!(ctx.client.get_project(&project.id).status, ProjectStatus::Funding); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #28)")] +fn test_remove_nonexistent_oracle_fails() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone()]); + let project = register_with_oracles(&ctx, &oracles, 1); + + let ghost = ctx.generate_address(); + ctx.client.remove_oracle(&ctx.admin, &project.id, &ghost); +} + +// ── InvalidOracleConfig validation ─────────────────────────────────────────── + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #30)")] +fn test_threshold_exceeds_oracle_count_fails() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone()]); + // threshold=2 but only 1 oracle — invalid. + register_with_oracles(&ctx, &oracles, 2); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #30)")] +fn test_zero_threshold_with_oracles_fails() { + let ctx = TestContext::new(); + let o1 = ctx.generate_address(); + let oracles = soroban_sdk::Vec::from_array(&ctx.env, [o1.clone()]); + register_with_oracles(&ctx, &oracles, 0); +} diff --git a/contracts/pifp_protocol/src/test_refund.rs b/contracts/pifp_protocol/src/test_refund.rs index b6a3453..4767d75 100644 --- a/contracts/pifp_protocol/src/test_refund.rs +++ b/contracts/pifp_protocol/src/test_refund.rs @@ -1,13 +1,10 @@ extern crate std; -use soroban_sdk::{ - testutils::{Address as _, Ledger}, - token, Address, Bytes, BytesN, Env, -}; +use soroban_sdk::{testutils::Address as _, token, Address, Env}; use crate::{PifpProtocol, PifpProtocolClient, ProjectStatus, Role}; -fn setup() -> (Env, PifpProtocolClient<'static>) { +fn setup() -> (Env, PifpProtocolClient<'static>, Address) { let env = Env::default(); env.mock_all_auths(); let mut ledger = env.ledger().get(); @@ -15,17 +12,12 @@ fn setup() -> (Env, PifpProtocolClient<'static>) { env.ledger().set(ledger); let contract_id = env.register(PifpProtocol, ()); let client = PifpProtocolClient::new(&env, &contract_id); - (env, client) -} - -fn setup_with_init() -> (Env, PifpProtocolClient<'static>, Address) { - let (env, client) = setup(); let super_admin = Address::generate(&env); client.init(&super_admin); (env, client, super_admin) } -fn create_token<'a>(env: &Env, admin: &Address) -> token::Client<'a> { +fn create_token(env: &Env, admin: &Address) -> token::Client<'static> { let addr = env.register_stellar_asset_contract_v2(admin.clone()); token::Client::new(env, &addr.address()) } @@ -43,7 +35,7 @@ fn dummy_metadata_uri(env: &Env) -> Bytes { #[test] fn test_refund_success_after_expiry() { - let (env, client, super_admin) = setup_with_init(); + let (env, client, super_admin) = setup(); let creator = Address::generate(&env); let donator = Address::generate(&env); let token_admin = Address::generate(&env); @@ -68,33 +60,20 @@ fn test_refund_success_after_expiry() { let mut ledger = env.ledger().get(); ledger.timestamp = deadline + 1; - ledger.sequence_number = 101; env.ledger().set(ledger); client.refund(&donator, &project.id, &token.address); - let token_client = token::Client::new(&env, &token.address); - assert_eq!(token_client.balance(&donator), 1_000i128); - assert_eq!(token_client.balance(&client.address), 0i128); + assert_eq!(token.balance(&donator), 1_000i128); + assert_eq!(token.balance(&client.address), 0i128); assert_eq!(client.get_balance(&project.id, &token.address), 0i128); - assert_eq!( - client.get_project(&project.id).status, - ProjectStatus::Expired - ); - - let contract_id = client.address.clone(); - env.as_contract(&contract_id, || { - assert_eq!( - crate::storage::get_donator_balance(&env, project.id, &token.address, &donator), - 0 - ); - }); + assert_eq!(client.get_project(&project.id).status, ProjectStatus::Expired); } #[test] #[should_panic(expected = "HostError: Error(Contract, #21)")] fn test_refund_fails_when_not_expired() { - let (env, client, super_admin) = setup_with_init(); + let (env, client, super_admin) = setup(); let creator = Address::generate(&env); let donator = Address::generate(&env); let token_admin = Address::generate(&env); @@ -117,13 +96,16 @@ fn test_refund_fails_when_not_expired() { token_sac.mint(&donator, &1_000i128); client.deposit(&project.id, &donator, &token.address, &400i128); + let sac = token::StellarAssetClient::new(&env, &token.address); + sac.mint(&donator, &1_000i128); + client.deposit(&project.id, &donator, &token.address, &400i128); client.refund(&donator, &project.id, &token.address); } #[test] #[should_panic(expected = "HostError: Error(Contract, #4)")] fn test_refund_double_refund_fails() { - let (env, client, super_admin) = setup_with_init(); + let (env, client, super_admin) = setup(); let creator = Address::generate(&env); let donator = Address::generate(&env); let token_admin = Address::generate(&env); @@ -148,7 +130,6 @@ fn test_refund_double_refund_fails() { let mut ledger = env.ledger().get(); ledger.timestamp = deadline + 1; - ledger.sequence_number = 101; env.ledger().set(ledger); client.refund(&donator, &project.id, &token.address); @@ -158,7 +139,7 @@ fn test_refund_double_refund_fails() { #[test] #[should_panic(expected = "HostError: Error(Contract, #4)")] fn test_refund_wrong_donator_fails() { - let (env, client, super_admin) = setup_with_init(); + let (env, client, super_admin) = setup(); let creator = Address::generate(&env); let donator = Address::generate(&env); let attacker = Address::generate(&env); @@ -184,7 +165,6 @@ fn test_refund_wrong_donator_fails() { let mut ledger = env.ledger().get(); ledger.timestamp = deadline + 1; - ledger.sequence_number = 101; env.ledger().set(ledger); client.refund(&attacker, &project.id, &token.address); @@ -192,7 +172,7 @@ fn test_refund_wrong_donator_fails() { #[test] fn test_refund_success_after_cancellation() { - let (env, client, super_admin) = setup_with_init(); + let (env, client, super_admin) = setup(); let creator = Address::generate(&env); let donator = Address::generate(&env); let token_admin = Address::generate(&env); @@ -214,31 +194,22 @@ fn test_refund_success_after_cancellation() { let token_sac = token::StellarAssetClient::new(&env, &token.address); token_sac.mint(&donator, &700i128); client.deposit(&project.id, &donator, &token.address, &600i128); - assert_eq!( - client.get_project(&project.id).status, - ProjectStatus::Active - ); + assert_eq!(client.get_project(&project.id).status, ProjectStatus::Active); client.cancel_project(&creator, &project.id); - assert_eq!( - client.get_project(&project.id).status, - ProjectStatus::Cancelled - ); + assert_eq!(client.get_project(&project.id).status, ProjectStatus::Cancelled); client.refund(&donator, &project.id, &token.address); - - let token_client = token::Client::new(&env, &token.address); - assert_eq!(token_client.balance(&donator), 700i128); - assert_eq!(token_client.balance(&client.address), 0i128); - assert_eq!(client.get_balance(&project.id, &token.address), 0i128); + assert_eq!(token.balance(&donator), 700i128); + assert_eq!(token.balance(&client.address), 0i128); } #[test] fn test_refund_distribution_after_cancellation_multi_donor() { - let (env, client, super_admin) = setup_with_init(); + let (env, client, super_admin) = setup(); let creator = Address::generate(&env); - let donator_a = Address::generate(&env); - let donator_b = Address::generate(&env); + let da = Address::generate(&env); + let db = Address::generate(&env); let token_admin = Address::generate(&env); let token = create_token(&env, &token_admin); let deadline = env.ledger().timestamp() + 1_000; @@ -268,17 +239,10 @@ fn test_refund_distribution_after_cancellation_multi_donor() { ); client.cancel_project(&super_admin, &project.id); - assert_eq!( - client.get_project(&project.id).status, - ProjectStatus::Cancelled - ); + client.refund(&da, &project.id, &token.address); + client.refund(&db, &project.id, &token.address); - client.refund(&donator_a, &project.id, &token.address); - client.refund(&donator_b, &project.id, &token.address); - - let token_client = token::Client::new(&env, &token.address); - assert_eq!(token_client.balance(&donator_a), 1_000i128); - assert_eq!(token_client.balance(&donator_b), 1_000i128); - assert_eq!(token_client.balance(&client.address), 0i128); - assert_eq!(client.get_balance(&project.id, &token.address), 0i128); + assert_eq!(token.balance(&da), 1_000i128); + assert_eq!(token.balance(&db), 1_000i128); + assert_eq!(token.balance(&client.address), 0i128); } diff --git a/contracts/pifp_protocol/src/test_utils.rs b/contracts/pifp_protocol/src/test_utils.rs index 2899d56..804054e 100644 --- a/contracts/pifp_protocol/src/test_utils.rs +++ b/contracts/pifp_protocol/src/test_utils.rs @@ -43,7 +43,6 @@ impl TestContext { let env = Env::default(); env.mock_all_auths(); - // Initialize ledger while preserving host's default protocol version let mut ledger = env.ledger().get(); ledger.timestamp = 100_000; ledger.sequence_number = 100; @@ -60,19 +59,11 @@ impl TestContext { client.grant_role(&admin, &oracle, &Role::Oracle); client.grant_role(&admin, &manager, &Role::ProjectManager); - Self { - env, - client, - admin, - oracle, - manager, - } + Self { env, client, admin, oracle, manager } } pub fn create_token(&self) -> (token::Client<'static>, token::StellarAssetClient<'static>) { - let addr = self - .env - .register_stellar_asset_contract_v2(self.admin.clone()); + let addr = self.env.register_stellar_asset_contract_v2(self.admin.clone()); ( token::Client::new(&self.env, &addr.address()), token::StellarAssetClient::new(&self.env, &addr.address()), @@ -82,11 +73,7 @@ impl TestContext { pub fn setup_project( &self, goal: i128, - ) -> ( - Project, - token::Client<'static>, - token::StellarAssetClient<'static>, - ) { + ) -> (Project, token::Client<'static>, token::StellarAssetClient<'static>) { let (token, sac) = self.create_token(); let tokens = Vec::from_array(&self.env, [token.address.clone()]); let project = self.register_project(&tokens, goal); diff --git a/contracts/pifp_protocol/src/test_whitelist.rs b/contracts/pifp_protocol/src/test_whitelist.rs index a00cf1a..6ecb888 100644 --- a/contracts/pifp_protocol/src/test_whitelist.rs +++ b/contracts/pifp_protocol/src/test_whitelist.rs @@ -30,7 +30,6 @@ fn test_whitelist_funding_restricted() { let result = client.try_deposit(&project.id, &donor, &token.address, &500); assert!(result.is_err()); - // Error::NotWhitelisted = 26 } #[test]