diff --git a/.gitignore b/.gitignore index 4e54530..aee07bc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,18 @@ tsconfig.tsbuildinfo target/ +# Rust build output +/target + +# IDE/editor config +.DS_Store +.idea/ +*.swp +*.swo + +# Backup files +**/*.rs.bk + +# Cargo package lock (optional: keep if publishing to crates.io) +Cargo.lock + diff --git a/Cargo.lock b/Cargo.lock index 587d2d2..197add2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", "once_cell", @@ -158,9 +158,9 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" @@ -182,9 +182,9 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.7.3" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" [[package]] name = "block-buffer" @@ -197,9 +197,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.17.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "byteorder" @@ -216,23 +216,23 @@ dependencies = [ "num-bigint", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "cc" -version = "1.2.20" +version = "1.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "d487aa071b5f64da6f19a3e848e3578944b726ee5a4854b82172f02aa876bfdc" dependencies = [ "shlex", ] [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "chrono" @@ -308,7 +308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -335,14 +335,14 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -350,27 +350,27 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -418,7 +418,7 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -439,6 +439,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "ecdsa" version = "0.16.9" @@ -515,9 +521,9 @@ checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" [[package]] name = "ethnum" -version = "1.5.0" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" +checksum = "ca81e6b4777c89fd810c25a4be2b1bd93ea034fbe58e6a75216a34c6b82c539b" [[package]] name = "ff" @@ -554,9 +560,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "js-sys", @@ -602,9 +608,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heapless" @@ -683,12 +689,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "fe4cd85333e22411419a0bcae1297d25e58c9443848b11dc6a86fefe8c78a661" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.4", "serde", ] @@ -746,15 +752,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.172" +version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "libm" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "log" @@ -762,13 +768,18 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "memchr" +version = "2.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" + [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] @@ -781,30 +792,29 @@ checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-derive" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" +checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -851,21 +861,21 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "prettyplease" -version = "0.2.15" +version = "0.2.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +checksum = "061c1221631e079b26479d25bbf2275bfe5917ae8419cd7e34f13bfc2aa7539a" dependencies = [ "proc-macro2", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -925,6 +935,26 @@ dependencies = [ "getrandom", ] +[[package]] +name = "ref-cast" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.104", +] + [[package]] name = "rfc6979" version = "0.4.0" @@ -946,9 +976,9 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" [[package]] name = "ryu" @@ -956,6 +986,30 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1375ba8ef45a6f15d83fa8748f1079428295d403d6ea991d09ab100155fbc06d" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + [[package]] name = "sec1" version = "0.7.3" @@ -977,46 +1031,49 @@ checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.192" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.192" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "serde_with" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6b6f7f2fcb69f747921f79f3926bd1e203fce4fef62c268dd3abfb6d86029aa" +checksum = "f2c45cd61fefa9db6f254525d46e392b852e0e61d9a1fd36e5bd183450a556d5" dependencies = [ "base64 0.22.1", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.10.0", + "schemars 0.9.0", + "schemars 1.0.3", "serde", "serde_derive", "serde_json", @@ -1026,21 +1083,21 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.12.0" +version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d00caa5193a3c8362ac2b73be6b9e768aa5a4b2f721d8f4b339600c3cb51f8e" +checksum = "de90945e6565ce0d9a25098082ed4ee4002e047cb59892c318d66821e14bb30f" dependencies = [ "darling", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -1075,9 +1132,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.15.0" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "soroban-builtin-sdk-macros" @@ -1088,7 +1145,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1168,14 +1225,14 @@ dependencies = [ "serde", "serde_json", "stellar-xdr", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "soroban-ledger-snapshot" -version = "22.0.7" +version = "22.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80bbe59497cb50e81861187e6bd2a2c805df253573d44ed56e7d373f79530758" +checksum = "2826e2c9d364edbb2ea112dc861077c74557bdad0a7a00487969088c7c648169" dependencies = [ "serde", "serde_json", @@ -1187,9 +1244,9 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "22.0.7" +version = "22.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85edd55eb09aa5dd7ba5ab595d2be7ac3f453e90e2f26d704ff26c130f2926f" +checksum = "c7ac27d7573e62b745513fa1be8dab7a09b9676a7f39db97164f1d458a344749" dependencies = [ "arbitrary", "bytes-lit", @@ -1209,9 +1266,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "22.0.7" +version = "22.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a141230aa65006d4b3eeee9d0589172d734a2abfbe15b84670e38e76e200b370" +checksum = "9ef0d7d62b2584696d306b8766728971c7d0731a03a5e047f1fc68722ac8cf0c" dependencies = [ "crate-git-revision", "darling", @@ -1224,14 +1281,14 @@ dependencies = [ "soroban-spec", "soroban-spec-rust", "stellar-xdr", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "soroban-spec" -version = "22.0.7" +version = "22.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b54326e9516b33be99c701b37242b27efb8e66cc1f1eff994b9d9a013a4be136" +checksum = "a4ad0867aec99770ed614fedbec7ac4591791df162ff9e548ab7ebd07cd23a9c" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -1241,9 +1298,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "22.0.7" +version = "22.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f009cab4dfd653bc94a06c5022f1ca9d30e198b0e451f84cf307231563d11de2" +checksum = "aebe31c042adfa2885ec47b67b08fcead8707da80a3fe737eaf2a9ae1a8cfdc3" dependencies = [ "prettyplease", "proc-macro2", @@ -1251,7 +1308,7 @@ dependencies = [ "sha2", "soroban-spec", "stellar-xdr", - "syn 2.0.101", + "syn 2.0.104", "thiserror", ] @@ -1356,9 +1413,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.101" +version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce2b7fc941b3a24138a0a7cf8e858bfc6a992e7978a068a5c760deb0ed43caf" +checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", @@ -1367,22 +1424,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.55" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.55" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1436,9 +1493,9 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" @@ -1462,7 +1519,7 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-shared", ] @@ -1484,7 +1541,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1522,7 +1579,7 @@ version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.10.0", "semver", ] @@ -1537,9 +1594,9 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -1556,7 +1613,7 @@ checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1567,52 +1624,51 @@ checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] name = "windows-link" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "1039dd0d3c310cf05de012d8a39ff557cb0d23087fd44cad61df08fc31907a2f" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "9ecf5b4cc5364572d7f4c329661bcc82724222973f2cab6f050a4e5c22f75181" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] [[package]] @@ -1632,5 +1688,5 @@ checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn 2.0.101", + "syn 2.0.104", ] diff --git a/Cargo.toml b/Cargo.toml index 2461b88..5480d3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,10 +5,10 @@ edition = "2021" [dependencies] heapless = "0.8.0" -soroban-sdk = "22.0.7" +soroban-sdk = "22.0.8" [dev-dependencies] -soroban-sdk = { version = "22.0.7", features = ["testutils"] } +soroban-sdk = { version = "22.0.8", features = ["testutils"] } [features] testutils = ["soroban-sdk/testutils"] diff --git a/README.md b/README.md index 6655ae8..81cd63b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,18 @@ +# 🚀 Stellar Multisig Contract + +A smart contract built using [Soroban](https://soroban.stellar.org/), written in Rust, enabling deterministic wallet address generation from email strings, safe token transfers, and currency conversion logic with platform fee calculations. + +- Deterministic wallet generation via SHA-256 hash of email +- Email → Address mapping (`derive_wallet_address_from_email`) +- Token transfer with balance tracking & logging +- Currency conversion rate computation +- Fee calculation (basis points) +- Input validation for timestamps, balances, and currency support + ## 📧 Contact For inquiries, discussions, or help, feel free to reach out to us: + - 📬 Email: [contact@nexacore.org](mailto:nexacore.org@gmail.com) - 🗣️ Telegram: [https://t.me/NexaFx](https://t.me/+WkWO3kNnA-1mYzVk) - 🐛 Issues: Open an issue for feature requests or bug reports diff --git a/src/conversion.rs b/src/conversion.rs index b743951..9dbf52b 100644 --- a/src/conversion.rs +++ b/src/conversion.rs @@ -165,7 +165,7 @@ impl Currency { #[contractimpl] impl ConversionContract { /// Initialize the conversion contract - pub fn initialize( + pub fn initialize_conversion( env: Env, admin: Address, fee_bps: u32, @@ -434,7 +434,7 @@ impl ConversionContract { } /// Get platform configuration - pub fn get_config(env: Env) -> PlatformConfig { + pub fn get_conversion_config(env: Env) -> PlatformConfig { env.storage() .instance() .get(&DataKey::Config) diff --git a/src/email_to_wallet.rs b/src/email_to_wallet.rs new file mode 100644 index 0000000..59cdddb --- /dev/null +++ b/src/email_to_wallet.rs @@ -0,0 +1,52 @@ +use crate::errors::AppError; +use crate::utils::derive_wallet_address_from_email; +use soroban_sdk::{contract, contractimpl, Address, Env, IntoVal, String}; + +pub struct EmailToWalletContract; + +impl EmailToWalletContract { + pub fn get_wallet_from_email(env: Env, email: String) -> Result { + derive_wallet_address_from_email(&env, &email) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use soroban_sdk::{testutils::Address as _, Env, String}; + + #[test] + fn same_email_same_address() { + let env = Env::default(); + let email = String::from_slice(&env, "user@example.com"); + + let addr1 = derive_wallet_address_from_email(&env, &email).expect("valid email"); + let addr2 = derive_wallet_address_from_email(&env, &email).expect("valid email"); + + assert_eq!(addr1, addr2, "Same email should produce same address"); + } + + #[test] + fn different_emails_different_addresses() { + let env = Env::default(); + let email1 = String::from_slice(&env, "alice@example.com"); + let email2 = String::from_slice(&env, "bob@example.com"); + + let addr1 = derive_wallet_address_from_email(&env, &email1).expect("valid email"); + let addr2 = derive_wallet_address_from_email(&env, &email2).expect("valid email"); + + assert_ne!( + addr1, addr2, + "Different emails should produce different addresses" + ); + } + + #[test] + fn empty_email_should_fail() { + let env = Env::default(); + let empty_email = String::from_slice(&env, ""); + + let result = derive_wallet_address_from_email(&env, &empty_email); + assert!(matches!(result, Err(AppError::InvalidAddress))); + } +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..59b4659 --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,14 @@ +use soroban_sdk::contracttype; + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum AppError { + InvalidAmount, + InvalidAddress, + InvalidTimestamp, + InsufficientBalance, + UnsupportedCurrency, + RateExpired, + ConversionLimitExceeded, + Unauthorized, +} diff --git a/src/escrow.rs b/src/escrow.rs index 3ec0837..638aea0 100644 --- a/src/escrow.rs +++ b/src/escrow.rs @@ -1,5 +1,3 @@ -#![no_std] - use core::fmt::Write; use heapless::String as HString; use soroban_sdk::{ @@ -327,4 +325,4 @@ impl EscrowContract { } escrows } -} \ No newline at end of file +} diff --git a/src/event.rs b/src/event.rs index 6691fcf..ef14e23 100644 --- a/src/event.rs +++ b/src/event.rs @@ -1,5 +1,3 @@ -#![no_std] - use soroban_sdk::{contracttype, symbol_short, Address, Bytes, BytesN, Env, Symbol, Vec}; // Event topics for efficient filtering and indexing @@ -331,4 +329,4 @@ impl EventQuery { pub fn system_events_filter() -> Symbol { SYSTEM_TOPIC } -} \ No newline at end of file +} diff --git a/src/fees.rs b/src/fees.rs index 1bf90f6..a2f6285 100644 --- a/src/fees.rs +++ b/src/fees.rs @@ -1,6 +1,4 @@ -use soroban_sdk::{ - contract, contractimpl, contracttype, symbol_short, token, Address, Env, Error -}; +use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, token, Address, Env, Error}; const MAX_BPS: u32 = 10000; // Represents 100% @@ -24,8 +22,8 @@ pub struct FeeDistributionConfig { pub admin: Address, pub treasury_address: Address, pub reward_pool_address: Address, - pub treasury_bps: u32, // Basis points for treasury (e.g., 5000 for 50%) - pub reward_pool_bps: u32, // Basis points for reward pool (e.g., 5000 for 50%) + pub treasury_bps: u32, // Basis points for treasury (e.g., 5000 for 50%) + pub reward_pool_bps: u32, // Basis points for reward pool (e.g., 5000 for 50%) } // To track total distributed amounts per token by this contract @@ -51,7 +49,7 @@ pub struct FeeSplitterContract; #[contractimpl] impl FeeSplitterContract { - pub fn initialize( + pub fn initialize_fees( env: Env, admin: Address, treasury_address: Address, @@ -65,7 +63,10 @@ impl FeeSplitterContract { admin.require_auth(); - if treasury_bps > MAX_BPS || reward_pool_bps > MAX_BPS || (treasury_bps + reward_pool_bps) > MAX_BPS { + if treasury_bps > MAX_BPS + || reward_pool_bps > MAX_BPS + || (treasury_bps + reward_pool_bps) > MAX_BPS + { return Err(Error::from_contract_error(ERR_INVALID_BPS_CONFIG)); } @@ -80,14 +81,17 @@ impl FeeSplitterContract { Ok(()) } - pub fn update_config( + pub fn update_fees_config( env: Env, treasury_address: Option
, reward_pool_address: Option
, treasury_bps: Option, reward_pool_bps: Option, ) -> Result { - let mut config: FeeDistributionConfig = env.storage().instance().get(&DataKey::Config) + let mut config: FeeDistributionConfig = env + .storage() + .instance() + .get(&DataKey::Config) .ok_or_else(|| Error::from_contract_error(ERR_NOT_INITIALIZED))?; config.admin.require_auth(); @@ -105,7 +109,10 @@ impl FeeSplitterContract { config.reward_pool_bps = bps; } - if config.treasury_bps > MAX_BPS || config.reward_pool_bps > MAX_BPS || (config.treasury_bps + config.reward_pool_bps) > MAX_BPS { + if config.treasury_bps > MAX_BPS + || config.reward_pool_bps > MAX_BPS + || (config.treasury_bps + config.reward_pool_bps) > MAX_BPS + { return Err(Error::from_contract_error(ERR_INVALID_BPS)); } @@ -113,8 +120,10 @@ impl FeeSplitterContract { Ok(config.clone()) } - pub fn get_config(env: Env) -> Result { - env.storage().instance().get(&DataKey::Config) + pub fn get_fees_config(env: Env) -> Result { + env.storage() + .instance() + .get(&DataKey::Config) .ok_or_else(|| Error::from_contract_error(ERR_NOT_INITIALIZED)) } @@ -135,28 +144,41 @@ impl FeeSplitterContract { // This ensures that only authorized contracts can trigger fee distribution from their balance. fee_collector_contract.require_auth(); - let config: FeeDistributionConfig = env.storage().instance().get(&DataKey::Config) + let config: FeeDistributionConfig = env + .storage() + .instance() + .get(&DataKey::Config) .ok_or_else(|| Error::from_contract_error(ERR_NOT_INITIALIZED))?; let token_client = token::Client::new(&env, &fee_token); - let treasury_amount = (total_fee_amount * i128::from(config.treasury_bps)) / i128::from(MAX_BPS); - let reward_pool_amount = (total_fee_amount * i128::from(config.reward_pool_bps)) / i128::from(MAX_BPS); - + let treasury_amount = + (total_fee_amount * i128::from(config.treasury_bps)) / i128::from(MAX_BPS); + let reward_pool_amount = + (total_fee_amount * i128::from(config.reward_pool_bps)) / i128::from(MAX_BPS); + // Ensure the sum of distributed amounts does not exceed the total fee. // Any dust/remainder from bps calculation will remain with the fee_collector_contract. if treasury_amount + reward_pool_amount > total_fee_amount { - return Err(Error::from_contract_error(ERR_FEE_DISTRIBUTION_FAILED)); + return Err(Error::from_contract_error(ERR_FEE_DISTRIBUTION_FAILED)); } if treasury_amount > 0 { - token_client.transfer(&fee_collector_contract, &config.treasury_address, &treasury_amount); + token_client.transfer( + &fee_collector_contract, + &config.treasury_address, + &treasury_amount, + ); } if reward_pool_amount > 0 { - token_client.transfer(&fee_collector_contract, &config.reward_pool_address, &reward_pool_amount); + token_client.transfer( + &fee_collector_contract, + &config.reward_pool_address, + &reward_pool_amount, + ); } - + // Emit event let fee_token_clone = fee_token.clone(); env.events().publish( @@ -173,7 +195,8 @@ impl FeeSplitterContract { // Update total distributed amounts if tracking within this contract let key = DataKey::TotalDistributed(fee_token_clone); - let mut totals: TokenDistributionTotals = env.storage().instance().get(&key).unwrap_or_default(); + let mut totals: TokenDistributionTotals = + env.storage().instance().get(&key).unwrap_or_default(); totals.to_treasury += treasury_amount; totals.to_reward_pool += reward_pool_amount; env.storage().instance().set(&key, &totals); @@ -183,6 +206,9 @@ impl FeeSplitterContract { // Function to get total distributed amounts for a token pub fn get_total_distributed(env: Env, token: Address) -> TokenDistributionTotals { - env.storage().instance().get(&DataKey::TotalDistributed(token)).unwrap_or_default() + env.storage() + .instance() + .get(&DataKey::TotalDistributed(token)) + .unwrap_or_default() } } diff --git a/src/lib.rs b/src/lib.rs index 165877b..5f9e2a5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,18 +1,21 @@ #![no_std] pub mod conversion; +pub mod email_to_wallet; +pub mod errors; pub mod escrow; pub mod event; pub mod events; +pub mod fees; pub mod multisig; pub mod token; -pub mod fees; pub mod utils; +pub use crate::email_to_wallet::EmailToWalletContract; pub use conversion::ConversionContract; pub use conversion::Currency; pub use escrow::EscrowContract; pub use event::*; pub use multisig::MultiSigContract; pub use token::TokenContract; -pub use utils::*; \ No newline at end of file +pub use utils::*; diff --git a/src/multisig.rs b/src/multisig.rs index 56bd3fd..33f02be 100644 --- a/src/multisig.rs +++ b/src/multisig.rs @@ -1,129 +1,127 @@ -#![no_std] - -use soroban_sdk::{ - contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, Symbol, Vec, -}; - -#[contracttype] -#[derive(Clone, Debug)] -pub struct MultiSigConfig { - signers: Vec
, - threshold: u32, - nonce: u32, -} - -#[contracttype] -#[derive(Clone)] -pub struct Transaction { - operation: BytesN<32>, - timestamp: u64, - nonce: u32, -} - -#[contract] -pub struct MultiSigContract; - -const CONFIG_KEY: Symbol = symbol_short!("CONFIG"); - -#[contractimpl] -impl MultiSigContract { - pub fn initialize(env: Env, signers: Vec
, threshold: u32) -> MultiSigConfig { - if threshold == 0 || threshold > signers.len() as u32 { - panic!("Invalid threshold"); - } - - let config = MultiSigConfig { - signers, - threshold, - nonce: 0, - }; - - env.storage().instance().set(&CONFIG_KEY, &config); - config - } - - pub fn propose_transaction( - env: Env, - operation: BytesN<32>, - signatures: Vec>, - proposer: Address, - ) -> bool { - let mut config: MultiSigConfig = env.storage().instance().get(&CONFIG_KEY).unwrap(); - let timestamp = env.ledger().timestamp(); - - let _transaction = Transaction { - operation: operation.clone(), - timestamp, - nonce: config.nonce, - }; - - let valid_signatures = signatures.len() as u32; - - let event = crate::event::DeFiEvent::MultisigTransactionProposed( - crate::event::MultisigTransactionProposedData { - nonce: config.nonce, - proposer: proposer.clone(), - operation_hash: operation.clone(), - threshold: config.threshold, - current_signatures: valid_signatures, - proposed_at: env.ledger().timestamp(), - }, - ); - crate::event::EventEmitter::emit_event(&env, crate::event::MULTISIG_TOPIC, event); - - if valid_signatures >= config.threshold { - let exec_event = crate::event::DeFiEvent::MultisigTransactionExecuted( - crate::event::MultisigTransactionExecutedData { - nonce: config.nonce, - signers: config.signers.clone(), - operation_hash: operation, - executed_at: env.ledger().timestamp(), - }, - ); - crate::event::EventEmitter::emit_event(&env, crate::event::MULTISIG_TOPIC, exec_event); - - config.nonce += 1; - env.storage().instance().set(&CONFIG_KEY, &config); - - true - } else { - false - } - } - - pub fn get_config(env: Env) -> MultiSigConfig { - env.storage().instance().get(&CONFIG_KEY).unwrap() - } - - pub fn update_config( - env: Env, - new_signers: Vec
, - new_threshold: u32, - proposer: Address, - ) -> MultiSigConfig { - if new_threshold == 0 || new_threshold > new_signers.len() as u32 { - panic!("Invalid threshold"); - } - - let old_config: MultiSigConfig = env.storage().instance().get(&CONFIG_KEY).unwrap(); - - let new_config = MultiSigConfig { - signers: new_signers.clone(), - threshold: new_threshold, - nonce: old_config.nonce, - }; - - let event = crate::event::DeFiEvent::MultisigConfigUpdated( - crate::event::MultisigConfigUpdatedData { - old_signers: old_config.signers, - new_signers, - old_threshold: old_config.threshold, - new_threshold, - updated_at: env.ledger().timestamp(), - }, - ); - crate::event::EventEmitter::emit_event(&env, crate::event::MULTISIG_TOPIC, event); - env.storage().instance().set(&CONFIG_KEY, &new_config); - new_config - } -} \ No newline at end of file +use soroban_sdk::{ + contract, contractimpl, contracttype, symbol_short, Address, BytesN, Env, Symbol, Vec, +}; + +#[contracttype] +#[derive(Clone, Debug)] +pub struct MultiSigConfig { + signers: Vec
, + threshold: u32, + nonce: u32, +} + +#[contracttype] +#[derive(Clone)] +pub struct Transaction { + operation: BytesN<32>, + timestamp: u64, + nonce: u32, +} + +#[contract] +pub struct MultiSigContract; + +const CONFIG_KEY: Symbol = symbol_short!("CONFIG"); + +#[contractimpl] +impl MultiSigContract { + pub fn initialize_multisig(env: Env, signers: Vec
, threshold: u32) -> MultiSigConfig { + if threshold == 0 || threshold > signers.len() as u32 { + panic!("Invalid threshold"); + } + + let config = MultiSigConfig { + signers, + threshold, + nonce: 0, + }; + + env.storage().instance().set(&CONFIG_KEY, &config); + config + } + + pub fn propose_transaction( + env: Env, + operation: BytesN<32>, + signatures: Vec>, + proposer: Address, + ) -> bool { + let mut config: MultiSigConfig = env.storage().instance().get(&CONFIG_KEY).unwrap(); + let timestamp = env.ledger().timestamp(); + + let _transaction = Transaction { + operation: operation.clone(), + timestamp, + nonce: config.nonce, + }; + + let valid_signatures = signatures.len() as u32; + + let event = crate::event::DeFiEvent::MultisigTransactionProposed( + crate::event::MultisigTransactionProposedData { + nonce: config.nonce, + proposer: proposer.clone(), + operation_hash: operation.clone(), + threshold: config.threshold, + current_signatures: valid_signatures, + proposed_at: env.ledger().timestamp(), + }, + ); + crate::event::EventEmitter::emit_event(&env, crate::event::MULTISIG_TOPIC, event); + + if valid_signatures >= config.threshold { + let exec_event = crate::event::DeFiEvent::MultisigTransactionExecuted( + crate::event::MultisigTransactionExecutedData { + nonce: config.nonce, + signers: config.signers.clone(), + operation_hash: operation, + executed_at: env.ledger().timestamp(), + }, + ); + crate::event::EventEmitter::emit_event(&env, crate::event::MULTISIG_TOPIC, exec_event); + + config.nonce += 1; + env.storage().instance().set(&CONFIG_KEY, &config); + + true + } else { + false + } + } + + pub fn get_multisig_config(env: Env) -> MultiSigConfig { + env.storage().instance().get(&CONFIG_KEY).unwrap() + } + + pub fn update_multisig_config( + env: Env, + new_signers: Vec
, + new_threshold: u32, + proposer: Address, + ) -> MultiSigConfig { + if new_threshold == 0 || new_threshold > new_signers.len() as u32 { + panic!("Invalid threshold"); + } + + let old_config: MultiSigConfig = env.storage().instance().get(&CONFIG_KEY).unwrap(); + + let new_config = MultiSigConfig { + signers: new_signers.clone(), + threshold: new_threshold, + nonce: old_config.nonce, + }; + + let event = crate::event::DeFiEvent::MultisigConfigUpdated( + crate::event::MultisigConfigUpdatedData { + old_signers: old_config.signers, + new_signers, + old_threshold: old_config.threshold, + new_threshold, + updated_at: env.ledger().timestamp(), + }, + ); + crate::event::EventEmitter::emit_event(&env, crate::event::MULTISIG_TOPIC, event); + env.storage().instance().set(&CONFIG_KEY, &new_config); + new_config + } +} diff --git a/src/schema.rs b/src/schema.rs index 4c9cb4e..1ca52f7 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,4 +1,4 @@ -#![no_std] + use soroban_sdk::{contracttype, Address, Symbol, Vec, Map, Env}; /// Represents a swap offer in the contract's storage diff --git a/src/swap.rs b/src/swap.rs index 77eb7d9..7e00071 100644 --- a/src/swap.rs +++ b/src/swap.rs @@ -1,4 +1,4 @@ -#![no_std] + use soroban_sdk::{ contract, contractimpl, contracttype, token, Address, Env, IntoVal, Symbol, Vec, Map, log, events @@ -27,7 +27,7 @@ pub struct SwapContract; impl SwapTrait for SwapContract { /// The initial contract configuration - fn initialize(env: Env, admin: Address) -> SwapConfig { + fn initialize_swap(env: Env, admin: Address) -> SwapConfig { // Verify the contract is not already initialized if env.storage().has(&DataKey::Config) { panic!("Contract already initialized"); @@ -279,7 +279,7 @@ impl SwapTrait for SwapContract { .ok_or_else(|| panic!("Offer not found")).unwrap() } - fn get_config(env: Env) -> SwapConfig { + fn get_swap_config(env: Env) -> SwapConfig { env.storage().get(&DataKey::Config) .ok_or_else(|| panic!("Contract not initialized")).unwrap() } diff --git a/src/token.rs b/src/token.rs index 5f45282..9b61934 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,5 +1,3 @@ -#![no_std] - use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, Symbol}; #[contracttype] @@ -24,7 +22,7 @@ pub struct TokenContract; #[contractimpl] impl TokenContract { - pub fn initialize( + pub fn initialize_token( env: Env, admin: Address, name: Symbol, @@ -124,7 +122,7 @@ impl TokenContract { .unwrap_or(Balance { amount: 0 }); balance.amount } - pub fn get_config(env: Env) -> TokenConfig { + pub fn get_token_config(env: Env) -> TokenConfig { env.storage().instance().get(&CONFIG_KEY).unwrap() } -} \ No newline at end of file +} diff --git a/src/utils.rs b/src/utils.rs index 19fda1e..c39ad58 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,43 +1,31 @@ -#![no_std] - -use soroban_sdk::{contracttype, log, token, Address, Env, Vec}; - use crate::conversion::Currency; - -/// Custom error types for better error handling -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum ConversionError { - InvalidAmount, - InvalidAddress, - InvalidTimestamp, - InsufficientBalance, - UnsupportedCurrency, - RateExpired, - ConversionLimitExceeded, - Unauthorized, -} +use crate::errors::AppError; +use soroban_sdk::vec; +use soroban_sdk::xdr::FromXdr; +use soroban_sdk::{ + contracttype, log, token, xdr::ScAddressType, Address, Bytes, BytesN, Env, String, +}; /// Validates that an amount is positive -pub fn validate_positive_amount(amount: i128) -> Result<(), ConversionError> { +pub fn validate_positive_amount(amount: i128) -> Result<(), AppError> { if amount <= 0 { - return Err(ConversionError::InvalidAmount); + return Err(AppError::InvalidAmount); } Ok(()) } /// Validates that a timestamp is in the future -pub fn validate_future_timestamp(env: &Env, timestamp: u64) -> Result<(), ConversionError> { +pub fn validate_future_timestamp(env: &Env, timestamp: u64) -> Result<(), AppError> { if timestamp <= env.ledger().timestamp() { - return Err(ConversionError::InvalidTimestamp); + return Err(AppError::InvalidTimestamp); } Ok(()) } /// Validates an address -pub fn validate_address(env: &Env, address: &Address) -> Result<(), ConversionError> { +pub fn validate_address(env: &Env, address: &Address) -> Result<(), AppError> { if address.to_string().is_empty() { - return Err(ConversionError::InvalidAddress); + return Err(AppError::InvalidAddress); } Ok(()) } @@ -49,9 +37,9 @@ pub fn transfer_tokens( from: &Address, to: &Address, amount: &i128, -) -> Result<(), ConversionError> { +) -> Result<(), AppError> { if *amount <= 0 { - return Err(ConversionError::InvalidAmount); + return Err(AppError::InvalidAmount); } // Get balances before transfer @@ -84,12 +72,9 @@ pub fn get_token_balance(env: &Env, token_address: &Address, account: &Address) } /// Computes exchange rate between two token amounts -pub fn compute_exchange_rate( - offer_amount: i128, - request_amount: i128, -) -> Result { +pub fn compute_exchange_rate(offer_amount: i128, request_amount: i128) -> Result { if offer_amount <= 0 || request_amount <= 0 { - return Err(ConversionError::InvalidAmount); + return Err(AppError::InvalidAmount); } // Return rate scaled by 10^8 for precision @@ -101,22 +86,19 @@ pub fn compute_exchange_rate( pub fn validate_currency_support( currency: &Currency, supported_currencies: &soroban_sdk::Vec, -) -> Result<(), ConversionError> { +) -> Result<(), AppError> { for supported in supported_currencies.iter() { if supported == *currency { return Ok(()); } } - Err(ConversionError::UnsupportedCurrency) + Err(AppError::UnsupportedCurrency) } /// Checks if user has sufficient balance for conversion -pub fn check_sufficient_balance( - user_balance: i128, - required_amount: i128, -) -> Result<(), ConversionError> { +pub fn check_sufficient_balance(user_balance: i128, required_amount: i128) -> Result<(), AppError> { if user_balance < required_amount { - return Err(ConversionError::InsufficientBalance); + return Err(AppError::InsufficientBalance); } Ok(()) } @@ -126,9 +108,9 @@ pub fn validate_conversion_limits( amount: i128, min_amount: i128, max_amount: i128, -) -> Result<(), ConversionError> { +) -> Result<(), AppError> { if amount < min_amount || amount > max_amount { - return Err(ConversionError::ConversionLimitExceeded); + return Err(AppError::ConversionLimitExceeded); } Ok(()) } @@ -176,9 +158,9 @@ pub fn validate_rate_lock_duration( env: &Env, duration: u64, max_duration: u64, -) -> Result<(), ConversionError> { +) -> Result<(), AppError> { if duration > max_duration { - return Err(ConversionError::InvalidTimestamp); + return Err(AppError::InvalidTimestamp); } Ok(()) } @@ -196,10 +178,10 @@ pub fn update_balance_atomically( current_balance: i128, amount_change: i128, is_debit: bool, -) -> Result { +) -> Result { let new_balance = if is_debit { if current_balance < amount_change { - return Err(ConversionError::InsufficientBalance); + return Err(AppError::InsufficientBalance); } current_balance - amount_change } else { @@ -221,4 +203,34 @@ pub fn validate_token_contract(env: &Env, _token_address: &Address) -> bool { pub fn validate_token_balance(env: &Env, _token_address: &Address, _amount: i128) -> bool { true -} \ No newline at end of file +} + +// derive wallet address from email + +pub fn derive_wallet_address_from_email(env: &Env, email: &String) -> Result { + if email.len() == 0 { + return Err(AppError::InvalidAddress); + } + + let len = email.len() as usize; + if len > 256 { + return Err(AppError::InvalidAddress); + } + + let mut buf = [0u8; 256]; + email.copy_into_slice(&mut buf[..len]); + let email_bytes = Bytes::from_slice(env, &buf[..len]); + + let hash: BytesN<32> = env.crypto().sha256(&email_bytes).into(); + + let mut xdr: [u8; 40] = [0; 40]; + xdr[3] = 18; + xdr[7] = 1; + let slice: &mut [u8; 32] = (&mut xdr[8..40]).try_into().unwrap(); + hash.copy_into_slice(slice); + + let addr_bytes = Bytes::from_slice(env, &xdr); + let address = Address::from_xdr(env, &addr_bytes).map_err(|_| AppError::InvalidAddress)?; + + Ok(address) +} diff --git a/test_snapshots/test_btc_to_usd_conversion.1.json b/test_snapshots/test_btc_to_usd_conversion.1.json index 49f8269..5f79b6b 100644 --- a/test_snapshots/test_btc_to_usd_conversion.1.json +++ b/test_snapshots/test_btc_to_usd_conversion.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_different_fee_rates.1.json b/test_snapshots/test_different_fee_rates.1.json index 3c6d2c2..a086609 100644 --- a/test_snapshots/test_different_fee_rates.1.json +++ b/test_snapshots/test_different_fee_rates.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_eth_to_usd_conversion.1.json b/test_snapshots/test_eth_to_usd_conversion.1.json index c4aadb4..500b531 100644 --- a/test_snapshots/test_eth_to_usd_conversion.1.json +++ b/test_snapshots/test_eth_to_usd_conversion.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_fee_calculation_accuracy.1.json b/test_snapshots/test_fee_calculation_accuracy.1.json index 3f29ac6..2ca0e72 100644 --- a/test_snapshots/test_fee_calculation_accuracy.1.json +++ b/test_snapshots/test_fee_calculation_accuracy.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_fee_distribution_across_multiple_conversions.1.json b/test_snapshots/test_fee_distribution_across_multiple_conversions.1.json index 7303532..f1a42ed 100644 --- a/test_snapshots/test_fee_distribution_across_multiple_conversions.1.json +++ b/test_snapshots/test_fee_distribution_across_multiple_conversions.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_fee_precision_with_small_amounts.1.json b/test_snapshots/test_fee_precision_with_small_amounts.1.json index 241bc59..32dd88f 100644 --- a/test_snapshots/test_fee_precision_with_small_amounts.1.json +++ b/test_snapshots/test_fee_precision_with_small_amounts.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_gbp_to_usd_conversion.1.json b/test_snapshots/test_gbp_to_usd_conversion.1.json index 55d9531..f205952 100644 --- a/test_snapshots/test_gbp_to_usd_conversion.1.json +++ b/test_snapshots/test_gbp_to_usd_conversion.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_insufficient_balance_failure.1.json b/test_snapshots/test_insufficient_balance_failure.1.json index 7f97225..754a257 100644 --- a/test_snapshots/test_insufficient_balance_failure.1.json +++ b/test_snapshots/test_insufficient_balance_failure.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_multiple_conversions_different_amounts.1.json b/test_snapshots/test_multiple_conversions_different_amounts.1.json index 9b74916..8fd0069 100644 --- a/test_snapshots/test_multiple_conversions_different_amounts.1.json +++ b/test_snapshots/test_multiple_conversions_different_amounts.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_ngn_to_usd_conversion.1.json b/test_snapshots/test_ngn_to_usd_conversion.1.json index 0deac47..df15074 100644 --- a/test_snapshots/test_ngn_to_usd_conversion.1.json +++ b/test_snapshots/test_ngn_to_usd_conversion.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_partial_balance_insufficient.1.json b/test_snapshots/test_partial_balance_insufficient.1.json index 7868ee3..2050df7 100644 --- a/test_snapshots/test_partial_balance_insufficient.1.json +++ b/test_snapshots/test_partial_balance_insufficient.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_usd_to_eur_conversion.1.json b/test_snapshots/test_usd_to_eur_conversion.1.json index e00f23e..c8db5aa 100644 --- a/test_snapshots/test_usd_to_eur_conversion.1.json +++ b/test_snapshots/test_usd_to_eur_conversion.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_usd_to_ngn_conversion.1.json b/test_snapshots/test_usd_to_ngn_conversion.1.json index af14ddd..a28c844 100644 --- a/test_snapshots/test_usd_to_ngn_conversion.1.json +++ b/test_snapshots/test_usd_to_ngn_conversion.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_zero_balance_conversion_failure.1.json b/test_snapshots/test_zero_balance_conversion_failure.1.json index 7f97225..754a257 100644 --- a/test_snapshots/test_zero_balance_conversion_failure.1.json +++ b/test_snapshots/test_zero_balance_conversion_failure.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/test_snapshots/test_zero_fee_configuration.1.json b/test_snapshots/test_zero_fee_configuration.1.json index 60f89bd..1c87a6e 100644 --- a/test_snapshots/test_zero_fee_configuration.1.json +++ b/test_snapshots/test_zero_fee_configuration.1.json @@ -12,7 +12,7 @@ "function": { "contract_fn": { "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", + "function_name": "initialize_conversion", "args": [ { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" diff --git a/tests/test_conversion.rs b/tests/test_conversion.rs index dda6ff1..bc78491 100644 --- a/tests/test_conversion.rs +++ b/tests/test_conversion.rs @@ -22,7 +22,7 @@ fn setup_contract( _admin: &Address, fee_collector: &Address, ) { - client.initialize( + client.initialize_conversion( _admin, &50u32, // 0.5% fee fee_collector, @@ -204,7 +204,7 @@ fn test_eth_to_usd_conversion() { let (env, client, admin, fee_collector) = create_test_env(); env.mock_all_auths(); // Increase max_amount for this test to allow 1 ETH in wei - client.initialize( + client.initialize_conversion( &admin, &50u32, // 0.5% fee &fee_collector, @@ -268,7 +268,7 @@ fn test_different_fee_rates() { env.mock_all_auths(); // Initialize with higher fee (1%) - client.initialize( + client.initialize_conversion( &admin, &100u32, // 1% fee &fee_collector, @@ -379,7 +379,7 @@ fn test_zero_fee_configuration() { env.mock_all_auths(); // Initialize with zero fee - client.initialize( + client.initialize_conversion( &admin, &0u32, // 0% fee &fee_collector,