diff --git a/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json b/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json index 6b6878d..f44a302 100644 --- a/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json +++ b/.sqlx/query-957f4145c738e0c81e9f0d1c6ea312966f50f802f8b1365b2fa20330277eb4ac.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json b/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json index 8a9b19c..fba945e 100644 --- a/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json +++ b/.sqlx/query-b07560cf0c54dddc13cb6f941fa45d536b2e7d85e0e86e22fa911156d9704168.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json b/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json index 42aa2c4..d52ce84 100644 --- a/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json +++ b/.sqlx/query-b6e0976bf2960fb60ed91fe0cded10324cc45c67d8e5d28950dfafa7e98880da.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } @@ -103,7 +104,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json b/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json index b818959..3eab25e 100644 --- a/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json +++ b/.sqlx/query-bea373addcff988470868b63ed5630b82402146a833bb310ae665a86f0f8fa52.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json b/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json index 00f5689..a5da8f1 100644 --- a/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json +++ b/.sqlx/query-cd402400d4b0c62f598ac78c03bcd37bc5e6189b89d06a5920714433d0dc2574.json @@ -49,7 +49,8 @@ "HighloadWallet", "Wallet", "SafeMultisig", - "EverWallet" + "EverWallet", + "WalletV5R1" ] } } diff --git a/Cargo.lock b/Cargo.lock index 506a970..9f2c2c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -22,17 +22,6 @@ dependencies = [ "generic-array", ] -[[package]] -name = "ahash" -version = "0.7.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" -dependencies = [ - "getrandom 0.2.16", - "once_cell", - "version_check", -] - [[package]] name = "ahash" version = "0.8.12" @@ -57,9 +46,9 @@ dependencies = [ [[package]] name = "aide" -version = "0.13.5" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5678d2978845ddb4bd736a026f467dd652d831e9e6254b0e41b07f7ee7523309" +checksum = "6966317188cdfe54c58c0900a195d021294afb3ece9b7073d09e4018dbb1e3a2" dependencies = [ "aide-macros", "axum", @@ -72,7 +61,7 @@ dependencies = [ "serde", "serde_json", "serde_qs", - "thiserror 1.0.69", + "thiserror 2.0.18", "tower-layer", "tower-service", "tracing", @@ -80,14 +69,14 @@ dependencies = [ [[package]] name = "aide-macros" -version = "0.7.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0487f8598afe49e6bc950a613a678bd962c4a6f431022ded62643c8b990301a" +checksum = "9f2a08f14808f3c46f3e3004b727bace64af44c3c5996d0480a14d3852b1b25a" dependencies = [ "darling 0.20.11", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -157,24 +146,28 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "arc-swap" -version = "1.7.1" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" +checksum = "f9f3647c145568cec02c42054e07bdf9a5a698e15b466fb2341bfc393cd24aa5" +dependencies = [ + "rustversion", +] [[package]] name = "argon2" -version = "0.4.1" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4ce4441f99dbd377ca8a8f57b698c44d0d6e712d8329b5040da5a64aa1ce73" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", + "cpufeatures 0.2.17", "password-hash", ] @@ -198,7 +191,7 @@ checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -224,9 +217,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "aws-lc-rs" -version = "1.15.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b5ce75405893cd713f9ab8e297d8e438f624dde7d706108285f7e17a25a180f" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" dependencies = [ "aws-lc-sys", "zeroize", @@ -234,9 +227,9 @@ dependencies = [ [[package]] name = "aws-lc-sys" -version = "0.34.0" +version = "0.37.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "179c3777a8b5e70e90ea426114ffc565b2c1a9f82f6c4a0c5a34aa6ef5e781b6" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" dependencies = [ "cc", "cmake", @@ -246,14 +239,14 @@ dependencies = [ [[package]] name = "axum" -version = "0.7.9" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +checksum = "8b52af3cb4058c895d37317bb27508dccc8e5f2d39454016b297bf4a400597b8" dependencies = [ - "async-trait", "axum-core", "axum-macros", "bytes", + "form_urlencoded", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -267,8 +260,7 @@ dependencies = [ "multer", "percent-encoding", "pin-project-lite", - "rustversion", - "serde", + "serde_core", "serde_json", "serde_path_to_error", "serde_urlencoded", @@ -282,19 +274,17 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.4.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ - "async-trait", "bytes", - "futures-util", + "futures-core", "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", "pin-project-lite", - "rustversion", "sync_wrapper", "tower-layer", "tower-service", @@ -303,36 +293,35 @@ dependencies = [ [[package]] name = "axum-extra" -version = "0.9.6" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c794b30c904f0a1c2fb7740f7df7f7972dfaa14ef6f57cb6178dc63e5dca2f04" +checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ "axum", "axum-core", "bytes", - "fastrand", "futures-util", "http 1.4.0", "http-body 1.0.1", "http-body-util", "mime", - "multer", "pin-project-lite", - "serde", - "tower", + "rustversion", + "serde_core", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-macros" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce" +checksum = "604fde5e028fea851ce1d8570bbdc034bec850d157f7569d10f347d06808c05c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -355,15 +344,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bigdecimal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "560f42649de9fa436b73517378a147ec21f6c997a546581df4b4b31677828934" +checksum = "4d6867f1565b3aad85681f1015055b087fcfd840d6aeee6eee7f2da317603695" dependencies = [ "autocfg", "libm", @@ -388,14 +377,14 @@ dependencies = [ "regex", "rustc-hash 2.1.1", "shlex", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" dependencies = [ "serde_core", ] @@ -411,15 +400,16 @@ dependencies = [ [[package]] name = "blake3" -version = "1.8.2" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3888aaa89e4b2a40fca9848e400f6a658a5a3978de7be858e209cafa8be9a4a0" +checksum = "2468ef7d57b3fb7e16b576e8377cdbde2320c60e1491e961d11da40fc4f02a2d" dependencies = [ "arrayref", "arrayvec", "cc", "cfg-if", "constant_time_eq", + "cpufeatures 0.2.17", "memmap2", "rayon-core", ] @@ -444,9 +434,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "bumpalo-herd" @@ -465,9 +455,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.11.0" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" dependencies = [ "serde", ] @@ -497,7 +487,7 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b241c1e0296981e531864393cabeccba83061a6e79e5556cc267eca5cf2f0d92" dependencies = [ - "ahash 0.8.12", + "ahash", "blake3", "bytes", "hex", @@ -506,7 +496,7 @@ dependencies = [ "serde", "serde_json", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -521,9 +511,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.48" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c481bdbf0ed3b892f6f806287d72acd515b352a4ec27a208489b8c1bc839633a" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "jobserver", @@ -531,6 +521,12 @@ dependencies = [ "shlex", ] +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + [[package]] name = "cexpr" version = "0.6.0" @@ -560,7 +556,18 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher", - "cpufeatures", + "cpufeatures 0.2.17", +] + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", ] [[package]] @@ -570,7 +577,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", + "chacha20 0.9.1", "cipher", "poly1305", "zeroize", @@ -578,9 +585,9 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.42" +version = "0.4.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" dependencies = [ "iana-time-zone", "js-sys", @@ -614,9 +621,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -624,9 +631,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -636,27 +643,27 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "cmake" -version = "0.1.54" +version = "0.1.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" dependencies = [ "cc", ] @@ -667,6 +674,16 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -684,15 +701,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" -version = "0.6.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -724,16 +741,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] -name = "countme" -version = "3.0.1" +name = "cpufeatures" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] [[package]] name = "cpufeatures" -version = "0.2.17" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" dependencies = [ "libc", ] @@ -855,8 +875,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "curve25519-dalek-derive", + "digest 0.10.7", "fiat-crypto", "rustc_version", "subtle", @@ -871,20 +892,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "curve25519-dalek-ng" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" -dependencies = [ - "byteorder", - "digest 0.9.0", - "rand_core 0.6.4", - "subtle-ng", - "zeroize", + "syn 2.0.117", ] [[package]] @@ -918,7 +926,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -932,7 +940,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -943,7 +951,7 @@ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core 0.20.11", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -954,20 +962,7 @@ checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ "darling_core 0.21.3", "quote", - "syn 2.0.111", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown 0.14.5", - "lock_api", - "once_cell", - "parking_lot_core", + "syn 2.0.117", ] [[package]] @@ -986,9 +981,9 @@ dependencies = [ [[package]] name = "data-encoding" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "der" @@ -1003,32 +998,33 @@ dependencies = [ [[package]] name = "deranged" -version = "0.5.5" +version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +checksum = "cc3dc5ad92c2e2d1c193bbbbdf2ea477cb81331de4f3103f267ca18368b988c4" dependencies = [ "powerfmt", ] [[package]] name = "derive_more" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "1.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.111", + "rustc_version", + "syn 2.0.117", "unicode-xid", ] @@ -1061,7 +1057,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -1070,12 +1066,6 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - [[package]] name = "dunce" version = "1.0.5" @@ -1088,15 +1078,6 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" -[[package]] -name = "ed25519" -version = "1.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" -dependencies = [ - "signature 1.6.4", -] - [[package]] name = "ed25519" version = "2.2.3" @@ -1104,19 +1085,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", - "signature 2.2.0", + "signature", ] [[package]] name = "ed25519-dalek" -version = "1.0.1" -source = "git+https://github.com/broxus/ed25519-dalek.git#e5d68fd1490a7f6a0d473c6c1b1acef868960471" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ - "curve25519-dalek-ng", - "ed25519 1.5.3", - "rand 0.8.5", + "curve25519-dalek", + "ed25519", "serde", - "sha2 0.9.9", + "sha2 0.10.9", + "subtle", "zeroize", ] @@ -1150,10 +1132,10 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ - "heck 0.5.0", + "heck", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -1162,15 +1144,6 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" -[[package]] -name = "erased-serde" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c138974f9d5e7fe373eb04df7cae98833802ae4b11c24ac7039a21d5af4b26c" -dependencies = [ - "serde", -] - [[package]] name = "errno" version = "0.3.14" @@ -1236,9 +1209,9 @@ checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "flume" @@ -1301,9 +1274,9 @@ checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "futures" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1316,9 +1289,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1326,15 +1299,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1354,38 +1327,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1395,7 +1368,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1417,15 +1389,15 @@ checksum = "271272f4aa3689fd08e21dc3d2656156a67271a0bc21a370c8cc9a7b212d51a0" dependencies = [ "futures-util", "hickory-client", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", ] [[package]] name = "getrandom" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", @@ -1448,6 +1420,20 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.0", + "wasip2", + "wasip3", +] + [[package]] name = "glob" version = "0.3.3" @@ -1456,9 +1442,9 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "h2" -version = "0.4.12" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1473,15 +1459,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash 0.7.8", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1505,6 +1482,8 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" dependencies = [ + "allocator-api2", + "equivalent", "foldhash 0.2.0", ] @@ -1517,12 +1496,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" - [[package]] name = "heck" version = "0.5.0" @@ -1549,7 +1522,7 @@ dependencies = [ "once_cell", "radix_trie", "rand 0.9.2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] @@ -1572,7 +1545,7 @@ dependencies = [ "once_cell", "rand 0.9.2", "ring", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tokio", "tracing", @@ -1630,9 +1603,9 @@ dependencies = [ [[package]] name = "hmac-sha256" -version = "1.1.12" +version = "1.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad6880c8d4a9ebf39c6e8b77007ce223f646a4d21ce29d99f70cb16420545425" +checksum = "ec9d92d097f4749b64e8cc33d924d9f40a2d4eb91402b458014b781f5733d60f" [[package]] name = "hmac-sha512" @@ -1785,32 +1758,15 @@ dependencies = [ "tower-service", ] -[[package]] -name = "hyper-tls" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" -dependencies = [ - "bytes", - "http-body-util", - "hyper 1.8.1", - "hyper-util", - "native-tls", - "tokio", - "tokio-native-tls", - "tower-service", -] - [[package]] name = "hyper-util" -version = "0.1.18" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e9a2a24dc5c6821e71a7030e1e14b7b632acac55c40e9d2e082c621261bb56" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ "base64 0.22.1", "bytes", "futures-channel", - "futures-core", "futures-util", "http 1.4.0", "http-body 1.0.1", @@ -1819,7 +1775,7 @@ dependencies = [ "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.1", + "socket2 0.6.2", "system-configuration", "tokio", "tower-service", @@ -1829,9 +1785,9 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.64" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1899,9 +1855,9 @@ checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", @@ -1913,9 +1869,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" @@ -1932,6 +1888,12 @@ dependencies = [ "zerovec", ] +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + [[package]] name = "ident_case" version = "1.0.1" @@ -1961,9 +1923,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad4bb2b565bca0645f4d68c5c9af97fba094e9791da685bf83cb5f3ce74acf2" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" dependencies = [ "equivalent", "hashbrown 0.16.1", @@ -1988,9 +1950,9 @@ checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "iri-string" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f867b9d1d896b67beb18518eda36fdb77a32ea590de864f1325b294a6d14397" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" dependencies = [ "memchr", "serde", @@ -2004,27 +1966,49 @@ checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" -version = "1.0.15" +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", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" @@ -2038,9 +2022,9 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -2055,11 +2039,17 @@ dependencies = [ "spin", ] +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + [[package]] name = "libc" -version = "0.2.178" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libloading" @@ -2073,19 +2063,19 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.10" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" dependencies = [ "bitflags", "libc", - "redox_syscall", + "redox_syscall 0.7.1", ] [[package]] @@ -2201,11 +2191,11 @@ dependencies = [ [[package]] name = "lru" -version = "0.8.1" +version = "0.16.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.16.1", ] [[package]] @@ -2235,9 +2225,9 @@ dependencies = [ [[package]] name = "matchit" -version = "0.7.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "md-5" @@ -2251,15 +2241,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744133e4a0e0a658e1374cf3bf8e415c4052a15a111acd372764c55b4177d490" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -2270,95 +2260,57 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93c0d11ac30a033511ae414355d80f70d9f29a44a49140face477117a1ee90db" -[[package]] -name = "metrics" -version = "0.20.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b9b8653cec6897f73b519a43fba5ee3d50f62fe9af80b428accdcc093b4a849" -dependencies = [ - "ahash 0.7.8", - "metrics-macros", - "portable-atomic 0.3.20", -] - [[package]] name = "metrics" version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" dependencies = [ - "ahash 0.8.12", - "portable-atomic 1.11.1", + "ahash", + "portable-atomic", ] [[package]] name = "metrics-exporter-prometheus" -version = "0.16.2" +version = "0.17.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd7399781913e5393588a8d8c6a2867bf85fb38eaf2502fdce465aad2dc6f034" +checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" dependencies = [ "base64 0.22.1", "http-body-util", "hyper 1.8.1", + "hyper-rustls", "hyper-util", "indexmap", "ipnet", - "metrics 0.24.3", - "metrics-util 0.19.1", + "metrics", + "metrics-util", "quanta", - "thiserror 1.0.69", + "thiserror 2.0.18", "tokio", "tracing", ] [[package]] name = "metrics-exporter-prometheus" -version = "0.17.2" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b166dea96003ee2531cf14833efedced545751d800f03535801d833313f8c15" +checksum = "3589659543c04c7dc5526ec858591015b87cd8746583b51b48ef4353f99dbcda" dependencies = [ "base64 0.22.1", "http-body-util", "hyper 1.8.1", - "hyper-rustls", "hyper-util", "indexmap", "ipnet", - "metrics 0.24.3", - "metrics-util 0.20.1", + "metrics", + "metrics-util", "quanta", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", ] -[[package]] -name = "metrics-macros" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "731f8ecebd9f3a4aa847dfe75455e4757a45da40a7793d2f0b1f9b6ed18b23f3" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "metrics-util" -version = "0.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8496cc523d1f94c1385dd8f0f0c2c480b2b8aeccb5b7e4485ad6365523ae376" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", - "hashbrown 0.15.5", - "metrics 0.24.3", - "quanta", - "rand 0.9.2", - "rand_xoshiro", - "sketches-ddsketch", -] - [[package]] name = "metrics-util" version = "0.20.1" @@ -2368,7 +2320,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "hashbrown 0.16.1", - "metrics 0.24.3", + "metrics", "quanta", "rand 0.9.2", "rand_xoshiro", @@ -2389,9 +2341,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ "libc", "wasi", @@ -2400,17 +2352,16 @@ dependencies = [ [[package]] name = "moka" -version = "0.12.11" +version = "0.12.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8261cd88c312e0004c1d51baad2980c66528dfdb2bee62003e643a4d8f86b077" +checksum = "b4ac832c50ced444ef6be0767a008b02c106a909ba79d1d830501e94b96f6b7e" dependencies = [ "crossbeam-channel", "crossbeam-epoch", "crossbeam-utils", "equivalent", "parking_lot", - "portable-atomic 1.11.1", - "rustc_version", + "portable-atomic", "smallvec", "tagptr", "uuid", @@ -2435,9 +2386,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.14" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", @@ -2445,146 +2396,38 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework 2.11.1", + "security-framework", "security-framework-sys", "tempfile", ] [[package]] -name = "nekoton" -version = "0.13.1" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" +name = "nekoton-core" +version = "0.0.2" +source = "git+https://github.com/broxus/tycho-nekoton.git#9b9fe00e8accaf4ad0643c98dddba796d86e9c01" dependencies = [ "anyhow", "async-trait", - "base64 0.13.1", - "chacha20poly1305", - "curve25519-dalek-ng", - "downcast-rs", - "dyn-clone", - "ed25519-dalek", - "erased-serde", "futures-util", - "getrandom 0.2.16", - "hex", - "hmac 0.11.0", - "log", - "nekoton-abi", - "nekoton-contracts", - "nekoton-utils", - "num-bigint", - "once_cell", - "parking_lot", - "pbkdf2 0.12.2", - "quick_cache 0.4.3", - "rand 0.8.5", - "secstr", - "serde", - "serde_json", - "sha2 0.10.9", - "slip10_ed25519", - "thiserror 1.0.69", - "tiny-bip39", - "tiny-hderive", - "tokio", - "ton_abi", - "ton_block", - "ton_executor", - "ton_types", - "zeroize", -] - -[[package]] -name = "nekoton-abi" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "anyhow", - "base64 0.13.1", - "ed25519-dalek", - "hex", - "log", - "nekoton-derive", - "nekoton-utils", - "num-bigint", - "num-traits", - "once_cell", - "rustc-hash 1.1.0", - "serde", - "serde_json", - "smallvec", - "thiserror 1.0.69", - "ton_abi", - "ton_block", - "ton_executor", - "ton_types", - "ton_vm", -] - -[[package]] -name = "nekoton-contracts" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "anyhow", - "nekoton-abi", - "nekoton-jetton", - "nekoton-utils", - "once_cell", - "serde", - "thiserror 1.0.69", - "ton_abi", - "ton_block", - "ton_types", -] - -[[package]] -name = "nekoton-derive" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "either", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "nekoton-jetton" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" -dependencies = [ - "anyhow", - "lazy_static", "nekoton-utils", "num-bigint", "num-traits", + "pin-project", "serde", - "sha2 0.10.9", - "ton_abi", - "ton_block", - "ton_types", + "thiserror 2.0.18", + "tokio", + "tycho-executor", + "tycho-types", + "tycho-vm", ] [[package]] name = "nekoton-utils" -version = "0.13.0" -source = "git+https://github.com/broxus/nekoton.git#9c87c3d74dccf21c474a9ad8c2cc24c8eae96c11" +version = "0.0.2" +source = "git+https://github.com/broxus/tycho-nekoton.git#9b9fe00e8accaf4ad0643c98dddba796d86e9c01" dependencies = [ - "anyhow", - "base64 0.13.1", - "chacha20poly1305", - "ed25519-dalek", "hex", - "hmac 0.11.0", - "pbkdf2 0.12.2", - "secstr", "serde", - "sha2 0.10.9", - "thiserror 1.0.69", - "ton_block", - "ton_types", - "zeroize", ] [[package]] @@ -2608,9 +2451,9 @@ dependencies = [ [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] @@ -2624,20 +2467,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "num" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" -dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", -] - [[package]] name = "num-bigint" version = "0.4.6" @@ -2664,31 +2493,11 @@ dependencies = [ "zeroize", ] -[[package]] -name = "num-complex" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" -dependencies = [ - "num-traits", -] - [[package]] name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-derive" -version = "0.3.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" [[package]] name = "num-integer" @@ -2710,17 +2519,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -2758,7 +2556,7 @@ checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" dependencies = [ "critical-section", "parking_lot_core", - "portable-atomic 1.11.1", + "portable-atomic", ] [[package]] @@ -2796,14 +2594,14 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "openssl-probe" -version = "0.1.6" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "openssl-sys" @@ -2866,16 +2664,16 @@ checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", "windows-link 0.2.1", ] [[package]] name = "password-hash" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", @@ -2924,9 +2722,9 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "pest" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbcfd20a6d4eeba40179f05735784ad32bdaef05ce8e8af05f180d45bb3e7e22" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" dependencies = [ "memchr", "ucd-trie", @@ -2934,9 +2732,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51f72981ade67b1ca6adc26ec221be9f463f2b5839c7508998daa17c23d94d7f" +checksum = "11f486f1ea21e6c10ed15d5a7c77165d0ee443402f0780849d1768e7d9d6fe77" dependencies = [ "pest", "pest_generator", @@ -2944,27 +2742,47 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dee9efd8cdb50d719a80088b76f81aec7c41ed6d522ee750178f83883d271625" +checksum = "8040c4647b13b210a963c1ed407c1ff4fdfa01c31d6d2a098218702e6664f94f" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "pest_meta" -version = "2.8.4" +version = "2.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf1d70880e76bdc13ba52eafa6239ce793d85c8e43896507e43dd8984ff05b82" +checksum = "89815c69d36021a140146f26659a81d6c2afa33d216d736dd4be5381a7362220" dependencies = [ "pest", "sha2 0.10.9", ] +[[package]] +name = "pin-project" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677f1add503faace112b9f1373e43e9e054bfdd22ff1a63c1bc485eaec6a6a8a" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e918e4ff8c4549eb882f14b3a4bc8c8bc93de829416eacf579f1207a8fbf861" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "pin-project-lite" version = "0.2.16" @@ -3010,7 +2828,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ - "cpufeatures", + "cpufeatures 0.2.17", "opaque-debug", "universal-hash", ] @@ -3033,18 +2851,9 @@ dependencies = [ [[package]] name = "portable-atomic" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e30165d31df606f5726b090ec7592c308a0eaf61721ff64c9a3018e344a8753e" -dependencies = [ - "portable-atomic 1.11.1", -] - -[[package]] -name = "portable-atomic" -version = "1.11.1" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" [[package]] name = "potential_utf" @@ -3070,11 +2879,21 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] @@ -3094,25 +2913,13 @@ dependencies = [ "winapi", ] -[[package]] -name = "quick_cache" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a4b807ec70346b4fac3c13ae967634237847d49871f623fe0d455403346bad4" -dependencies = [ - "ahash 0.8.12", - "equivalent", - "hashbrown 0.14.5", - "parking_lot", -] - [[package]] name = "quick_cache" version = "0.6.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ada44a88ef953a3294f6eb55d2007ba44646015e18613d2f213016379203ef3" dependencies = [ - "ahash 0.8.12", + "ahash", "equivalent", "hashbrown 0.16.1", "parking_lot", @@ -3131,8 +2938,8 @@ dependencies = [ "quinn-udp", "rustc-hash 2.1.1", "rustls", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.2", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3144,6 +2951,7 @@ 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", @@ -3153,7 +2961,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -3168,16 +2976,16 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.1", + "socket2 0.6.2", "tracing", "windows-sys 0.60.2", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -3216,7 +3024,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20 0.10.0", + "getrandom 0.4.1", + "rand_core 0.10.0", ] [[package]] @@ -3236,7 +3055,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3245,25 +3064,31 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.16", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rand_xoshiro" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" dependencies = [ - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3304,11 +3129,40 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +dependencies = [ + "bitflags", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -3318,9 +3172,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -3329,15 +3183,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "reqwest" -version = "0.12.24" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" +checksum = "ab3f43e3283ab1488b624b44b0e988d0acea0b3214e694730a055cb6b2efa801" dependencies = [ "base64 0.22.1", "bytes", @@ -3349,21 +3203,21 @@ dependencies = [ "http-body-util", "hyper 1.8.1", "hyper-rustls", - "hyper-tls", "hyper-util", "js-sys", "log", "mime", - "native-tls", "percent-encoding", "pin-project-lite", + "quinn", + "rustls", "rustls-pki-types", + "rustls-platform-verifier", "serde", "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", - "tokio-native-tls", + "tokio-rustls", "tower", "tower-http", "tower-service", @@ -3381,7 +3235,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.16", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3408,9 +3262,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40a0376c50d0358279d9d643e4bf7b7be212f1f4ff1da9070a7b54d22ef75c88" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -3420,7 +3274,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "signature 2.2.0", + "signature", "spki", "subtle", "zeroize", @@ -3449,9 +3303,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" dependencies = [ "bitflags", "errno", @@ -3462,9 +3316,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.35" +version = "0.23.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" dependencies = [ "aws-lc-rs", "log", @@ -3478,31 +3332,58 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9980d917ebb0c0536119ba501e90834767bffc3d60641457fd84a1f3fd337923" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" dependencies = [ "openssl-probe", "rustls-pki-types", "schannel", - "security-framework 3.5.1", + "security-framework", ] [[package]] name = "rustls-pki-types" -version = "1.13.1" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "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 0.10.1", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "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.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -3518,9 +3399,9 @@ checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "same-file" @@ -3551,14 +3432,15 @@ dependencies = [ [[package]] name = "schemars" -version = "0.8.22" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" dependencies = [ "bigdecimal", "chrono", "dyn-clone", "indexmap", + "ref-cast", "schemars_derive", "serde", "serde_json", @@ -3567,14 +3449,14 @@ dependencies = [ [[package]] name = "schemars_derive" -version = "0.8.22" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +checksum = "5016d94c77c6d32f0b8e08b781f7dc8a90c2007d4e77472cc2807bc10a8438fe" dependencies = [ "proc-macro2", "quote", "serde_derive_internals", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -3589,34 +3471,11 @@ version = "3.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "490dcfcbfef26be6800d11870ff2df8774fa6e86d047e3e8c8a76b25655e41ca" -[[package]] -name = "secstr" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04f657244f605c4cf38f6de5993e8bd050c8a303f86aeabff142d5c7c113e12" -dependencies = [ - "libc", - "serde", -] - [[package]] name = "security-framework" -version = "2.11.1" +version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" -dependencies = [ - "bitflags", - "core-foundation 0.9.4", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework" -version = "3.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" dependencies = [ "bitflags", "core-foundation 0.10.1", @@ -3627,9 +3486,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.15.0" +version = "2.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" dependencies = [ "core-foundation-sys", "libc", @@ -3668,7 +3527,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -3679,20 +3538,20 @@ checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -3708,15 +3567,15 @@ dependencies = [ [[package]] name = "serde_qs" -version = "0.13.0" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" +checksum = "8b417bedc008acbdf6d6b4bc482d29859924114bbe2650b7921fb68a261d0aa6" dependencies = [ "axum", "futures", "percent-encoding", "serde", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -3731,19 +3590,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" -dependencies = [ - "indexmap", - "itoa", - "ryu", - "serde", - "unsafe-libyaml", -] - [[package]] name = "sha1" version = "0.10.6" @@ -3751,7 +3597,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -3763,7 +3609,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer 0.9.0", "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.9.0", "opaque-debug", ] @@ -3775,7 +3621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest 0.10.7", ] @@ -3796,19 +3642,14 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.7" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7664a098b8e616bdfcc2dc0e9ac44eb231eedf41db4e9fe95d8d32ec728dedad" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] -[[package]] -name = "signature" -version = "1.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" - [[package]] name = "signature" version = "2.2.0" @@ -3827,9 +3668,9 @@ checksum = "c1e9a774a6c28142ac54bb25d25562e6bcf957493a184f15ad4eebccb23e410a" [[package]] name = "slab" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slip10_ed25519" @@ -3861,9 +3702,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" dependencies = [ "libc", "windows-sys 0.60.2", @@ -3931,7 +3772,7 @@ dependencies = [ "serde_json", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-stream", "tracing", @@ -3949,7 +3790,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -3960,7 +3801,7 @@ checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" dependencies = [ "dotenvy", "either", - "heck 0.5.0", + "heck", "hex", "once_cell", "proc-macro2", @@ -3972,7 +3813,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn 2.0.111", + "syn 2.0.117", "tokio", "url", ] @@ -4016,7 +3857,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -4057,7 +3898,7 @@ dependencies = [ "smallvec", "sqlx-core", "stringprep", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", "whoami", @@ -4083,7 +3924,7 @@ dependencies = [ "serde", "serde_urlencoded", "sqlx-core", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "url", "uuid", @@ -4112,37 +3953,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" -[[package]] -name = "strum" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" - -[[package]] -name = "strum_macros" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" -dependencies = [ - "heck 0.4.1", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", -] - [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" -[[package]] -name = "subtle-ng" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" - [[package]] name = "syn" version = "1.0.109" @@ -4156,9 +3972,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.111" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4182,22 +3998,7 @@ checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", -] - -[[package]] -name = "sysinfo" -version = "0.30.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" -dependencies = [ - "cfg-if", - "core-foundation-sys", - "libc", - "ntapi", - "once_cell", - "rayon", - "windows 0.52.0", + "syn 2.0.117", ] [[package]] @@ -4211,14 +4012,14 @@ dependencies = [ "ntapi", "objc2-core-foundation", "objc2-io-kit", - "windows 0.61.3", + "windows", ] [[package]] name = "system-configuration" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" dependencies = [ "bitflags", "core-foundation 0.9.4", @@ -4243,12 +4044,12 @@ checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" [[package]] name = "tempfile" -version = "3.23.0" +version = "3.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.1", "once_cell", "rustix", "windows-sys 0.61.2", @@ -4265,11 +4066,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -4280,18 +4081,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -4336,30 +4137,30 @@ dependencies = [ [[package]] name = "time" -version = "0.3.44" +version = "0.3.47" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", - "serde", + "serde_core", "time-core", "time-macros", ] [[package]] name = "time-core" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" [[package]] name = "time-macros" -version = "0.2.24" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" dependencies = [ "num-conv", "time-core", @@ -4429,7 +4230,7 @@ dependencies = [ "digest 0.10.7", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto-proc", ] @@ -4442,7 +4243,7 @@ dependencies = [ "proc-macro2", "quote", "rustc-hash 2.1.1", - "syn 2.0.111", + "syn 2.0.117", "tl-scheme", ] @@ -4456,14 +4257,14 @@ dependencies = [ "pest", "pest_derive", "rustc-hash 2.1.1", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "tokio" -version = "1.48.0" +version = "1.49.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" dependencies = [ "bytes", "libc", @@ -4471,7 +4272,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.1", + "socket2 0.6.2", "tokio-macros", "windows-sys 0.61.2", ] @@ -4480,163 +4281,53 @@ dependencies = [ name = "tokio-macros" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" -dependencies = [ - "rustls", - "tokio", -] - -[[package]] -name = "tokio-stream" -version = "0.1.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" -dependencies = [ - "futures-core", - "pin-project-lite", - "tokio", -] - -[[package]] -name = "tokio-util" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "slab", - "tokio", -] - -[[package]] -name = "ton_abi" -version = "2.1.0" -source = "git+https://github.com/broxus/ton-labs-abi#7d84f87a1799b727e33f9b09c8e38c764fbd5c68" -dependencies = [ - "anyhow", - "base64 0.13.1", - "byteorder", - "ed25519 1.5.3", - "ed25519-dalek", - "hex", - "num-bigint", - "num-traits", - "serde", - "serde_json", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", - "ton_block", - "ton_types", -] - -[[package]] -name = "ton_block" -version = "1.9.73" -source = "git+https://github.com/broxus/ton-labs-block#f1c3e222ee6a2b2ccf663f4f2df5e1335b95961a" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" dependencies = [ - "anyhow", - "base64 0.13.1", - "crc", - "ed25519 1.5.3", - "ed25519-dalek", - "hex", - "log", - "num", - "num-traits", - "rand 0.8.5", - "rustc-hash 1.1.0", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", - "ton_types", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "ton_executor" -version = "1.15.54" -source = "git+https://github.com/broxus/ton-labs-executor.git#b299a0fc8f3c3ecc28e8e38a9c014be56d4ce52d" +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "anyhow", - "log", - "thiserror 1.0.69", - "ton_block", - "ton_types", - "ton_vm", + "rustls", + "tokio", ] [[package]] -name = "ton_types" -version = "1.10.2" -source = "git+https://github.com/broxus/ton-labs-types#8556b60547a20f16d50abcab084479d0c9db3756" +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ - "anyhow", - "base64 0.13.1", - "countme", - "crc", - "dashmap 5.5.3", - "hex", - "log", - "num", - "num-derive", - "num-traits", - "rand 0.8.5", - "rustc-hash 1.1.0", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] -name = "ton_vm" -version = "1.8.29" -source = "git+https://github.com/broxus/ton-labs-vm.git#211bd88f46fa257ac4b939447f209465d3e201e1" +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ - "anyhow", - "ed25519 1.5.3", - "ed25519-dalek", - "hex", - "lazy_static", - "log", - "num", - "num-traits", - "rand 0.8.5", - "sha2 0.9.9", - "smallvec", - "thiserror 1.0.69", - "ton_block", - "ton_types", + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "slab", + "tokio", ] [[package]] name = "tower" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", @@ -4651,9 +4342,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.7" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf146f99d442e8e68e585f5d798ccd3cad9a7835b917e09728880a862706456" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" dependencies = [ "bitflags", "bytes", @@ -4684,9 +4375,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.43" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "log", "pin-project-lite", @@ -4701,7 +4392,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -4714,14 +4405,14 @@ checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.35" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -4802,17 +4493,18 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tycho-block-util" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "105be4705dc8ba4983dec0d5164089920557567bc1fbeb4bc818daa4110a2a38" dependencies = [ "anyhow", "arc-swap", "bytes", "hex", - "metrics 0.24.3", + "metrics", "parking_lot", "rayon", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tycho-storage-traits", "tycho-types", @@ -4821,22 +4513,25 @@ dependencies = [ [[package]] name = "tycho-build-info" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34bcd667b76ce41c8faded67e30a2f5ab5335dc8dec24a7aad3e6a9c0ce6c5ca" dependencies = [ "anyhow", ] [[package]] name = "tycho-core" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e22a046a4fecfeb0a79ed9d9f686ed065844a84ce5322e61f096eecb34e891" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "arc-swap", "async-trait", "bitflags", + "blake3", "bumpalo", "bumpalo-herd", "bytes", @@ -4845,22 +4540,22 @@ dependencies = [ "castaway", "clap", "crc32c", - "dashmap 6.1.0", + "dashmap", "futures-util", "humantime", - "metrics 0.24.3", + "metrics", "moka", "parking_lot", "parking_lot_core", "pin-project-lite", - "quick_cache 0.6.18", + "quick_cache", "rand 0.9.2", "scopeguard", "serde", "sha2 0.10.9", "smallvec", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tokio", "tracing", @@ -4892,37 +4587,38 @@ dependencies = [ [[package]] name = "tycho-executor" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "148f0edab661a199f2be88448f8a463ca107a7db370e6abf58ac8b2116ec24e0" +checksum = "a4ba91906bc028e800c91e232dfee409b5503e102f91d7931701d5b0d717e846" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "num-bigint", - "thiserror 2.0.17", + "thiserror 2.0.18", "tycho-types", "tycho-vm", ] [[package]] name = "tycho-network" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f51d4db5f49a9ab6951139cbeb384602d6ad1edbb1cb453a4a35c5c97cd53a6" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "arc-swap", "base64 0.22.1", "bytes", "bytesize", "castaway", - "dashmap 6.1.0", - "ed25519 2.2.3", + "dashmap", + "ed25519", "exponential-backoff", "futures-util", "hex", "indexmap", - "metrics 0.24.3", + "metrics", "moka", "parking_lot", "pin-project-lite", @@ -4934,8 +4630,8 @@ dependencies = [ "rustls-pki-types", "rustls-webpki", "serde", - "socket2 0.6.1", - "thiserror 2.0.17", + "socket2 0.6.2", + "thiserror 2.0.18", "tl-proto", "tokio", "tokio-util", @@ -4946,15 +4642,16 @@ dependencies = [ [[package]] name = "tycho-storage" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51fa9c12bf73ebb58cbbac15412a2fbdcf0d31a2c892658d3bfdb1aed8b251f" dependencies = [ "anyhow", "arc-swap", "bytesize", "fdlimit", "libc", - "metrics 0.24.3", + "metrics", "rand 0.9.2", "rlimit", "scopeguard", @@ -4969,8 +4666,9 @@ dependencies = [ [[package]] name = "tycho-storage-traits" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3449aeb0ad7213e38bb8047498a8af265e2ff7682922bac518af83f20d9ebfec" dependencies = [ "bytes", "smallvec", @@ -4979,16 +4677,18 @@ dependencies = [ [[package]] name = "tycho-types" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2ccb37e250becb5c4b827536644c777c247b41ac6c9ddd30902ff1db29818a7" +version = "0.3.2" +source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" dependencies = [ - "ahash 0.8.12", + "ahash", + "anyhow", "base64 0.22.1", "bitflags", "blake3", + "bytes", "crc32c", - "dashmap 6.1.0", + "dashmap", + "ed25519-dalek", "hex", "num-bigint", "num-traits", @@ -4996,45 +4696,59 @@ dependencies = [ "rayon", "scc", "serde", + "serde_json", + "serde_path_to_error", "sha2 0.10.9", "smallvec", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", "tycho-crypto", + "tycho-types-abi-proc", "tycho-types-proc", "typeid", ] +[[package]] +name = "tycho-types-abi-proc" +version = "0.3.0" +source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "tycho-types-proc" version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad05cf4ab89631f8c11d85c3aa80f781502440f75361d251f866e0d76ae9d31" +source = "git+https://github.com/broxus/tycho-types.git?rev=f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1#f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "tycho-util" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e9f4dfca5d4d29d678e2933b0d08f8b84635e1d43f671a71c1419051dce0f8" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "base64 0.22.1", "bytes", "bytesize", "castaway", "crossbeam-deque", - "dashmap 6.1.0", + "dashmap", "futures-executor", "futures-util", "getip", "humantime", "libc", - "metrics 0.24.3", + "metrics", "metrics-exporter-prometheus 0.17.2", "rand 0.9.2", "rayon", @@ -5042,8 +4756,8 @@ dependencies = [ "serde", "serde_json", "serde_path_to_error", - "sysinfo 0.37.2", - "thiserror 2.0.17", + "sysinfo", + "thiserror 2.0.18", "tikv-jemalloc-ctl", "tl-proto", "tokio", @@ -5059,21 +4773,22 @@ dependencies = [ [[package]] name = "tycho-util-proc" -version = "0.3.5" -source = "git+https://github.com/broxus/tycho.git?rev=bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e#bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "723c91dccdb15a705b0448705f631c6e32298931013c054e54f6ffd9cb592341" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] name = "tycho-vm" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "013cf249ea5a32b38050adfc8cbca471da017c0c03292e7462665859638226ac" +checksum = "bef4b5767addc9c546991fe85519684444814527f317a3bd80a852b501250d71" dependencies = [ - "ahash 0.8.12", + "ahash", "anyhow", "bitflags", "blake2", @@ -5082,8 +4797,9 @@ dependencies = [ "num-integer", "num-traits", "sha2 0.10.9", - "thiserror 2.0.17", + "thiserror 2.0.18", "tl-proto", + "tracing", "tycho-crypto", "tycho-types", "tycho-vm-proc", @@ -5091,14 +4807,14 @@ dependencies = [ [[package]] name = "tycho-vm-proc" -version = "0.3.0" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94d448e5c9526dcfdd2d3f63d9e13de2a207e1aadf906ea4d1e61e45f0aeceb3" +checksum = "6aac25f611eb7ab6031a0a3c0ea14884989a7280073bb154b431a991f9adb779" dependencies = [ "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -5110,54 +4826,49 @@ dependencies = [ "argon2", "async-trait", "axum", - "base64 0.13.1", + "base64 0.22.1", "bigdecimal", "chacha20poly1305", "chrono", "clap", - "dashmap 5.5.3", + "dashmap", "derive_more", "ed25519-dalek", "futures", "futures-util", "hex", "hmac-sha256", - "http 0.2.12", - "itertools 0.10.5", + "http 1.4.0", + "itertools 0.14.0", "lazy_static", "log", "lru", - "metrics 0.20.1", - "metrics-exporter-prometheus 0.16.2", - "nekoton", - "nekoton-abi", - "nekoton-contracts", - "nekoton-utils", + "metrics", + "metrics-exporter-prometheus 0.18.1", + "nekoton-core", "num-bigint", "num-traits", "opg", "parking_lot", + "pbkdf2 0.12.2", "pomfrit", - "rand 0.8.5", + "rand 0.10.0", "rayon", "regex", "reqwest", - "rustc-hash 1.1.0", + "rustc-hash 2.1.1", "schemars", "serde", "serde_json", - "serde_yaml", + "sha2 0.10.9", + "slip10_ed25519", "sqlx", - "strum", - "strum_macros", - "sysinfo 0.30.13", - "thiserror 1.0.69", + "thiserror 2.0.18", "tikv-jemallocator", + "tiny-bip39", + "tiny-hderive", "tokio", "tokio-util", - "ton_abi", - "ton_block", - "ton_types", "tower", "tower-http", "tower-service", @@ -5199,9 +4910,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" @@ -5240,12 +4951,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "unsafe-libyaml" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" - [[package]] name = "untrusted" version = "0.9.0" @@ -5254,9 +4959,9 @@ checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.7" +version = "2.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" dependencies = [ "form_urlencoded", "idna", @@ -5278,11 +4983,11 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.19.0" +version = "1.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" dependencies = [ - "getrandom 0.3.4", + "getrandom 0.4.1", "js-sys", "serde_core", "wasm-bindgen", @@ -5333,9 +5038,18 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen", ] @@ -5348,9 +5062,9 @@ checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -5361,11 +5075,12 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.56" +version = "0.4.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +checksum = "70a6e77fd0ae8029c9ea0063f87c46fde723e7d887703d74ad2616d792e51e6f" dependencies = [ "cfg-if", + "futures-util", "js-sys", "once_cell", "wasm-bindgen", @@ -5374,9 +5089,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5384,31 +5099,65 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -5424,6 +5173,15 @@ 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 = "weedb" version = "0.6.0" @@ -5431,9 +5189,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7769a20b8caefbfd7005172847727296a96c9aa19531f8f153219bc6b1fb00a" dependencies = [ "librocksdb-sys", - "metrics 0.24.3", + "metrics", "rocksdb", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", ] @@ -5478,16 +5236,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" -dependencies = [ - "windows-core 0.52.0", - "windows-targets 0.52.6", -] - [[package]] name = "windows" version = "0.61.3" @@ -5510,15 +5258,6 @@ dependencies = [ "windows-core 0.61.2", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.61.2" @@ -5564,7 +5303,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -5575,7 +5314,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -5647,6 +5386,15 @@ dependencies = [ "windows-link 0.2.1", ] +[[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" @@ -5683,6 +5431,21 @@ dependencies = [ "windows-link 0.2.1", ] +[[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" @@ -5740,6 +5503,12 @@ dependencies = [ "windows-link 0.1.3", ] +[[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" @@ -5758,6 +5527,12 @@ 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" @@ -5776,6 +5551,12 @@ 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" @@ -5806,6 +5587,12 @@ 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" @@ -5824,6 +5611,12 @@ 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" @@ -5842,6 +5635,12 @@ 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" @@ -5860,6 +5659,12 @@ 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" @@ -5880,9 +5685,91 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] [[package]] name = "writeable" @@ -5909,28 +5796,28 @@ checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", "synstructure", ] [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -5950,7 +5837,7 @@ checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", "synstructure", ] @@ -5965,13 +5852,13 @@ dependencies = [ [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] [[package]] @@ -6004,9 +5891,15 @@ checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.111", + "syn 2.0.117", ] +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + [[package]] name = "zstd-safe" version = "7.2.4" diff --git a/Cargo.toml b/Cargo.toml index e45c495..2b31e7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,47 +7,49 @@ publish = false license-file = "LICENSE" [dependencies] -aide = { version = "0.13.5", features = ["axum", "axum-extra", "scalar", "macros"] } +aide = { version = "0.15.1", features = ["axum", "axum-extra", "axum-json", "scalar", "macros"] } anyhow = "1.0" -axum = { version = "0.7", features = ["multipart", "macros"] } -argon2 = "0.4.1" +axum = { version = "0.8.8", features = ["multipart", "macros"] } +argon2 = "0.5" async-trait = "0.1" -base64 = "0.13" +base64 = "0.22" bigdecimal = { version = "0.4.5", features = ["serde"] } chacha20poly1305 = "0.10.1" chrono = { version = "0.4", features = ["serde"] } -clap = { version = "4.5.3", features = ["derive"] } -dashmap = "5.3.4" -derive_more = { version = "1.0.0", features = ["full"] } +clap = { version = "4.5", features = ["derive"] } +dashmap = "6.1" +derive_more = { version = "2", features = ["full"] } futures = "0.3" futures-util = "0.3.31" hex = "0.4" hmac-sha256 = "1.1.4" -http = "0.2" -itertools = "0.10.1" +http = "1" +itertools = "0.14" lazy_static = "1.4.0" log = { version = "0.4", features = ["std", "serde"] } -lru = "0.8" -metrics = "0.20.1" -metrics-exporter-prometheus = { version = "0.16.0", default-features = false, features = ["http-listener"] } +lru = "0.16" +metrics = "0.24" +metrics-exporter-prometheus = { version = "0.18.1", default-features = false, features = ["http-listener"] } num-bigint = "0.4" num-traits = "0.2" opg = { version = "0.2", features = ["uuid"] } parking_lot = "0.12.0" +pbkdf2 = { version = "0.12.2" } pomfrit = "0.1" -rand = "0.8" +rand = { version = "0.10", features = ["thread_rng"] } rayon = "1.10" regex = "1.5" -reqwest = { version = "0.12", features = ["json"] } -rustc-hash = "1.1.0" -schemars = { version = "0.8.13", features = ["chrono", "bigdecimal04", "uuid1"] } +reqwest = { version = "0.13", features = ["json"] } +rustc-hash = "2" +schemars = { version = "0.9.0", features = ["chrono04", "bigdecimal04", "uuid1"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_yaml = "0.9.4" +sha2 = { version = "0.10.8" } +slip10_ed25519 = "0.1.3" sqlx = { version = "0.8.2", features = ["runtime-tokio-native-tls", "postgres", "uuid", "bigdecimal", "chrono", "json"] } -strum = "0.24.1" -strum_macros = "0.24.1" -thiserror = "1.0" +thiserror = "2.0" +tiny-hderive = { git = "https://github.com/broxus/tiny-hderive.git" } +tiny-bip39 = { git = "https://github.com/broxus/tiny-bip39.git", default-features = false } tokio = { version = "1", features = ["sync", "fs", "rt-multi-thread", "macros", "signal", "parking_lot"] } tokio-util = "0.7" tower = { version = "0.5", features = ["limit"] } @@ -55,37 +57,29 @@ tower-http = { version = "0.6", features = ["trace", "cors", "limit", "set-heade tower-service = "0.3.3" tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } -tycho-types = { version = "0.3.1", features = ["tycho", "stats", "serde"] } +tycho-types = { version = "0.3.2" , features = ["tycho", "stats", "serde", "abi"] } uuid = { version = "1.1", features = ["v4", "serde"] } -ed25519-dalek = { git = "https://github.com/broxus/ed25519-dalek.git" } +ed25519-dalek = { version = "2.1.1" } tikv-jemallocator = { version = "0.6.0", features = [ "unprefixed_malloc_on_supported_platforms", "background_threads", ] } -tycho-block-util = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" } -tycho-core = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e", features = ["cli"] } -tycho-storage = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e" } -tycho-util = { git = "https://github.com/broxus/tycho.git", rev = "bb2d3cca4c3dd010a4e1aef82e2f76655fc23a3e", features = ["cli"] } +tycho-block-util = { version = "0.3.6" } +tycho-core = { version = "0.3.6", features = ["cli"] } +tycho-storage = { version = "0.3.6" } +tycho-util = { version = "0.3.6", features = ["cli"] } -tycho-vm = "0.3.0" -tycho-executor = "0.3.0" +tycho-vm = "0.3.2" +tycho-executor = "0.3.2" -# TON specific dependencies -ton_block = { git = "https://github.com/broxus/ton-labs-block" } -ton_abi = { git = "https://github.com/broxus/ton-labs-abi" } -ton_types = { git = "https://github.com/broxus/ton-labs-types" } - -# Nekoton SDK -nekoton = { git = "https://github.com/broxus/nekoton.git", default-features = true } -nekoton-abi = { git = "https://github.com/broxus/nekoton.git", features = ["derive"] } -nekoton-utils = { git = "https://github.com/broxus/nekoton.git" } -nekoton-contracts = { git = "https://github.com/broxus/nekoton.git" } - -sysinfo = "0.30.13" +nekoton-core = { git = "https://github.com/broxus/tycho-nekoton.git" } [features] default = [] + +[patch.crates-io] +tycho-types = { git = "https://github.com/broxus/tycho-types.git", rev = "f9e5dbe1222ee51f3e4f5a6836f95a515dd6cdf1"} diff --git a/README.md b/README.md index ba43e5f..e89376e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,5 @@

- - Logo - -

- -

-

Everscale Wallet API

+

Tycho Wallet API

GitHub @@ -15,7 +9,7 @@ ### Overview This is a light node + api for sending and tracking payments. The app listens for addresses from the database and -indexes all transactions, putting information about them in the postsgres DB. All transactions with native EVERs are +indexes all transactions, putting information about them in the postsgres DB. All transactions with native TYCHOs are tracked, and there is a whitelist of root token addresses to be tracked in the settings. There is a callbacks table in the database, where you can specify the url of your backend to which callbacks will come for all transactions. @@ -133,14 +127,15 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif ### Let's start using Wallet API 1. #### Create address - Create yourself a "system address" by calling `/address/create` with empty parameters. The response will return a EVER - address. It is necessary to send EVERs on it, which will be consumed as gas for further work. + Create yourself a "system address" by calling `/address/create` with empty parameters. The response will return a TYCHO + address. It is necessary to send TYCHOs on it, which will be consumed as gas for further work. Default used account type is + `Wallet v5 r1`. **For simplicity, you use the script** ```bash API_KEY=${API_KEY} SECRET=${API_SECRET} HOST=${HOST} \ - ./scripts/wallet.sh -m create_account + ./scripts/wallet.sh -m create_account --account-type WalletV5R1 ``` 2. #### Callbacks @@ -162,7 +157,7 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif TOKEN_ADDRESS - Token address (example: 0:0ee39330eddb680ce731cd6a443c71d9069db06d149a9bec9569d1eb8d04eb37) TOKEN_CONTRACT_VERSION - "Tip3" or "OldTip3v4" -4. #### Transfer EVER +4. #### Transfer TYCHO Example request: ``` /transactions/create @@ -174,11 +169,11 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif "bounce":false, "outputs":[ { - // how much EVER to send. To send 1 EVER this value = 1000000000 + // how much TYCHO to send. To send 1 TYCHO this value = 1000000000 "value":"1000000000", - // Set Normal to take the number of sent EVERs from the value + // Set Normal to take the number of sent TYCHOs from the value "outputType":"Normal", - // Recipient address of EVERs + // Recipient address of TYCHOs "recipientAddress":"0:0000000000000000000000000000000000000000000000000000000000000000" } ], @@ -204,6 +199,13 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif event a `Done` state by calling `/events/mark`. 2) by polling the GET method `/transactions/id/` + To confirm a pending multisig transaction, use the script: + ```bash + API_KEY=${API_KEY} SECRET=${API_SECRET} HOST=${HOST} \ + ./scripts/wallet.sh -m confirm_transaction \ + --address {multisig_wallet_address} --transaction-id {transaction_id} + ``` + 5. #### How to process a payment from a user on the backend We generate a deposit address for the user by calling `/address/create` with empty parameters. After receiving the payment, the backend receives a callback of the form `AccountTransactionEvent` (see [swagger](https://tonapi.broxus.com/swagger.yaml)). @@ -216,7 +218,7 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif 6. #### Transfer tokens First, check the status and balance of the address you want to send tokens from by making a GET request to /address/{string}. - The address you are sending tokens from must have at least 0.6 EVER (balance >= 600000000). + The address you are sending tokens from must have at least 0.6 TYCHO (balance >= 600000000). To transfer tokens, use the method: ``` @@ -226,13 +228,13 @@ NOTE: scripts are prepared and tested on **Ubuntu 20.04**. You may need to modif "id":"00000000-0000-0000-0000-000000000000", // The address of the sender. For example, your system address. "fromAddress":"0:0000000000000000000000000000000000000000000000000000000000000000", - // Recipient address of EVERs + // Recipient address of TYCHOs "recipientAddress":"0:0000000000000000000000000000000000000000000000000000000000000000", // The number of tokens with decimals. For example, for transferring 1 USDT this value = "1000000" "value":"1000000000", - // How much to apply EVER, the default recommended value is 0.5 EVER. The funds will be debited fromAddress. + // How much to apply TYCHO, the default recommended value is 0.5 TYCHO. The funds will be debited fromAddress. "fee": "5000000000", - // The address to which to return the residuals EVER. For example, your system address. + // The address to which to return the residuals TYCHO. For example, your system address. "sendGasTo":"0:0000000000000000000000000000000000000000000000000000000000000000", // Token Address from whitelist "rootAddress":"0:0000000000000000000000000000000000000000000000000000000000000000", @@ -523,12 +525,12 @@ for setting up the deployment environment. 1. **Build the builder image**: The `builder.dockerfile` is responsible for compiling the project using Rust. It builds the project based on the - specified network (either `everscale` or `venom`) and prepares the database for the application using SQLx. + specified network (either `tycho` or other) and prepares the database for the application using SQLx. Use the following command to build the builder image: ```bash - podman build --layers --network=host -f builder.dockerfile -t builder --build-arg DATABASE_URL="postgresql://everscale:everscale@localhost:5432/everscale" + podman build --layers --network=host -f builder.dockerfile -t builder --build-arg DATABASE_URL="postgresql://tycho:tycho@localhost:5432/tycho" ``` 2. **Build the deployment image**: @@ -538,7 +540,7 @@ for setting up the deployment environment. Build the deployment image using the following command: ```bash - podman build --layers -f deploy.dockerfile -t ever-wallet + podman build --layers -f deploy.dockerfile -t tycho-wallet ``` #### Running the Container @@ -550,7 +552,7 @@ Once the images are built, you can run the container using Podman or Docker. To run the application, use the following command: ```bash - podman run --network=host ever-wallet + podman run --network=host tycho-wallet ``` This will run the `tycho-wallet-api` server using the default configuration files already existing in the container. @@ -568,14 +570,14 @@ Once the images are built, you can run the container using Podman or Docker. ```bash podman run --network=host \ - -v /tmp/everscale-data:/var/db/tycho-wallet-api - -e DB_USER=everscale \ - -e DB_PASSWORD=everscale \ + -v /tmp/tycho-data:/var/db/tycho-wallet-api + -e DB_USER=tycho \ + -e DB_PASSWORD=tycho \ -e DB_HOST=localhost \ - -e DB_NAME=everscale \ + -e DB_NAME=tycho \ -e SECRET=0xAAAAA \ -e SALT=OreOYYe5nHWTHnOPSvsmMQ \ - ever-wallet + tycho-wallet ``` It generally allows dynamically setting environment variables for database credentials, secrets, and other @@ -583,7 +585,7 @@ Once the images are built, you can run the container using Podman or Docker. ### Troubleshooting -When the node is out of sync, which especially applies for Venom, removing database and re-syncing node may help to +When the node is out of sync, removing database and re-syncing node may help to restore service operations. `rm -rf /var/db/tycho-wallet-api` diff --git a/builder.dockerfile b/builder.dockerfile index 56a3bad..9809f72 100644 --- a/builder.dockerfile +++ b/builder.dockerfile @@ -30,21 +30,18 @@ WORKDIR /app # Copy the source code into the Docker image COPY . /app -# Define a build argument to pass in the network (default to "Everscale") -ARG NETWORK="everscale" -ARG DATABASE_URL=postgres://everscale:everscale@localhost:5432/everscale +# Define a build argument to pass in the network (default to "Tycho") +ARG NETWORK="tycho" +ARG DATABASE_URL=postgres://tycho:tycho@localhost:5432/tycho # Migrations first, otherwise it may not compile RUN cargo sqlx database create --database-url "$DATABASE_URL" RUN cargo sqlx migrate run --database-url "$DATABASE_URL" # Build the project based on the network variable -RUN if [ "$NETWORK" = "everscale" ]; then \ +RUN if [ "$NETWORK" = "tycho" ]; then \ cargo sqlx prepare && \ RUSTFLAGS="-C target_cpu=native" SQLX_OFFLINE=true cargo build --release; \ - elif [ "$NETWORK" = "venom" ]; then \ - cargo sqlx prepare && \ - RUSTFLAGS="-C target_cpu=native" SQLX_OFFLINE=true cargo build --release --features venom; \ else \ echo 'ERROR: Unexpected network'; \ exit 1; \ diff --git a/migrations/20260218000000_add_wallet_v5r1.sql b/migrations/20260218000000_add_wallet_v5r1.sql new file mode 100644 index 0000000..3249980 --- /dev/null +++ b/migrations/20260218000000_add_wallet_v5r1.sql @@ -0,0 +1 @@ +ALTER TYPE twa_account_type ADD VALUE 'WalletV5R1' AFTER 'EverWallet'; \ No newline at end of file diff --git a/nix/local-test.nix b/nix/local-test.nix index 001ac6e..e960f27 100644 --- a/nix/local-test.nix +++ b/nix/local-test.nix @@ -10,44 +10,44 @@ services.tycho-wallet-api = { enable = true; port = 7354; - chain = "Everscale"; - dbPasswordFile = "/var/everwalletapidb"; # fill it with password - everSecretFile = "/var/everwalletapisecret"; - everSaltFile = "/var/everwalletapisalt"; + chain = "Tycho"; + dbPasswordFile = "/var/tychowalletapidb"; # fill it with password + tychoSecretFile = "/var/tychowalletapisecret"; + tychoSaltFile = "/var/tychowalletapisalt"; metricsHost = "0.0.0.0"; }; systemd.services = { - everwalletapidb-key = { + tychowalletapidb-key = { enable = true; - description = "Ever wallet API password for PostgreSQL is provided"; + description = "Tycho wallet API password for PostgreSQL is provided"; wantedBy = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; script = '' - echo "Ever wallet API password for PostgreSQL is done" + echo "Tycho wallet API password for PostgreSQL is done" ''; }; - everwalletapisecret-key = { + tychowalletapisecret-key = { enable = true; - description = "Ever wallet encryption secret is provided"; + description = "Tycho wallet encryption secret is provided"; wantedBy = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; script = '' - echo "Ever wallet encryption secret is done" + echo "Tycho wallet encryption secret is done" ''; }; - everwalletapisalt-key = { + tychowalletapisalt-key = { enable = true; - description = "Ever wallet encryption salt is provided"; + description = "Tycho wallet encryption salt is provided"; wantedBy = [ "network.target" ]; serviceConfig.Type = "oneshot"; serviceConfig.RemainAfterExit = true; script = '' - echo "Ever wallet encryption salt is done" + echo "Tycho wallet encryption salt is done" ''; }; }; diff --git a/nix/module.nix b/nix/module.nix index 57f7fe6..be83794 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -40,9 +40,9 @@ in { }; chain = mkOption { type = types.str; - default = "Everscale"; + default = "Tycho"; description = '' - Which blockchain to use: Everscale, Venom + Which blockchain to use: Tycho, any other ''; }; @@ -199,44 +199,44 @@ in { dbPasswordFile = mkOption { type = types.str; - default = "/run/keys/everwalletapidb"; + default = "/run/keys/tychowalletapidb"; description = '' Location of file with password for RPC. ''; }; dbPasswordFileService = mkOption { type = types.str; - default = "everwalletapidb-key.service"; + default = "tychowalletapidb-key.service"; description = '' Service that indicates that dbPasswordFile is ready. ''; }; - everSecretFile = mkOption { + tychoSecretFile = mkOption { type = types.str; - default = "/run/keys/everwalletapisecret"; + default = "/run/keys/tychowalletapisecret"; description = '' Location of file with secret for decrypting transactions. ''; }; - everSecretFileService = mkOption { + tychoSecretFileService = mkOption { type = types.str; - default = "everwalletapisecret-key.service"; + default = "tychowalletapisecret-key.service"; description = '' - Service that indicates that everSecretFile is ready. + Service that indicates that tychoSecretFile is ready. ''; }; - everSaltFile = mkOption { + tychoSaltFile = mkOption { type = types.str; - default = "/run/keys/everwalletapisalt"; + default = "/run/keys/tychowalletapisalt"; description = '' Location of file with salt for ???. ''; }; - everSaltFileService = mkOption { + tychoSaltFileService = mkOption { type = types.str; - default = "everwalletapisalt-key.service"; + default = "tychowalletapisalt-key.service"; description = '' - Service that indicates that everSaltFile is ready. + Service that indicates that tychoSaltFile is ready. ''; }; }; @@ -263,14 +263,14 @@ in { # Create systemd service systemd.services.tycho-wallet-api = { enable = true; - description = "Service that indexes transactions for Ever or Venom"; - after = ["network.target" cfg.dbPasswordFileService cfg.everSecretFileService cfg.everSaltFileService]; - wants = ["network.target" cfg.dbPasswordFileService cfg.everSecretFileService cfg.everSaltFileService]; + description = "Service that indexes transactions for Tycho or any other"; + after = ["network.target" cfg.dbPasswordFileService cfg.tychoSecretFileService cfg.tychoSaltFileService]; + wants = ["network.target" cfg.dbPasswordFileService cfg.tychoSecretFileService cfg.tychoSaltFileService]; path = with pkgs; [ ]; script = '' export DB_PASSWORD=$(cat ${cfg.dbPasswordFile} | xargs echo -n) - export SECRET=$(cat ${cfg.everSecretFile} | xargs echo -n) - export SALT=$(cat ${cfg.everSaltFile} | xargs echo -n) + export SECRET=$(cat ${cfg.tychoSecretFile} | xargs echo -n) + export SALT=$(cat ${cfg.tychoSaltFile} | xargs echo -n) ${cfg.package}/bin/tycho-wallet-api server \ --config /etc/${cfg.configdir}/config.json \ diff --git a/scripts/wallet.sh b/scripts/wallet.sh index 2ee7cf5..71431f1 100755 --- a/scripts/wallet.sh +++ b/scripts/wallet.sh @@ -23,6 +23,11 @@ function print_help() { echo ' --dst-addr Recipient address' echo ' --root-addr Root Token address' echo ' --amount Token amount' + echo '' + echo ' - confirm_transaction - confirm a pending multisig transaction.' + echo ' Options:' + echo ' --address Multisig wallet address' + echo ' --transaction-id Multisig transaction ID to confirm' } while [[ $# -gt 0 ]]; do @@ -98,6 +103,28 @@ while [[ $# -gt 0 ]]; do exit 1 fi ;; + --address) + address="$2" + shift # past argument + if [ "$#" -gt 0 ]; then shift; + else + echo 'ERROR: Expected address' + echo '' + print_help + exit 1 + fi + ;; + --transaction-id) + transaction_id="$2" + shift # past argument + if [ "$#" -gt 0 ]; then shift; + else + echo 'ERROR: Expected transaction ID' + echo '' + print_help + exit 1 + fi + ;; *) # unknown option echo 'ERROR: Unknown option' echo '' @@ -200,6 +227,28 @@ function create_token_transaction() { --data-raw "$body" } +function confirm_transaction() { + timestamp=$1 + address=$2 + transaction_id=$3 + + uri="/ton/v3/transactions/confirm" + body='{"id": "", "address": "", "transactionId": 0}' + body=$(echo "$body" | jq --indent 4 -r --arg id "$(uuidgen)" '.id = $id') + body=$(echo "$body" | jq --indent 4 -r --arg address "$address" '.address = $address') + body=$(echo "$body" | jq --indent 4 -r --argjson transaction_id "$transaction_id" '.transactionId = $transaction_id') + + stringToSign="$timestamp$uri$body" + signature=$(create_signature "$stringToSign") + + curl -s --location --request POST "$host$uri" \ + --header 'Content-Type: application/json' \ + --header "api-key: $api_key" \ + --header "timestamp: $timestamp" \ + --header "sign: $signature" \ + --data-raw "$body" +} + case $method in create_account) if [ -z "$account_type" ]; then @@ -264,6 +313,23 @@ case $method in timestamp=$(timestamp_ms) create_token_transaction "$timestamp" "$sender" "$recipient" "$root_address" "$amount" | jq . ;; + confirm_transaction) + if [ -z "$address" ]; then + echo 'ERROR: Skipped address' + echo '' + print_help + exit 1 + fi + if [ -z "$transaction_id" ]; then + echo 'ERROR: Skipped transaction ID' + echo '' + print_help + exit 1 + fi + + timestamp=$(timestamp_ms) + confirm_transaction "$timestamp" "$address" "$transaction_id" | jq . + ;; *) # unknown method echo 'ERROR: Unknown method' echo '' diff --git a/src/api/controllers/address.rs b/src/api/controllers/address.rs index 5e3c068..73f5910 100644 --- a/src/api/controllers/address.rs +++ b/src/api/controllers/address.rs @@ -2,7 +2,7 @@ use axum::extract::{Path, State}; use axum::Json; use tokio::time::Instant; -use metrics::{histogram, increment_counter}; +use metrics::{counter, histogram}; use crate::api::controllers::*; use crate::api::requests::*; @@ -24,8 +24,8 @@ pub async fn post_address_create( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "createAddress"); - increment_counter!("requests_processed", "method" => "createAddress"); + histogram!("execution_time_seconds", "method" => "createAddress").record(elapsed); + counter!("requests_processed", "method" => "createAddress").increment(1); Ok(Json(AddressResponse::from(address))) } diff --git a/src/api/controllers/authorization.rs b/src/api/controllers/authorization.rs index 288c470..03febe7 100644 --- a/src/api/controllers/authorization.rs +++ b/src/api/controllers/authorization.rs @@ -1,7 +1,6 @@ use std::sync::Arc; use aide::{OperationInput, OperationOutput}; -use axum::async_trait; use axum::body::Body; use axum::extract::{FromRequest, FromRequestParts, OriginalUri}; use axum::http::request::Parts; @@ -10,6 +9,7 @@ use axum::http::{Method, StatusCode}; use axum::middleware::Next; use axum::response::IntoResponse; use schemars::JsonSchema; +use std::future::ready; use crate::api::int_schema; use crate::models::*; @@ -103,25 +103,24 @@ async fn check_api_key( #[derive(Debug, Clone)] pub struct IdExtractor(pub ServiceId); -#[async_trait] impl FromRequestParts for IdExtractor where S: Send + Sync, { type Rejection = Rejection; - async fn from_request_parts( + fn from_request_parts( parts: &mut Parts, _state: &S, - ) -> Result { + ) -> impl std::future::Future> + Send { let id: Option<&IdExtractor> = parts.extensions.get(); - match id { + ready(match id { Some(service_id) => Ok(IdExtractor(service_id.0)), None => Err(Rejection { reason: "Service id not found".to_string(), status_code: StatusCode::UNAUTHORIZED, }), - } + }) } } diff --git a/src/api/controllers/misc.rs b/src/api/controllers/misc.rs index 14104da..2c2eb86 100644 --- a/src/api/controllers/misc.rs +++ b/src/api/controllers/misc.rs @@ -1,7 +1,12 @@ +use std::str::FromStr; + use axum::extract::State; use axum::Json; -use metrics::{histogram, increment_counter}; +use metrics::{counter, histogram}; use tokio::time::Instant; +use tycho_types::abi::SerializeAbiValue; +use tycho_types::abi::SerializeAbiValueParams; +use tycho_types::cell::HashBytes; use uuid::Uuid; use crate::api::controllers::*; @@ -31,11 +36,25 @@ pub async fn post_read_contract( req.responsible.unwrap_or_default(), ) .await - .map(|value| ReadContractResponse { object: value })?; + .map(|values| { + let params = SerializeAbiValueParams::default(); + let mut output = Vec::new(); + for value in values { + output.push(OutputParamDTO { + abi_value: serde_json::to_string(&SerializeAbiValue::with_params( + &value.value, + params, + )) + .unwrap_or_default(), + name: value.name.to_string(), + }); + } + ReadContractResponse { output } + })?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "readContract"); - increment_counter!("requests_processed", "method" => "readContract"); + histogram!("execution_time_seconds", "method" => "readContract").record(elapsed); + counter!("requests_processed", "method" => "readContract").increment(1); Ok(Json(tokens)) } @@ -57,8 +76,8 @@ pub async fn post_encode_tvm_cell( .map(|cell| EncodedCellResponse { base64_cell: cell })?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "encodeTvmCell"); - increment_counter!("requests_processed", "method" => "encodeTvmCell"); + histogram!("execution_time_seconds", "method" => "encodeTvmCell").record(elapsed); + counter!("requests_processed", "method" => "encodeTvmCell").increment(1); Ok(Json(cell)) } @@ -95,14 +114,14 @@ pub async fn post_prepare_generic_message( ) .await?; - ctx.memory_storage.add_message(unsigned_message.clone()); + let message_hash = ctx.memory_storage.add_message(unsigned_message); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "prepareGenericMessage"); - increment_counter!("requests_processed", "method" => "prepareGenericMessage"); + histogram!("execution_time_seconds", "method" => "prepareGenericMessage").record(elapsed); + counter!("requests_processed", "method" => "prepareGenericMessage").increment(1); Ok(Json(UnsignedMessageHashResponse { - unsigned_message_hash: hex::encode(unsigned_message.hash()), + unsigned_message_hash: message_hash.to_string(), })) } @@ -111,21 +130,24 @@ pub async fn post_send_signed_message( Json(req): Json, ) -> Result> { let start = Instant::now(); + let message_hash = HashBytes::from_str(&req.hash) + .map_err(|_| ControllersError::WrongInput("Bad hash format".to_string()))?; - let res = match ctx.memory_storage.get_message(&req.hash) { + let res = match ctx.memory_storage.get_message(&message_hash) { Some(message) => { + let expire_at = message.expire_at(); let signature: [u8; 64] = hex::decode(req.signature) .map_err(|_| ControllersError::WrongInput("Bad signature format".to_string()))? .try_into() .map_err(|_| ControllersError::WrongInput("Bad signature format".to_string()))?; - let signed_message = message - .sign(&signature) + let owned_message = message + .with_signature(&ed25519_dalek::Signature::from_bytes(&signature)) .map_err(|_| ControllersError::WrongInput("Bad signature format".to_string()))?; let hash = ctx .ton_service - .send_signed_message(req.sender_addr, req.hash, signed_message) + .send_signed_message(req.sender_addr, req.hash, owned_message, expire_at) .await?; Ok(SignedMessageHashResponse { @@ -138,8 +160,8 @@ pub async fn post_send_signed_message( }?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "sendSignedMessage"); - increment_counter!("requests_processed", "method" => "sendSignedMessage"); + histogram!("execution_time_seconds", "method" => "sendSignedMessage").record(elapsed); + counter!("requests_processed", "method" => "sendSignedMessage").increment(1); Ok(Json(res)) } @@ -180,8 +202,8 @@ pub async fn post_send_generic_message( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "sendGenericMessage"); - increment_counter!("requests_processed", "method" => "sendGenericMessage"); + histogram!("execution_time_seconds", "method" => "sendGenericMessage").record(elapsed); + counter!("requests_processed", "method" => "sendGenericMessage").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -200,8 +222,8 @@ pub async fn post_set_callback( .await?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "setCallback"); - increment_counter!("requests_processed", "method" => "setCallback"); + histogram!("execution_time_seconds", "method" => "setCallback").record(elapsed); + counter!("requests_processed", "method" => "setCallback").increment(1); Ok(Json(SetCallbackResponse { callback: response })) } @@ -223,8 +245,8 @@ pub async fn get_token_whitelist( })?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "getTokenWhitelist"); - increment_counter!("requests_processed", "method" => "getTokenWhitelist"); + histogram!("execution_time_seconds", "method" => "getTokenWhitelist").record(elapsed); + counter!("requests_processed", "method" => "getTokenWhitelist").increment(1); Ok(Json(whitelist)) } @@ -237,8 +259,8 @@ pub async fn post_resubscribe_for_all_accounts( ctx.ton_service.resubscribe_for_all_accounts().await?; let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "resubscribeForAllAccounts"); - increment_counter!("requests_processed", "method" => "resubscribeForAllAccounts"); + histogram!("execution_time_seconds", "method" => "resubscribeForAllAccounts").record(elapsed); + counter!("requests_processed", "method" => "resubscribeForAllAccounts").increment(1); Ok(Json(ResubscribeResponse {})) } diff --git a/src/api/controllers/transactions.rs b/src/api/controllers/transactions.rs index 8793fe8..92fc560 100644 --- a/src/api/controllers/transactions.rs +++ b/src/api/controllers/transactions.rs @@ -1,6 +1,6 @@ use axum::extract::{Path, State}; use axum::Json; -use metrics::{histogram, increment_counter}; +use metrics::{counter, histogram}; use tokio::time::Instant; use uuid::Uuid; @@ -46,8 +46,8 @@ pub async fn post_transactions_create( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "transactionCreate"); - increment_counter!("requests_processed", "method" => "transactionCreate"); + histogram!("execution_time_seconds", "method" => "transactionCreate").record(elapsed); + counter!("requests_processed", "method" => "transactionCreate").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -66,8 +66,8 @@ pub async fn post_transactions_confirm( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "transactionConfirm"); - increment_counter!("requests_processed", "method" => "transactionConfirm"); + histogram!("execution_time_seconds", "method" => "transactionConfirm").record(elapsed); + counter!("requests_processed", "method" => "transactionConfirm").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -156,8 +156,8 @@ pub async fn post_tokens_transactions_create( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "tokenTransactionCreate"); - increment_counter!("requests_processed", "method" => "tokenTransactionCreate"); + histogram!("execution_time_seconds", "method" => "tokenTransactionCreate").record(elapsed); + counter!("requests_processed", "method" => "tokenTransactionCreate").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -176,8 +176,8 @@ pub async fn post_tokens_transactions_burn( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "tokenTransactionBurn"); - increment_counter!("requests_processed", "method" => "tokenTransactionBurn"); + histogram!("execution_time_seconds", "method" => "tokenTransactionBurn").record(elapsed); + counter!("requests_processed", "method" => "tokenTransactionBurn").increment(1); Ok(Json(TransactionResponse::from(transaction))) } @@ -196,8 +196,8 @@ pub async fn post_tokens_transactions_mint( .map(From::from); let elapsed = start.elapsed(); - histogram!("execution_time_seconds", elapsed, "method" => "tokenTransactionMint"); - increment_counter!("requests_processed", "method" => "tokenTransactionMint"); + histogram!("execution_time_seconds", "method" => "tokenTransactionMint").record(elapsed); + counter!("requests_processed", "method" => "tokenTransactionMint").increment(1); Ok(Json(TransactionResponse::from(transaction))) } diff --git a/src/api/docs.rs b/src/api/docs.rs index cac5d58..f2daa78 100644 --- a/src/api/docs.rs +++ b/src/api/docs.rs @@ -14,7 +14,7 @@ pub fn route() -> ApiRouter { // As a result, the `serve_redoc` route will // have the `text/html` content-type correctly set // with a 200 status. - aide::gen::infer_responses(true); + aide::generate::infer_responses(true); let router = ApiRouter::new() .route( @@ -27,7 +27,7 @@ pub fn route() -> ApiRouter { // Afterwards we disable response inference because // it might be incorrect for other routes. - aide::gen::infer_responses(false); + aide::generate::infer_responses(false); router } diff --git a/src/api/error.rs b/src/api/error.rs index eaddb77..46839c8 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -46,9 +46,6 @@ pub enum Error { #[error("an error occurred with oneshot channel")] RecvError(#[from] oneshot::error::RecvError), - #[error("an error occurred with tokens")] - TokensJson(#[from] nekoton_abi::TokensJsonError), - #[error("an error occurred with hex conversion")] FromHexError(#[from] hex::FromHexError), @@ -88,7 +85,6 @@ impl Error { | Self::Anyhow(_) | Self::Ed25519(_) | Self::RecvError(_) - | Self::TokensJson(_) | Self::FromHexError(_) | Self::TryFromSliceError(_) => StatusCode::INTERNAL_SERVER_ERROR, Error::TonService(e) => e.status_code(), @@ -140,12 +136,6 @@ impl Error { tracing::error!("RecvError error: {:?}", e); } - Self::TokensJson(ref e) => { - // TODO: we probably want to use `tracing` instead - // so that this gets linked to the HTTP request by `TraceLayer`. - tracing::error!("TokensJson error: {:?}", e); - } - Self::FromHexError(ref e) => { // TODO: we probably want to use `tracing` instead // so that this gets linked to the HTTP request by `TraceLayer`. diff --git a/src/api/mod.rs b/src/api/mod.rs index ebe06c4..5793a63 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -11,7 +11,7 @@ use axum::http::Method; use futures_util::future::BoxFuture; use metrics::{describe_counter, describe_histogram}; use metrics_exporter_prometheus::Matcher; -use schemars::schema::{InstanceType, SchemaObject}; +use schemars::{json_schema, Schema, SchemaGenerator}; use tower::ServiceBuilder; use tower_http::cors::{AllowHeaders, AllowMethods, AllowOrigin, CorsLayer}; use tower_http::trace::TraceLayer; @@ -139,14 +139,9 @@ fn api_docs(api: TransformOpenApi) -> TransformOpenApi { .summary("Tycho Wallet indexer") } -pub(super) fn int_schema(_: &mut schemars::SchemaGenerator) -> schemars::schema::Schema { - let object_schema = schemars::schema::SchemaObject { - instance_type: Some(InstanceType::Number.into()), - ..Default::default() - }; - object_schema.into() +pub(super) fn int_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({"type": "number"}) } -pub(super) fn any_schema(_: &mut schemars::SchemaGenerator) -> schemars::schema::Schema { - let object_schema = SchemaObject::default(); - object_schema.into() +pub(super) fn any_schema(_: &mut SchemaGenerator) -> Schema { + json_schema!({}) } diff --git a/src/api/requests/misc.rs b/src/api/requests/misc.rs index db5d711..18098ea 100644 --- a/src/api/requests/misc.rs +++ b/src/api/requests/misc.rs @@ -2,7 +2,7 @@ use bigdecimal::BigDecimal; use schemars::JsonSchema; use serde::Deserialize; -use ton_abi::Param; +use tycho_types::abi::{AbiHeaderType, NamedAbiType}; use uuid::Uuid; use crate::api::any_schema; @@ -22,24 +22,24 @@ pub struct FunctionDetailsDTO { pub function_name: String, pub input_params: Vec, #[schemars(schema_with = "any_schema")] - pub output_params: Vec, + pub output_params: Vec, #[schemars(schema_with = "any_schema")] - pub headers: Vec, + pub headers: Vec, } #[derive(Deserialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct InputParamDTO { + pub value: String, #[schemars(schema_with = "any_schema")] - pub param: Param, - pub value: serde_json::Value, + pub abi_type: NamedAbiType, } impl From for InputParam { fn from(i: InputParamDTO) -> Self { Self { - param: i.param, value: i.value, + abi_type: i.abi_type, } } } diff --git a/src/api/requests/transactions.rs b/src/api/requests/transactions.rs index d37bda3..0b36e00 100644 --- a/src/api/requests/transactions.rs +++ b/src/api/requests/transactions.rs @@ -1,6 +1,5 @@ use bigdecimal::BigDecimal; use derive_more::Constructor; -use nekoton_utils::TrustMe; use num_traits::FromPrimitive; use schemars::JsonSchema; @@ -128,7 +127,7 @@ impl From for TokenTransactionSend { notify_receiver: c.notify_receiver.unwrap_or(false), fee: c .fee - .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).unwrap()), payload: c.payload, } } @@ -157,7 +156,7 @@ impl From for TokenTransactionBurn { value: c.value, fee: c .fee - .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).unwrap()), } } } @@ -188,10 +187,10 @@ impl From for TokenTransactionMint { notify: c.notify.unwrap_or(false), deploy_wallet_value: c .deploy_wallet_value - .unwrap_or_else(|| BigDecimal::from_u64(DEPLOY_TOKEN_VALUE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(DEPLOY_TOKEN_VALUE).unwrap()), fee: c .fee - .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).trust_me()), + .unwrap_or_else(|| BigDecimal::from_u64(TOKEN_FEE).unwrap()), } } } diff --git a/src/api/responses/address.rs b/src/api/responses/address.rs index 8de8900..4169879 100644 --- a/src/api/responses/address.rs +++ b/src/api/responses/address.rs @@ -2,7 +2,6 @@ use std::str::FromStr; use bigdecimal::BigDecimal; use derive_more::Constructor; -use nekoton_utils::TrustMe; use tycho_types::models::StdAddr; use schemars::JsonSchema; @@ -108,7 +107,7 @@ pub struct AddressBalanceDataResponse { impl AddressBalanceDataResponse { pub fn new(a: AddressDb, b: NetworkAddressData) -> Self { - let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).trust_me(); + let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); Self { @@ -172,7 +171,7 @@ pub struct AddressInfoDataResponse { impl AddressInfoDataResponse { pub fn new(a: AddressDb) -> Self { - let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).trust_me(); + let account = StdAddr::from_str(&format!("{}:{}", a.workchain_id, a.hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); Self { @@ -236,7 +235,7 @@ pub struct TokenBalanceDataResponse { impl TokenBalanceDataResponse { pub fn new(a: TokenBalanceFromDb, b: NetworkTokenAddressData) -> Self { let account = - StdAddr::from_str(&format!("{}:{}", a.account_workchain_id, a.account_hex)).trust_me(); + StdAddr::from_str(&format!("{}:{}", a.account_workchain_id, a.account_hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); Self { diff --git a/src/api/responses/misc.rs b/src/api/responses/misc.rs index b704685..0254236 100644 --- a/src/api/responses/misc.rs +++ b/src/api/responses/misc.rs @@ -1,9 +1,8 @@ -use crate::models::WhitelistedTokenFromDb; -use nekoton_contracts::tip3_any::TokenWalletVersion; - use schemars::JsonSchema; use serde::Serialize; +use crate::{models::WhitelistedTokenFromDb, utils::token_wallets::models::TokenWalletVersion}; + #[derive(Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct ResubscribeResponse {} @@ -11,7 +10,7 @@ pub struct ResubscribeResponse {} #[derive(Serialize, JsonSchema)] #[serde(rename_all = "camelCase")] pub struct ReadContractResponse { - pub object: serde_json::Value, + pub output: Vec, } #[derive(Serialize, JsonSchema)] @@ -62,3 +61,10 @@ pub struct TokenWhitelistResponse { pub count: i32, pub items: Vec, } + +#[derive(Serialize, JsonSchema)] +#[serde(rename_all = "camelCase")] +pub struct OutputParamDTO { + pub abi_value: String, + pub name: String, +} diff --git a/src/api/responses/transactions.rs b/src/api/responses/transactions.rs index 2a17530..17b8a83 100644 --- a/src/api/responses/transactions.rs +++ b/src/api/responses/transactions.rs @@ -1,7 +1,8 @@ use std::str::FromStr; +use base64::{engine::general_purpose, Engine as _}; + use bigdecimal::BigDecimal; -use nekoton_utils::pack_std_smc_addr; use tycho_types::models::StdAddr; use schemars::JsonSchema; @@ -91,15 +92,14 @@ impl From for TransactionDataResponse { .into_iter() .map(|output| { let output_address = - nekoton_utils::repack_address(&output.recipient_address.0) - .unwrap_or_default(); + StdAddr::from_str(&output.recipient_address.0).unwrap_or_default(); let output_base64url = - Address(pack_std_smc_addr(true, &output_address, true).unwrap()); + Address(output_address.display_base64_url(true).to_string()); TransactionOutput { value: output.value, recipient: Account { - workchain_id: output_address.workchain_id(), - hex: Address(output_address.address().to_hex_string()), + workchain_id: output_address.workchain as i32, + hex: Address(output_address.address.to_string()), base64url: output_base64url, }, } @@ -251,7 +251,9 @@ impl From for TokenTransactionDataResponse { let account = StdAddr::from_str(&format!("{}:{}", c.account_workchain_id, c.account_hex)).unwrap(); let base64url = Address(account.display_base64_url(true).to_string()); - let payload = c.payload.map(base64::encode); + let payload = c + .payload + .map(|value| general_purpose::STANDARD.encode(value)); TokenTransactionDataResponse { id: c.id, diff --git a/src/api/router/address.rs b/src/api/router/address.rs index 37da9b5..fd076a2 100644 --- a/src/api/router/address.rs +++ b/src/api/router/address.rs @@ -34,14 +34,14 @@ pub fn router() -> ApiRouter { taged("address"), ) .api_route_with( - "/:address", + "/{address}", get_with(controllers::get_address_balance, |op| { op.response::<200, Json>() }), taged("address"), ) .api_route_with( - "/:address/info", + "/{address}/info", get_with(controllers::get_address_info, |op| { op.response::<200, Json>() }), diff --git a/src/api/router/events.rs b/src/api/router/events.rs index 5289874..a08f4d4 100644 --- a/src/api/router/events.rs +++ b/src/api/router/events.rs @@ -31,7 +31,7 @@ pub fn router() -> ApiRouter { taged("events"), ) .api_route_with( - "/id/:id", + "/id/{id}", get_with(controllers::get_events_id, |op| { op.response::<200, Json>() }), diff --git a/src/api/router/mod.rs b/src/api/router/mod.rs index 677acfa..b4bc2c3 100644 --- a/src/api/router/mod.rs +++ b/src/api/router/mod.rs @@ -25,10 +25,12 @@ pub fn router( ) -> ApiRouter { describe_gauge!("in_flight_requests", "number of inflight requests"); let (in_flight_requests_layer, counter) = InFlightRequestsLayer::pair(); - tokio::spawn(async { + let in_flight_gauge = gauge!("in_flight_requests"); + tokio::spawn(async move { counter - .run_emitter(Duration::from_secs(5), |count| async move { - gauge!("in_flight_requests", count as f64) + .run_emitter(Duration::from_secs(5), move |count| { + let gauge = in_flight_gauge.clone(); + async move { gauge.set(count as f64) } }) .await; }); diff --git a/src/api/router/tokens.rs b/src/api/router/tokens.rs index 7b98852..464b290 100644 --- a/src/api/router/tokens.rs +++ b/src/api/router/tokens.rs @@ -21,21 +21,21 @@ pub fn router() -> ApiRouter { taged("tokens"), ) .api_route_with( - "/address/:address", + "/address/{address}", get_with(controllers::get_token_address_balance, |op| { op.response::<200, Json>() }), taged("tokens"), ) .api_route_with( - "/transactions/id/:internal_id", + "/transactions/id/{internal_id}", get_with(controllers::get_tokens_transactions_id, |op| { op.response::<200, Json>() }), taged("tokens"), ) .api_route_with( - "/transactions/mh/:message_hash", + "/transactions/mh/{message_hash}", get_with(controllers::get_tokens_transactions_mh, |op| { op.response::<200, Json>() }), diff --git a/src/api/router/transactions.rs b/src/api/router/transactions.rs index 8d5c87a..6533b3c 100644 --- a/src/api/router/transactions.rs +++ b/src/api/router/transactions.rs @@ -31,21 +31,21 @@ pub fn router() -> ApiRouter { taged("transactions"), ) .api_route_with( - "/id/:id", + "/id/{id}", get_with(controllers::get_transactions_id, |op| { op.response::<200, Json>() }), taged("transactions"), ) .api_route_with( - "/h/:hash", + "/h/{hash}", get_with(controllers::get_transactions_h, |op| { op.response::<200, Json>() }), taged("transactions"), ) .api_route_with( - "/mh/:message_hash", + "/mh/{message_hash}", get_with(controllers::get_transactions_mh, |op| { op.response::<200, Json>() }), diff --git a/src/client/callback/mod.rs b/src/client/callback/mod.rs index d60fe89..95470f2 100644 --- a/src/client/callback/mod.rs +++ b/src/client/callback/mod.rs @@ -1,6 +1,6 @@ use anyhow::Result; +use base64::{engine::general_purpose, Engine as _}; use chrono::Utc; -use nekoton_utils::TrustMe; use reqwest::{Method, StatusCode, Url}; use crate::models::*; @@ -13,7 +13,7 @@ pub struct CallbackClient { impl CallbackClient { pub fn new() -> Self { Self { - client: reqwest::ClientBuilder::new().build().trust_me(), + client: reqwest::ClientBuilder::new().build().unwrap(), } } } @@ -64,5 +64,5 @@ impl CallbackClient { fn calc_sign(body: String, url: String, timestamp_ms: i64, secret: String) -> String { let concat = format!("{}{}{}", timestamp_ms, url, body); let calculated_signature = hmac_sha256::HMAC::mac(concat.as_bytes(), secret.as_bytes()); - base64::encode(calculated_signature) + general_purpose::STANDARD.encode(calculated_signature) } diff --git a/src/client/ton/mod.rs b/src/client/ton/mod.rs index 47f66b7..23b3a26 100644 --- a/src/client/ton/mod.rs +++ b/src/client/ton/mod.rs @@ -1,24 +1,22 @@ use std::str::FromStr; use std::sync::Arc; -use anyhow::anyhow; use axum::http::StatusCode; use bigdecimal::{BigDecimal, ToPrimitive}; -use ed25519_dalek::{Keypair, PublicKey, SecretKey, Signer}; -use nekoton::core::models::Expiration; -use nekoton::core::ton_wallet::multisig::DeployParams; -use nekoton::core::ton_wallet::{MultisigType, TransferAction}; -use nekoton::core::InternalMessage; -use nekoton::crypto::{SignedMessage, UnsignedMessage}; -use nekoton_abi::MessageBuilder; -use nekoton_utils::{SimpleClock, TrustMe}; +use ed25519_dalek::Signer; +use ed25519_dalek::VerifyingKey; +use nekoton_core::contracts::function_ext::ExecutionOutput; +use nekoton_core::contracts::function_ext::FunctionExt; use num_bigint::BigUint; use num_traits::FromPrimitive; use tokio::sync::oneshot; -use ton_block::{GetRepresentationHash, MsgAddressInt}; -use ton_types::{deserialize_tree_of_cells, SliceData, UInt256}; -use tycho_types::cell::HashBytes; -use tycho_types::models::StdAddr; +use tycho_types::abi::extend_signature_with_id; +use tycho_types::abi::{Function, NamedAbiValue, UnsignedExternalMessage}; +use tycho_types::boc::Boc; +use tycho_types::cell::{CellBuilder, HashBytes}; +use tycho_types::models::StdAddrFormat; +use tycho_types::models::{GlobalCapabilities, OwnedMessage, SignatureContext, StdAddr}; +use tycho_util::time::now_sec; use uuid::Uuid; use crate::api::*; @@ -27,10 +25,11 @@ use crate::prelude::*; use crate::services::*; use crate::sqlx_client::*; use crate::ton_core::*; +use crate::utils::mnemonic::{derive_from_phrase, generate_key, Bip39MnemonicData, MnemonicType}; +use crate::utils::ton_wallet::multisig::DeployParams; +use crate::utils::ton_wallet::MultisigType; use crate::utils::*; -mod utils; - #[derive(Clone)] pub struct TonClient { ton_core: Arc, @@ -52,9 +51,10 @@ impl TonClient { .await? .into_iter() .map(|item| { - StdAddr::from_str(&format!("{}:{}", item.workchain_id, item.hex)).trust_me() + StdAddr::from_str(&format!("{}:{}", item.workchain_id, item.hex)) + .map_err(From::from) }) - .collect::>(); + .collect::>>()?; // Subscribe to ton accounts let owner_accounts = owner_addresses @@ -68,52 +68,48 @@ impl TonClient { } pub async fn create_address(&self, payload: CreateAddress) -> Result { - let generated_key = nekoton::crypto::generate_key(nekoton::crypto::MnemonicType::Bip39( - nekoton::crypto::Bip39MnemonicData::labs_old(0), - )); + let generated_key = generate_key(MnemonicType::Bip39(Bip39MnemonicData::labs_old(0))); - let Keypair { public, secret } = nekoton::crypto::derive_from_phrase( - &generated_key.words.join(" "), - generated_key.account_type, - )?; + let signing_key = + derive_from_phrase(&generated_key.words.join(" "), generated_key.account_type)?; + + let public = signing_key.verifying_key(); let workchain_id = payload.workchain_id.unwrap_or_default(); let account_type = payload.account_type.unwrap_or_default(); let address = match account_type { AccountType::HighloadWallet => { - nekoton::core::ton_wallet::highload_wallet_v2::compute_contract_address( + ton_wallet::highload_wallet_v2::compute_contract_address( &public, workchain_id as i8, ) } - AccountType::Wallet => nekoton::core::ton_wallet::wallet_v3::compute_contract_address( + AccountType::Wallet => { + ton_wallet::wallet_v3::compute_contract_address(&public, workchain_id as i8) + } + AccountType::WalletV5R1 => { + ton_wallet::wallet_v5r1::compute_contract_address(&public, workchain_id as i8) + } + AccountType::SafeMultisig => ton_wallet::multisig::compute_contract_address( &public, + MultisigType::SafeMultisigWallet, workchain_id as i8, ), - AccountType::SafeMultisig => { - nekoton::core::ton_wallet::multisig::compute_contract_address( - &public, - MultisigType::SafeMultisigWallet, - workchain_id as i8, - ) - } AccountType::EverWallet => { - nekoton::core::ton_wallet::ever_wallet::compute_contract_address( - &public, - workchain_id as i8, - ) + ton_wallet::ever_wallet::compute_contract_address(&public, workchain_id as i8) } - }; + }?; let (custodians, confirmations) = match account_type { AccountType::SafeMultisig => ( Some(payload.custodians.unwrap_or(1)), Some(payload.confirmations.unwrap_or(1)), ), - AccountType::HighloadWallet | AccountType::Wallet | AccountType::EverWallet => { - (None, None) - } + AccountType::HighloadWallet + | AccountType::Wallet + | AccountType::WalletV5R1 + | AccountType::EverWallet => (None, None), }; if let (Some(custodians), Some(confirmations)) = (custodians, confirmations) { @@ -132,14 +128,15 @@ impl TonClient { let mut custodians = Vec::with_capacity(public_keys.len()); for key in public_keys { - custodians.push( - PublicKey::from_bytes(&hex::decode(key).map_err(|_| { - TonServiceError::WrongInput("Invalid custodian".to_string()) - })?) - .map_err(|_| { - TonServiceError::WrongInput("Invalid custodian".to_string()) - })?, - ); + let decoded_key = hex::decode(key).map_err(|_| { + TonServiceError::WrongInput("Invalid custodian".to_string()) + })?; + let mut key = [0u8; 32]; + key.copy_from_slice(&decoded_key); + + custodians.push(VerifyingKey::from_bytes(&key).map_err(|_| { + TonServiceError::WrongInput("Invalid custodian".to_string()) + })?); } custodians.push(public); @@ -150,20 +147,22 @@ impl TonClient { Some(custodians) } - AccountType::HighloadWallet | AccountType::Wallet | AccountType::EverWallet => None, + AccountType::HighloadWallet + | AccountType::Wallet + | AccountType::WalletV5R1 + | AccountType::EverWallet => None, }; // Subscribe to accounts - let account = HashBytes::from_str(&address.address().to_hex_string()) - .map_err(|_| anyhow!("Couldn't parse address"))?; + let account = address.address; self.ton_core.add_ton_account_subscription([account]); Ok(CreatedAddress { - workchain_id: address.workchain_id(), - hex: address.address().to_hex_string(), - base64url: nekoton_utils::pack_std_smc_addr(true, &address, true)?, + workchain_id: address.workchain as i32, + hex: address.address.to_string(), + base64url: address.display_base64_url(true).to_string(), public_key: public.to_bytes().to_vec(), - private_key: secret.to_bytes().to_vec(), + private_key: signing_key.to_bytes().to_vec(), account_type, custodians, confirmations, @@ -171,31 +170,24 @@ impl TonClient { }) } - pub async fn get_address_info( - &self, - owner: &MsgAddressInt, - ) -> Result { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + pub async fn get_address_info(&self, owner: &StdAddr) -> Result { + let account = owner.address; let contract = match self.ton_core.get_contract_state(&account) { Ok(contract) => contract, Err(_) => return Ok(NetworkAddressData::uninit(owner)), }; - let network_balance = - BigDecimal::from_u128(contract.account.storage.balance.grams.as_u128()) - .ok_or(TonClientError::ParseBigDecimal)?; - - let (last_transaction_hash, last_transaction_lt) = - utils::parse_last_transaction(&contract.last_transaction_id); + let network_balance = BigDecimal::from_u128(contract.account.balance.tokens.into_inner()) + .ok_or(TonClientError::ParseBigDecimal)?; Ok(NetworkAddressData { - workchain_id: contract.account.addr.workchain_id(), - hex: contract.account.addr.address().to_hex_string(), - account_status: contract.account.storage.state.into(), + workchain_id: contract.account.address.workchain(), + hex: contract.account.address.to_string(), + account_status: contract.account.state.into(), network_balance, - last_transaction_hash, - last_transaction_lt, - sync_u_time: contract.timings.current_utime(&SimpleClock) as i64, + last_transaction_hash: Some(contract.last_transaction_hash.to_string()), + last_transaction_lt: Some(contract.account.last_trans_lt.to_string()), + sync_u_time: 0i64, // TODO fix }) } @@ -204,59 +196,68 @@ impl TonClient { address: &AddressDb, public_key: &[u8], private_key: &[u8], - ) -> Result, Error> { - let public_key = PublicKey::from_bytes(public_key)?; + ) -> Result, Error> { + let mut key = [0u8; 32]; + key.copy_from_slice(public_key); + + let public_key = VerifyingKey::from_bytes(&key)?; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; let unsigned_message = match address.account_type { AccountType::SafeMultisig => { - let custodians: Vec = - serde_json::from_value(address.custodians_public_keys.clone().trust_me()) - .trust_me(); + let custodians: Vec = serde_json::from_value( + address.custodians_public_keys.clone().unwrap_or_default(), + )?; let owners = custodians .into_iter() - .map(|item| PublicKey::from_bytes(&hex::decode(item).trust_me()).trust_me()) - .collect::>(); - - nekoton::core::ton_wallet::multisig::prepare_deploy( - &SimpleClock, + .map(|item| { + let mut key = [0u8; 32]; + key.copy_from_slice(&hex::decode(item).map_err(anyhow::Error::from)?); + VerifyingKey::from_bytes(&key).map_err(anyhow::Error::from) + }) + .collect::, anyhow::Error>>()?; + + ton_wallet::multisig::prepare_deploy( &public_key, MultisigType::SafeMultisigWallet, address.workchain_id as i8, - Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT), + expire_at, DeployParams { owners: &owners, - req_confirms: address.confirmations.trust_me() as u8, + req_confirms: address.confirmations.unwrap_or_default() as u8, expiration_time: None, }, )? } - AccountType::EverWallet => nekoton::core::ton_wallet::ever_wallet::prepare_deploy( - &SimpleClock, + AccountType::EverWallet => ton_wallet::ever_wallet::prepare_deploy( &public_key, address.workchain_id as i8, - Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT), + expire_at, )?, - AccountType::HighloadWallet | AccountType::Wallet => { + AccountType::WalletV5R1 | AccountType::HighloadWallet | AccountType::Wallet => { return Ok(None); } }; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), }; - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let owned_message = unsigned_message.sign(&key_pair, context)?; + + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; + let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { id: Uuid::new_v4(), - message_hash: signed_message.message.hash()?.to_hex_string(), + message_hash: hash.to_string(), account_workchain_id: address.workchain_id, account_hex: address.hex.clone(), original_value: None, @@ -265,7 +266,11 @@ impl TonClient { bounce: false, }; - Ok(Some((sent_transaction, signed_message))) + Ok(Some(PrepareResult { + sent_transaction, + owned_message, + expire_at, + })) } pub async fn prepare_transaction( @@ -275,79 +280,94 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { + ) -> Result { let original_value = transaction.outputs.iter().map(|o| o.value.clone()).sum(); let original_outputs = serde_json::to_value(transaction.outputs.clone())?; let bounce = transaction.bounce.unwrap_or_default(); - let public_key = PublicKey::from_bytes(public_key)?; - let address = nekoton_utils::repack_address(&transaction.from_address.0)?; + let mut key = [0u8; 32]; + key.copy_from_slice(public_key); - let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); + let public_key = VerifyingKey::from_bytes(&key)?; + + let (address, _) = StdAddr::from_str_ext(&transaction.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; // parse input payload - let payload_cell = match &transaction.payload { - None => None, - Some(s) => { - let bytes = base64::decode(s).map_err(anyhow::Error::from)?; - let mut slice = &bytes[..]; - let tree_of_cells = deserialize_tree_of_cells(&mut slice)?; - Some(tree_of_cells) - } + let body = transaction + .payload + .map(Boc::decode_base64) + .transpose() + .map_err(anyhow::Error::from)?; + + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), }; - let transfer_action = match account_type { + let owned_message = match account_type { AccountType::HighloadWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; - let mut gifts: Vec = vec![]; + let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = nekoton_utils::repack_address(&item.recipient_address.0)?; + let (destination, _) = + StdAddr::from_str_ext(&item.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let amount = item .value .to_u128() .ok_or(TonClientError::ParseBigDecimal)?; - let body = payload_cell - .as_ref() - .map(|c| SliceData::load_cell(c.clone())) - .transpose()?; - gifts.push(nekoton::core::ton_wallet::Gift { + gifts.push(ton_wallet::Gift { flags: flags.into(), bounce, destination, amount, - body, + body: body.clone(), state_init: None, }); } - nekoton::core::ton_wallet::highload_wallet_v2::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::highload_wallet_v2::prepare_transfer( &public_key, ¤t_state, gifts, - expiration, - )? + expire_at, + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), self.ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::Wallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; let recipient = transaction .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = nekoton_utils::repack_address(&recipient.recipient_address.0)?; + let (destination, _) = + StdAddr::from_str_ext(&recipient.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let amount = recipient .value .to_u128() .ok_or(TonClientError::ParseBigDecimal)?; let flags = recipient.output_type.clone().unwrap_or_default(); - let body = payload_cell.map(SliceData::load_cell).transpose()?; - let gifts = vec![nekoton::core::ton_wallet::Gift { + let gifts = vec![ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -355,26 +375,69 @@ impl TonClient { body, state_init: None, }]; - let seqno_offset = nekoton::core::ton_wallet::wallet_v3::estimate_seqno_offset( - &SimpleClock, + let seqno_offset = + ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); + let unsigned_message = ton_wallet::wallet_v3::prepare_transfer( + &public_key, ¤t_state, - &[], - ); - nekoton::core::ton_wallet::wallet_v3::prepare_transfer( - &SimpleClock, + seqno_offset, + gifts, + expire_at, + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), self.ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? + } + AccountType::WalletV5R1 => { + let account = address.address; + let current_state = self.ton_core.get_contract_state(&account)?.account; + + let mut gifts: Vec = vec![]; + for item in transaction.outputs { + let flags = item.output_type.unwrap_or_default(); + let (destination, _) = + StdAddr::from_str_ext(&item.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let amount = item + .value + .to_u128() + .ok_or(TonClientError::ParseBigDecimal)?; + + gifts.push(ton_wallet::Gift { + flags: flags.into(), + bounce, + destination, + amount, + body: body.clone(), + state_init: None, + }); + } + + let seqno_offset = 0; // TODO: implement seqno offset if needed + + let unsigned_message = ton_wallet::wallet_v5r1::prepare_transfer( &public_key, ¤t_state, seqno_offset, gifts, - expiration, - )? + expire_at, + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), self.ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::SafeMultisig => { let recipient = transaction .outputs .first() .ok_or(TonClientError::RecipientNotFound)?; - let destination = nekoton_utils::repack_address(&recipient.recipient_address.0)?; + let (destination, _) = + StdAddr::from_str_ext(&recipient.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let amount = recipient .value .to_u128() @@ -385,9 +448,7 @@ impl TonClient { None => return Err(TonClientError::CustodiansNotFound.into()), }; - let body = payload_cell.map(SliceData::load_cell).transpose()?; - - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -395,78 +456,70 @@ impl TonClient { body, state_init: None, }; - nekoton::core::ton_wallet::multisig::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::multisig::prepare_transfer( MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, address.clone(), gift, - expiration, - )? + expire_at, + )?; + + unsigned_message.sign(&key_pair, context)? } AccountType::EverWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; - let mut gifts: Vec = vec![]; + let mut gifts: Vec = vec![]; for item in transaction.outputs { let flags = item.output_type.unwrap_or_default(); - let destination = nekoton_utils::repack_address(&item.recipient_address.0)?; + let (destination, _) = + StdAddr::from_str_ext(&item.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let amount = item .value .to_u128() .ok_or(TonClientError::ParseBigDecimal)?; - let body = payload_cell - .as_ref() - .map(|c| SliceData::load_cell(c.clone())) - .transpose()?; - gifts.push(nekoton::core::ton_wallet::Gift { + gifts.push(ton_wallet::Gift { flags: flags.into(), bounce, destination, amount, - body, + body: body.clone(), state_init: None, }); } - nekoton::core::ton_wallet::ever_wallet::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::ever_wallet::prepare_transfer( &public_key, ¤t_state, address.clone(), gifts, - expiration, - )? - } - }; - let unsigned_message = match transfer_action { - TransferAction::Sign(unsigned_message) => unsigned_message, - TransferAction::DeployFirst => { - return Err(TonClientError::AccountNotDeployed(address.to_string()).into()) + expire_at, + )?; + + unsigned_message.sign(&key_pair, context)? } }; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; + let message_hash = cell_builder.repr_hash(); + let sent_transaction = SentTransaction { id: transaction.id, - message_hash: signed_message.message.hash()?.to_hex_string(), - account_workchain_id: address.workchain_id(), - account_hex: address.address().to_hex_string(), + message_hash: message_hash.to_string(), + account_workchain_id: address.workchain as i32, + account_hex: address.address.to_string(), original_value: Some(original_value), original_outputs: Some(original_outputs), aborted: false, bounce, }; - Ok((sent_transaction, signed_message)) + Ok(PrepareResult { + sent_transaction, + owned_message, + expire_at, + }) } pub async fn prepare_confirm_transaction( @@ -474,37 +527,46 @@ impl TonClient { transaction: TransactionConfirm, public_key: &[u8], private_key: &[u8], - ) -> Result<(SentTransaction, SignedMessage), Error> { - let public_key = PublicKey::from_bytes(public_key)?; - let address = nekoton_utils::repack_address(&transaction.address.0)?; + ) -> Result { + let mut key = [0u8; 32]; + key.copy_from_slice(public_key); + + let public_key = VerifyingKey::from_bytes(&key)?; - let account_workchain_id = address.workchain_id(); - let account_hex = address.address().to_hex_string(); + let (address, _) = StdAddr::from_str_ext(&transaction.address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; - let unsigned_message = nekoton::core::ton_wallet::multisig::prepare_confirm_transaction( - &SimpleClock, + let account_workchain_id = address.workchain as i32; + let account_hex = address.address.to_string(); + + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; + + let unsigned_message = ton_wallet::multisig::prepare_confirm_transaction( MultisigType::SafeMultisigWallet, &public_key, address, transaction.transaction_id, - Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT), + expire_at, )?; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); + + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), }; - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let owned_message = unsigned_message.sign(&key_pair, context)?; + + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; + let message_hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { id: transaction.id, - message_hash: signed_message.message.hash()?.to_hex_string(), + message_hash: message_hash.to_string(), account_workchain_id, account_hex, original_value: None, @@ -513,19 +575,25 @@ impl TonClient { bounce: false, }; - Ok((sent_transaction, signed_message)) + Ok(PrepareResult { + sent_transaction, + owned_message, + expire_at, + }) } pub async fn get_token_address_info( &self, - owner: &MsgAddressInt, - root_address: &MsgAddressInt, + owner: &StdAddr, + root_address: &StdAddr, ) -> Result { - let root_account = UInt256::from_be_bytes(&root_address.address().get_bytestring(0)); + let root_account = root_address.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; - let token_address = get_token_wallet_address(&root_contract, owner)?; - let token_account = UInt256::from_be_bytes(&token_address.address().get_bytestring(0)); + let context = self.ton_core.blockchain_context(); + + let token_address = get_token_wallet_address(root_contract, context.clone(), owner)?; + let token_account = token_address.address; let token_contract = match self.ton_core.get_contract_state(&token_account) { Ok(contract) => contract, Err(_) => { @@ -536,22 +604,22 @@ impl TonClient { } }; - let (version, network_balance) = get_token_wallet_basic_info(&token_contract)?; - let sync_u_time = token_contract.timings.current_utime(&SimpleClock) as i64; + let account_status = token_contract.account.state.clone().into(); + let last_transaction_hash = Some(token_contract.last_transaction_hash.to_string()); + let last_transaction_lt = Some(token_contract.account.last_trans_lt.to_string()); - let (last_transaction_hash, last_transaction_lt) = - utils::parse_last_transaction(&token_contract.last_transaction_id); + let (version, network_balance) = get_token_wallet_basic_info(token_contract, context)?; Ok(NetworkTokenAddressData { - workchain_id: token_address.workchain_id(), - hex: token_address.address().to_hex_string(), + workchain_id: token_address.workchain as i32, + hex: token_address.address.to_string(), root_address: root_address.to_string(), version: version.to_string(), network_balance, - account_status: token_contract.account.storage.state.into(), + account_status, last_transaction_hash, last_transaction_lt, - sync_u_time, + sync_u_time: 0, // TODO: fix }) } @@ -562,24 +630,31 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { - let owner = nekoton_utils::repack_address(&input.from_address.0)?; + ) -> Result { + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let token_owner_db = self .sqlx_client .get_token_address( - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; - let token_wallet = nekoton_utils::repack_address(&token_owner_db.address)?; + let token_wallet = + StdAddr::from_str(&token_owner_db.address).map_err(anyhow::Error::from)?; - let recipient = nekoton_utils::repack_address(&input.recipient_address.0)?; - let destination = nekoton::core::models::TransferRecipient::OwnerWallet(recipient); + let (destination, _) = + StdAddr::from_str_ext(&input.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => nekoton_utils::repack_address(send_gas_to.0.as_str())?, + Some(send_gas_to) => { + let (send_gas_to, _) = StdAddr::from_str_ext(&send_gas_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + send_gas_to + } None => owner.clone(), }; @@ -591,15 +666,13 @@ impl TonClient { let attached_amount = input.fee.to_u128().ok_or(TonClientError::ParseBigDecimal)?; // parse input payload - let payload_cell = match &input.payload { - None => None, - Some(s) => { - let bytes = base64::decode(s).map_err(anyhow::Error::from)?; - let mut slice = &bytes[..]; - let tree_of_cells = deserialize_tree_of_cells(&mut slice)?; - Some(tree_of_cells) - } - }; + + let body = input + .payload + .as_ref() + .map(Boc::decode_base64) + .transpose() + .map_err(anyhow::Error::from)?; let internal_message = prepare_token_transfer( owner.clone(), @@ -610,7 +683,7 @@ impl TonClient { send_gas_to, input.notify_receiver, attached_amount, - payload_cell.unwrap_or_default(), + body.unwrap_or_default(), )?; let res = build_token_transaction( @@ -634,25 +707,33 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { - let owner = nekoton_utils::repack_address(&input.from_address.0)?; + ) -> Result { + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let token_owner_db = self .sqlx_client .get_token_address( - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; - let token_wallet = nekoton_utils::repack_address(&token_owner_db.address)?; + + let token_wallet = + StdAddr::from_str(&token_owner_db.address).map_err(anyhow::Error::from)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => nekoton_utils::repack_address(send_gas_to.0.as_str())?, + Some(send_gas_to) => { + let (send_gas_to, _) = StdAddr::from_str_ext(&send_gas_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + send_gas_to + } None => owner.clone(), }; - let callback_to = nekoton_utils::repack_address(input.callback_to.0.as_str())?; + let (callback_to, _) = StdAddr::from_str_ext(&input.callback_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let version = token_owner_db.version.into(); @@ -693,15 +774,20 @@ impl TonClient { private_key: &[u8], account_type: &AccountType, custodians: &Option, - ) -> Result<(SentTransaction, SignedMessage), Error> { - let owner = nekoton_utils::repack_address(&input.owner_address.0)?; - let root_token = nekoton_utils::repack_address(&input.root_address.0)?; - let recipient = nekoton_utils::repack_address(&input.recipient_address.0)?; - - let root_account = UInt256::from_be_bytes(&root_token.address().get_bytestring(0)); + ) -> Result { + let (owner, _) = StdAddr::from_str_ext(&input.owner_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let (root_token, _) = StdAddr::from_str_ext(&input.root_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let (recipient, _) = + StdAddr::from_str_ext(&input.recipient_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + + let root_account = root_token.address; let root_contract = self.ton_core.get_contract_state(&root_account)?; + let context = self.ton_core.blockchain_context(); - let version = get_root_token_version(&root_contract)?; + let version = get_root_token_version(root_contract, context)?; let (value, _) = input.value.clone().as_bigint_and_exponent(); let tokens = value.to_biguint().ok_or(TonClientError::ParseBigUint)?; @@ -715,7 +801,11 @@ impl TonClient { .ok_or(TonClientError::ParseBigUint)?; let send_gas_to = match &input.send_gas_to { - Some(send_gas_to) => nekoton_utils::repack_address(send_gas_to.0.as_str())?, + Some(send_gas_to) => { + let (send_gas_to, _) = StdAddr::from_str_ext(&send_gas_to.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + send_gas_to + } None => owner.clone(), }; @@ -750,12 +840,13 @@ impl TonClient { pub async fn send_transaction( &self, - account: UInt256, - signed_message: SignedMessage, + account: HashBytes, + owned_message: OwnedMessage, + expire_at: u32, ) -> Result { let status = self .ton_core - .send_ton_message(&account, &signed_message.message, signed_message.expire_at) + .send_ton_message(account, owned_message, expire_at) .await?; Ok(status) @@ -763,8 +854,8 @@ impl TonClient { pub fn add_pending_message( &self, - account: UInt256, - message_hash: UInt256, + account: HashBytes, + message_hash: HashBytes, expire_at: u32, ) -> Result, Error> { let status = self @@ -800,14 +891,12 @@ impl TonClient { pub async fn run_local( &self, - contract_address: UInt256, - function: ton_abi::Function, - input: &[ton_abi::Token], + contract_address: HashBytes, + function: Function, + input: &[NamedAbiValue], responsible: bool, - ) -> anyhow::Result> { - use nekoton_abi::FunctionExt; - - let state = match self.ton_core.get_contract_state(&contract_address) { + ) -> anyhow::Result>> { + let mut state = match self.ton_core.get_contract_state(&contract_address) { Ok(a) => a, Err(e) => { tracing::error!("Failed to get contract state: {e:?}"); @@ -815,13 +904,18 @@ impl TonClient { } }; - let res = if responsible { - function.run_local_responsible(&SimpleClock, state.account, input, &[]) - } else { - function.run_local(&SimpleClock, state.account, input, &[]) - }; + let ExecutionOutput { values, exit_code } = function.run_local( + &mut state.account, + input, + responsible, + &mut self.ton_core.blockchain_context(), + )?; + + if exit_code != 0 { + return Err(anyhow::anyhow!("Non-zero result code: {exit_code}")); + } - res.map(Some) + Ok(Some(values)) } pub async fn prepare_signed_generic_message( @@ -835,9 +929,9 @@ impl TonClient { bounce: bool, account_type: &AccountType, custodians: &Option, - function: Option, - params: Option>, - ) -> Result { + function: Option, + params: Option>, + ) -> Result<(OwnedMessage, u32), Error> { let unsigned_message = self .prepare_generic_message( sender_addr, @@ -853,21 +947,20 @@ impl TonClient { ) .await?; - let public_key = PublicKey::from_bytes(public_key).unwrap_or_default(); + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + let expire_at = unsigned_message.expire_at(); + + let context = SignatureContext { + global_id: self.ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(self.ton_core.capabilities()), }; - let data_to_sign = ton_abi::extend_signature_with_id( - unsigned_message.hash(), - self.ton_core.signature_id(), - ); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let owned_message = unsigned_message.sign(&key_pair, context)?; - Ok(signed_message) + Ok((owned_message, expire_at)) } pub async fn prepare_generic_message( @@ -880,51 +973,35 @@ impl TonClient { bounce: bool, account_type: &AccountType, custodians: &Option, - function: Option, - params: Option>, - ) -> Result, Error> { - let address = nekoton_utils::repack_address(sender_addr)?; - let public_key = PublicKey::from_bytes(public_key)?; + function: Option, + params: Option>, + ) -> Result { + let mut key = [0u8; 32]; + key.copy_from_slice(public_key); - let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); + let public_key = VerifyingKey::from_bytes(&key)?; - let function_data = function.and_then(|x| { - let tokens = params.unwrap_or_default(); - let (func, _) = MessageBuilder::new(&x).build(); - func.encode_internal_input(&tokens).ok() - }); - let body = function_data.map(SliceData::load_builder).transpose()?; + let (address, _) = StdAddr::from_str_ext(sender_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; - let destination = nekoton_utils::repack_address(target_addr)?; - let amount = value.to_u128().ok_or(TonClientError::ParseBigDecimal)?; - let transfer_action = match account_type { - AccountType::Wallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); - let current_state = self.ton_core.get_contract_state(&account)?.account; + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; - let gifts = vec![nekoton::core::ton_wallet::Gift { - flags: execution_flag, - bounce, - destination, - amount, - body, - state_init: None, - }]; + let function_data = function.and_then(|function| { + let tokens = params.unwrap_or_default(); + function.encode_internal_input(&tokens).ok() + }); + let body = function_data + .map(|data| data.build()) + .transpose() + .map_err(anyhow::Error::from)?; - let seqno_offset = nekoton::core::ton_wallet::wallet_v3::estimate_seqno_offset( - &SimpleClock, - ¤t_state, - &[], - ); + let (destination, _) = StdAddr::from_str_ext(target_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; - nekoton::core::ton_wallet::wallet_v3::prepare_transfer( - &SimpleClock, - &public_key, - ¤t_state, - seqno_offset, - gifts, - expiration, - )? + let amount = value.to_u128().ok_or(TonClientError::ParseBigDecimal)?; + let unsigned_message = match account_type { + AccountType::Wallet | AccountType::WalletV5R1 | AccountType::HighloadWallet => { + return Err(anyhow::anyhow!("Not implemented").into()); } AccountType::SafeMultisig => { let has_multiple_owners = match custodians { @@ -932,7 +1009,7 @@ impl TonClient { None => return Err(TonClientError::CustodiansNotFound.into()), }; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: execution_flag, bounce, destination, @@ -941,42 +1018,20 @@ impl TonClient { state_init: None, }; - nekoton::core::ton_wallet::multisig::prepare_transfer( - &SimpleClock, + ton_wallet::multisig::prepare_transfer( MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, address, gift, - expiration, - )? - } - AccountType::HighloadWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); - let current_state = self.ton_core.get_contract_state(&account)?.account; - - let gift = nekoton::core::ton_wallet::Gift { - flags: execution_flag, - bounce, - destination, - amount, - body, - state_init: None, - }; - - nekoton::core::ton_wallet::highload_wallet_v2::prepare_transfer( - &SimpleClock, - &public_key, - ¤t_state, - vec![gift], - expiration, + expire_at, )? } AccountType::EverWallet => { - let account = UInt256::from_be_bytes(&address.address().get_bytestring(0)); + let account = address.address; let current_state = self.ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: execution_flag, bounce, destination, @@ -985,28 +1040,20 @@ impl TonClient { state_init: None, }; - nekoton::core::ton_wallet::ever_wallet::prepare_transfer( - &SimpleClock, + ton_wallet::ever_wallet::prepare_transfer( &public_key, ¤t_state, address, vec![gift], - expiration, + expire_at, )? } }; - let unsigned_message = match transfer_action { - TransferAction::Sign(unsigned_message) => unsigned_message, - TransferAction::DeployFirst => { - return Err(TonClientError::AccountNotDeployed(target_addr.to_string()).into()) - } - }; Ok(unsigned_message) } - pub fn add_ton_account_subscription(&self, account: UInt256) { - let account = HashBytes::from_slice(account.as_slice()); + pub fn add_ton_account_subscription(&self, account: HashBytes) { self.ton_core.add_ton_account_subscription([account]) } @@ -1048,13 +1095,13 @@ impl TonClientError { fn build_token_transaction( ton_core: &Arc, id: Uuid, - owner: MsgAddressInt, + owner: StdAddr, public_key: &[u8], private_key: &[u8], account_type: &AccountType, custodians: &Option, internal_message: InternalMessage, -) -> anyhow::Result<(SentTransaction, SignedMessage)> { +) -> anyhow::Result { let flags = TransactionSendOutputType::default(); let bounce = internal_message.bounce; @@ -1062,16 +1109,29 @@ fn build_token_transaction( let amount = internal_message.amount; let body = Some(internal_message.body); - let expiration = Expiration::Timeout(DEFAULT_EXPIRATION_TIMEOUT); + let mut key = [0u8; 32]; + key.copy_from_slice(public_key); + + let public_key = VerifyingKey::from_bytes(&key)?; + + let expire_at = now_sec() + DEFAULT_EXPIRATION_TIMEOUT; - let public_key = PublicKey::from_bytes(public_key).unwrap_or_default(); + let mut key = [0u8; 32]; + key.copy_from_slice(private_key); - let transfer_action = match account_type { + let key_pair = ed25519_dalek::SigningKey::from_bytes(&key); + + let context = SignatureContext { + global_id: ton_core.signature_id().unwrap_or_default(), + capabilities: GlobalCapabilities::new(ton_core.capabilities()), + }; + + let owned_message = match account_type { AccountType::HighloadWallet => { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1080,19 +1140,23 @@ fn build_token_transaction( state_init: None, }; - nekoton::core::ton_wallet::highload_wallet_v2::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::highload_wallet_v2::prepare_transfer( &public_key, ¤t_state, vec![gift], - expiration, - )? + expire_at, + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::Wallet => { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; - let gifts = vec![nekoton::core::ton_wallet::Gift { + let gifts = vec![ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1101,20 +1165,48 @@ fn build_token_transaction( state_init: None, }]; - let seqno_offset = nekoton::core::ton_wallet::wallet_v3::estimate_seqno_offset( - &SimpleClock, - ¤t_state, - &[], - ); + let seqno_offset = ton_wallet::wallet_v3::estimate_seqno_offset(¤t_state, &[]); - nekoton::core::ton_wallet::wallet_v3::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::wallet_v3::prepare_transfer( &public_key, ¤t_state, seqno_offset, gifts, - expiration, - )? + expire_at, + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? + } + AccountType::WalletV5R1 => { + let account = owner.address; + let current_state = ton_core.get_contract_state(&account)?.account; + + let gift = ton_wallet::Gift { + flags: flags.into(), + bounce, + destination, + amount, + body, + state_init: None, + }; + + let seqno_offset = 0; // TODO: implement seqno offset if needed + + let unsigned_message = ton_wallet::wallet_v5r1::prepare_transfer( + &public_key, + ¤t_state, + seqno_offset, + vec![gift], + expire_at, + )?; + + let data_to_sign = + extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); + let signature = key_pair.sign(&data_to_sign); + unsigned_message.sign(&signature.to_bytes())? } AccountType::SafeMultisig => { let has_multiple_owners = match custodians { @@ -1122,7 +1214,7 @@ fn build_token_transaction( None => return Err(TonClientError::CustodiansNotFound.into()), }; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1131,21 +1223,22 @@ fn build_token_transaction( state_init: None, }; - nekoton::core::ton_wallet::multisig::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::multisig::prepare_transfer( MultisigType::SafeMultisigWallet, &public_key, has_multiple_owners, owner.clone(), gift, - expiration, - )? + expire_at, + )?; + + unsigned_message.sign(&key_pair, context)? } AccountType::EverWallet => { - let account = UInt256::from_be_bytes(&owner.address().get_bytestring(0)); + let account = owner.address; let current_state = ton_core.get_contract_state(&account)?.account; - let gift = nekoton::core::ton_wallet::Gift { + let gift = ton_wallet::Gift { flags: flags.into(), bounce, destination, @@ -1154,46 +1247,44 @@ fn build_token_transaction( state_init: None, }; - nekoton::core::ton_wallet::ever_wallet::prepare_transfer( - &SimpleClock, + let unsigned_message = ton_wallet::ever_wallet::prepare_transfer( &public_key, ¤t_state, owner.clone(), vec![gift], - expiration, - )? - } - }; + expire_at, + )?; - let unsigned_message = match transfer_action { - TransferAction::Sign(unsigned_message) => unsigned_message, - TransferAction::DeployFirst => { - return Err(TonClientError::AccountNotDeployed(owner.to_string()).into()) + unsigned_message.sign(&key_pair, context)? } }; - let key_pair = Keypair { - secret: SecretKey::from_bytes(private_key)?, - public: public_key, - }; - - let data_to_sign = - ton_abi::extend_signature_with_id(unsigned_message.hash(), ton_core.signature_id()); - let signature = key_pair.sign(&data_to_sign); - let signed_message = unsigned_message.sign(&signature.to_bytes())?; + let cell_builder = CellBuilder::build_from(&owned_message).map_err(anyhow::Error::from)?; + let hash = cell_builder.repr_hash(); let sent_transaction = SentTransaction { id, - message_hash: signed_message.message.hash()?.to_hex_string(), - account_workchain_id: owner.workchain_id(), - account_hex: owner.address().to_hex_string(), + message_hash: hash.to_string(), + account_workchain_id: owner.workchain as i32, + account_hex: owner.address.to_string(), original_value: None, original_outputs: None, aborted: false, bounce, }; - Ok((sent_transaction, signed_message)) + Ok(PrepareResult { + sent_transaction, + owned_message, + expire_at, + }) } const TYCHO_TESTNET_CHAIN_ID: i32 = -4000; + +#[derive(Debug)] +pub struct PrepareResult { + pub sent_transaction: SentTransaction, + pub owned_message: OwnedMessage, + pub expire_at: u32, +} diff --git a/src/client/ton/utils.rs b/src/client/ton/utils.rs deleted file mode 100644 index d961322..0000000 --- a/src/client/ton/utils.rs +++ /dev/null @@ -1,15 +0,0 @@ -use nekoton_abi::LastTransactionId; - -pub fn parse_last_transaction( - last_transaction: &LastTransactionId, -) -> (Option, Option) { - let (last_transaction_hash, last_transaction_lt) = match last_transaction { - LastTransactionId::Exact(transaction_id) => ( - Some(transaction_id.hash.to_hex_string()), - Some(transaction_id.lt.to_string()), - ), - LastTransactionId::Inexact { .. } => (None, None), - }; - - (last_transaction_hash, last_transaction_lt) -} diff --git a/src/cmd/run.rs b/src/cmd/run.rs index 1c63b99..5e867f4 100644 --- a/src/cmd/run.rs +++ b/src/cmd/run.rs @@ -128,13 +128,6 @@ impl Cmd { .init_blockchain_rpc(NoopBroadcastListener, NoopBroadcastListener)? .build()?; - let context = EngineContext::new( - node_config.api, - node.core_storage.clone(), - node.blockchain_rpc_client.clone(), - ) - .await?; - // Sync node. node.wait_for_neighbours(3).await; @@ -151,6 +144,14 @@ impl Cmd { node.update_validator_set_from_shard_state(&init_block_id) .await?; + let context = EngineContext::new( + node_config.api, + node.core_storage.clone(), + node.blockchain_rpc_client.clone(), + &init_block_id, + ) + .await?; + // Build strider. let archive_block_provider = node.build_archive_block_provider(); let storage_block_provider = node.build_storage_block_provider(); @@ -166,11 +167,6 @@ impl Cmd { ), ); - context - .start(&init_block_id) - .await - .context("failed to start context")?; - let api = Api::bind( context.config.server_addr, context.config.public_url.clone(), diff --git a/src/models/account_enums.rs b/src/models/account_enums.rs index 5b00ca4..07c5bd4 100644 --- a/src/models/account_enums.rs +++ b/src/models/account_enums.rs @@ -1,23 +1,26 @@ use std::str::FromStr; -use nekoton::core::models::TokenWalletVersion; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; -use strum_macros::EnumString; use tycho_types::models::{AccountState, StdAddr}; -use crate::models::{Address, AddressDb}; +use crate::{ + models::{Address, AddressDb}, + utils::token_wallets::models::TokenWalletVersion, +}; #[derive( Debug, Default, Deserialize, Serialize, Clone, JsonSchema, Eq, PartialEq, sqlx::Type, Copy, )] #[sqlx(type_name = "twa_account_type", rename_all = "PascalCase")] pub enum AccountType { - #[default] HighloadWallet, Wallet, SafeMultisig, EverWallet, + #[sqlx(rename = "WalletV5R1")] + #[default] + WalletV5R1, } #[derive(Debug, Deserialize, Serialize, Clone, JsonSchema, sqlx::Type, Eq, PartialEq)] @@ -38,18 +41,17 @@ impl From for AccountStatus { } } -impl From for AccountStatus { - fn from(state: ton_block::AccountState) -> Self { - match state { - ton_block::AccountState::AccountUninit => AccountStatus::UnInit, - ton_block::AccountState::AccountActive { .. } => AccountStatus::Active, - ton_block::AccountState::AccountFrozen { .. } => AccountStatus::Frozen, - } - } -} - #[derive( - Debug, Deserialize, Serialize, Clone, JsonSchema, Eq, PartialEq, sqlx::Type, Copy, EnumString, + Debug, + Deserialize, + Serialize, + Clone, + JsonSchema, + Eq, + PartialEq, + sqlx::Type, + Copy, + derive_more::FromStr, )] #[sqlx(type_name = "twa_token_wallet_version", rename_all = "PascalCase")] pub enum TokenWalletVersionDb { diff --git a/src/models/address.rs b/src/models/address.rs index 9ad59fb..b2c4465 100644 --- a/src/models/address.rs +++ b/src/models/address.rs @@ -1,6 +1,6 @@ use bigdecimal::BigDecimal; use schemars::JsonSchema; -use ton_block::MsgAddressInt; +use tycho_types::models::StdAddr; use crate::models::*; @@ -98,10 +98,10 @@ pub struct NetworkAddressData { } impl NetworkAddressData { - pub fn uninit(owner: &MsgAddressInt) -> NetworkAddressData { + pub fn uninit(owner: &StdAddr) -> NetworkAddressData { NetworkAddressData { - workchain_id: owner.workchain_id(), - hex: owner.address().to_hex_string(), + workchain_id: owner.workchain as i32, + hex: owner.address.to_string(), account_status: AccountStatus::UnInit, network_balance: Default::default(), last_transaction_hash: None, diff --git a/src/models/existing_contract.rs b/src/models/existing_contract.rs new file mode 100644 index 0000000..50812e3 --- /dev/null +++ b/src/models/existing_contract.rs @@ -0,0 +1,7 @@ +use tycho_types::cell::HashBytes; + +#[derive(Clone, Debug)] +pub struct ExistingContract { + pub account: tycho_types::models::Account, + pub last_transaction_hash: HashBytes, +} diff --git a/src/models/mod.rs b/src/models/mod.rs index d4810f8..51a0b45 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -2,6 +2,7 @@ pub use self::account_enums::*; pub use self::account_transaction_event::*; pub use self::address::*; pub use self::blockchain::*; +pub use self::existing_contract::*; pub use self::key::*; pub use self::last_key_blocks::*; pub use self::metrics::*; @@ -18,6 +19,7 @@ mod account_enums; mod account_transaction_event; mod address; mod blockchain; +mod existing_contract; mod key; mod last_key_blocks; mod metrics; diff --git a/src/models/owners_cache.rs b/src/models/owners_cache.rs index bb294ea..0ab1d69 100644 --- a/src/models/owners_cache.rs +++ b/src/models/owners_cache.rs @@ -3,23 +3,23 @@ use std::str::FromStr; use std::sync::Arc; use lru::LruCache; -use nekoton::core::models::TokenWalletVersion; -use nekoton_utils::TrustMe; use parking_lot::Mutex; -use ton_block::MsgAddressInt; +use tycho_types::cell::HashBytes; +use tycho_types::models::StdAddr; use crate::models::sqlx::*; use crate::sqlx_client::*; +use crate::utils::token_wallets::models::TokenWalletVersion; #[derive(Clone)] /// Maps token wallet address to Owner info pub struct OwnersCache { - cache: Arc>>, + cache: Arc>>, db: SqlxClient, } impl OwnersCache { - pub async fn get(&self, address: &MsgAddressInt) -> Option { + pub async fn get(&self, address: &StdAddr) -> Option { let info = { let mut lock = self.cache.lock(); lock.get(address).cloned() @@ -33,29 +33,29 @@ impl OwnersCache { .await .ok()?; OwnerInfo { - owner_address: MsgAddressInt::from_str(&format!( + owner_address: StdAddr::from_str(&format!( "{}:{}", got.owner_account_workchain_id, got.owner_account_hex )) - .trust_me(), - root_address: MsgAddressInt::from_str(&got.root_address).trust_me(), - code_hash: got.code_hash, + .unwrap(), + root_address: StdAddr::from_str(&got.root_address).unwrap(), + code_hash: HashBytes::from_slice(&got.code_hash), version: got.version.into(), } } }; Some(info) } - pub async fn insert(&self, key: MsgAddressInt, value: OwnerInfo) { + pub async fn insert(&self, key: StdAddr, value: OwnerInfo) { { self.cache.lock().put(key.clone(), value.clone()); } let owner = TokenOwnerFromDb { address: key.to_string(), - owner_account_workchain_id: value.owner_address.workchain_id(), - owner_account_hex: value.owner_address.address().to_hex_string(), + owner_account_workchain_id: value.owner_address.workchain as i32, + owner_account_hex: value.owner_address.address.to_string(), root_address: value.root_address.to_string(), - code_hash: value.code_hash, + code_hash: value.code_hash.as_array().to_vec(), created_at: chrono::Utc::now().naive_utc(), //doesn't matter version: value.version.into(), }; @@ -67,9 +67,9 @@ impl OwnersCache { #[derive(Clone, Debug)] pub struct OwnerInfo { - pub owner_address: MsgAddressInt, - pub root_address: MsgAddressInt, - pub code_hash: Vec, + pub owner_address: StdAddr, + pub root_address: StdAddr, + pub code_hash: HashBytes, pub version: TokenWalletVersion, } @@ -77,18 +77,21 @@ impl OwnersCache { pub async fn new(sqlx_client: SqlxClient) -> Result { let balances = sqlx_client.get_all_token_owners().await?; // no more than 10 mb - let mut cache = LruCache::new(NonZeroUsize::new(5000).trust_me()); + let mut cache = LruCache::new(NonZeroUsize::new(5000).unwrap()); balances.into_iter().for_each(|x| { + let k = StdAddr::from_str(&x.address).unwrap(); + let owner_address = StdAddr::from_str(&format!( + "{}:{}", + x.owner_account_workchain_id, x.owner_account_hex + )) + .unwrap(); + let root_address = StdAddr::from_str(&x.root_address).unwrap(); cache.put( - MsgAddressInt::from_str(&x.address).trust_me(), + k, OwnerInfo { - owner_address: MsgAddressInt::from_str(&format!( - "{}:{}", - x.owner_account_workchain_id, x.owner_account_hex - )) - .trust_me(), - root_address: MsgAddressInt::from_str(&x.root_address).trust_me(), - code_hash: x.code_hash, + owner_address, + root_address, + code_hash: HashBytes::from_slice(&x.code_hash), version: x.version.into(), }, ); diff --git a/src/models/states_cache.rs b/src/models/states_cache.rs deleted file mode 100644 index 9e00718..0000000 --- a/src/models/states_cache.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::sync::Arc; - -use lru::LruCache; -use nekoton::transport::models::ExistingContract; -use nekoton_utils::TrustMe; -use parking_lot::Mutex; -use ton_block::StdAddr; - -use crate::sqlx_client::*; - -#[derive(Clone)] -pub struct StatesCache { - cache: Arc>>, - db: SqlxClient, -} - -impl StatesCache { - pub async fn new(sqlx_client: SqlxClient) -> Result { - let states = sqlx_client.get_token_whitelist().await?; - let mut res = LruCache::new(100); - - states.into_iter().for_each(|x| { - if let Some(state) = x.state { - res.put(nekoton_utils::repack_address(&x.address).trust_me(), { - let state: ExistingContract = serde_json::from_value(state).trust_me(); - state - }); - } - }); - Ok(Self { - cache: Arc::new(Mutex::new(res)), - db: sqlx_client, - }) - } - - pub async fn get(&self, address: &StdAddr) -> Option { - let state = { - let mut lock = self.cache.lock(); - lock.get(address).cloned() - }; - match state { - Some(a) => Some(a), - None => { - let got = self.db.get_root_token(&address.to_string()).await.ok()?; - match got.state { - None => None, - Some(state) => { - let state: Option = serde_json::from_value(state).ok(); - state - } - } - } - } - } - - pub async fn insert(&self, key: StdAddr, value: ExistingContract) { - { - self.cache.lock().put(key.clone(), value.clone()); - } - - if let Err(e) = self - .db - .update_root_token_state(&key.to_string(), serde_json::json!(value)) - .await - { - tracing::error!("Failed inserting root token state: {}", e) - } - } -} diff --git a/src/models/token_balance.rs b/src/models/token_balance.rs index 41470fc..1aa3f70 100644 --- a/src/models/token_balance.rs +++ b/src/models/token_balance.rs @@ -1,4 +1,5 @@ use bigdecimal::BigDecimal; +use tycho_types::models::StdAddr; use crate::models::*; @@ -25,13 +26,10 @@ pub struct NetworkTokenAddressData { } impl NetworkTokenAddressData { - pub fn uninit( - owner: &ton_block::MsgAddressInt, - root: &ton_block::MsgAddressInt, - ) -> NetworkTokenAddressData { + pub fn uninit(owner: &StdAddr, root: &StdAddr) -> NetworkTokenAddressData { NetworkTokenAddressData { - workchain_id: owner.workchain_id(), - hex: owner.address().to_hex_string(), + workchain_id: owner.workchain as i32, + hex: owner.address.to_string(), root_address: root.to_string(), version: Default::default(), account_status: AccountStatus::UnInit, diff --git a/src/models/transactions.rs b/src/models/transactions.rs index 12cf206..4a80eb4 100644 --- a/src/models/transactions.rs +++ b/src/models/transactions.rs @@ -1,6 +1,6 @@ use bigdecimal::BigDecimal; use serde::{Deserialize, Serialize}; -use ton_abi::Param; +use tycho_types::abi::{AbiHeaderType, NamedAbiType}; use uuid::Uuid; use crate::models::*; @@ -181,14 +181,14 @@ pub struct SentTransaction { #[derive(Clone, Deserialize, Debug)] pub struct InputParam { - pub param: Param, - pub value: serde_json::Value, + pub value: String, + pub abi_type: NamedAbiType, } #[derive(Clone, Debug, Deserialize)] pub struct FunctionDetails { pub function_name: String, pub input_params: Vec, - pub output_params: Vec, - pub headers: Vec, + pub output_params: Vec, + pub headers: Vec, } diff --git a/src/server.rs b/src/server.rs index 895b2b9..c65e922 100644 --- a/src/server.rs +++ b/src/server.rs @@ -36,15 +36,18 @@ impl EngineContext { config: AppConfig, storage: CoreStorage, blockchain_rpc_client: BlockchainRpcClient, + last_block_id: &BlockId, ) -> Result> { let pool = PgPoolOptions::new() .max_connections(config.db_pool_size) .connect(&config.database_url) .await - .expect(&format!( - "Failed connection to database url - {}", - config.database_url - )); + .unwrap_or_else(|_| { + panic!( + "Failed connection to database url - {}", + config.database_url + ) + }); sqlx::migrate!().run(&pool).await?; @@ -63,11 +66,17 @@ impl EngineContext { token_transaction_tx, storage, blockchain_rpc_client, + last_block_id, ) .await?; let ton_client = Arc::new(TonClient::new(ton_core.clone(), sqlx_client.clone())); + ton_client + .start() + .await + .context("failed to start ton_client")?; + let ton_service = Arc::new(TonService::new( sqlx_client.clone(), ton_client.clone(), @@ -75,7 +84,12 @@ impl EngineContext { config.key.clone(), )); - let auth_service = Arc::new(AuthService::new(sqlx_client.clone())); + ton_service + .start() + .await + .context("failed to start ton_service")?; + + let auth_service = Arc::new(AuthService::new(sqlx_client)); let memory_storage = Arc::new(StorageHandler::default()); @@ -96,23 +110,6 @@ impl EngineContext { Ok(engine_context) } - pub async fn start(&self, last_block_id: &BlockId) -> Result<()> { - self.ton_client - .start() - .await - .context("failed to start ton_client")?; - self.ton_service - .start() - .await - .context("failed to start ton_service")?; - self.ton_core - .start(last_block_id) - .await - .context("failed to start ton_core")?; - - Ok(()) - } - fn start_listening_ton_transaction(self: &Arc, mut rx: TonTransactionRx) { let engine_context = Arc::downgrade(self); @@ -271,14 +268,14 @@ impl EngineContext { let now = chrono::Utc::now().timestamp() as u32; // Delete expired guards - self.guards.retain(|_, (_, expired_at)| now < *expired_at); + self.guards.retain(|_, (_, expire_at)| now < *expire_at); match self.guards.entry(account) { Entry::Occupied(entry) => entry.get().0.clone(), Entry::Vacant(entry) => { - let expired_at = now + 5 * DEFAULT_EXPIRATION_TIMEOUT; + let expire_at = now + 5 * DEFAULT_EXPIRATION_TIMEOUT; entry - .insert((Arc::new(Mutex::default()), expired_at)) + .insert((Arc::new(Mutex::default()), expire_at)) .value() .0 .clone() diff --git a/src/services/auth.rs b/src/services/auth.rs index 890ccb4..3ae7da6 100644 --- a/src/services/auth.rs +++ b/src/services/auth.rs @@ -6,6 +6,8 @@ use chrono::DateTime; use chrono::Utc; use parking_lot::Mutex; +use base64::{engine::general_purpose, Engine as _}; + use crate::models::*; use crate::sqlx_client::*; @@ -74,7 +76,7 @@ impl AuthService { let calculated_signature = hmac_sha256::HMAC::mac(concat.as_bytes(), key.secret.as_bytes()); - let expected_signature = base64::decode(signature)?; + let expected_signature = general_purpose::STANDARD.decode(signature)?; if calculated_signature != expected_signature.as_slice() { anyhow::bail!("Invalid signature"); diff --git a/src/services/storage.rs b/src/services/storage.rs index 6e5ac3b..7822bbf 100644 --- a/src/services/storage.rs +++ b/src/services/storage.rs @@ -1,28 +1,29 @@ -use std::time::SystemTime; - -use nekoton::crypto::UnsignedMessage; -use nekoton_utils::TrustMe; +use tycho_types::{ + abi::UnsignedExternalMessage, + cell::{CellBuilder, HashBytes}, +}; +use tycho_util::time::now_sec; use crate::utils::FxDashMap; #[derive(Default)] pub struct StorageHandler { - message_collection: FxDashMap>, + message_collection: FxDashMap, } impl StorageHandler { - pub fn add_message(&self, message: Box) -> String { - let key = hex::encode(message.hash()); - self.message_collection.insert(key.clone(), message); - key + pub fn add_message(&self, message: UnsignedExternalMessage) -> HashBytes { + let cell_builder = + CellBuilder::build_from(message.clone().without_signature().unwrap()).unwrap(); + let message_hash = *cell_builder.repr_hash(); + self.message_collection.insert(message_hash, message); + message_hash } - pub fn get_message(&self, hash: &str) -> Option> { - let now = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .trust_me() - .as_secs() as u32; - self.message_collection.retain(|_, v| v.expire_at() > now); + pub fn get_message(&self, hash: &HashBytes) -> Option { + let now = now_sec(); + self.message_collection + .retain(|_, message| message.expire_at() > now); let message = self.message_collection.get(hash).map(|x| x.value().clone()); message } diff --git a/src/services/ton.rs b/src/services/ton.rs index 782aa7e..2db6264 100644 --- a/src/services/ton.rs +++ b/src/services/ton.rs @@ -5,14 +5,14 @@ use std::sync::{Arc, Weak}; use axum::http::StatusCode; use bigdecimal::BigDecimal; use chrono::Utc; -use nekoton::crypto::{SignedMessage, UnsignedMessage}; -use nekoton_utils::{repack_address, unpack_std_smc_addr, TrustMe}; use serde_json::Value; -use ton_abi::contract::ABI_VERSION_2_2; -use ton_abi::{Param, Token, TokenValue}; -use ton_block::{GetRepresentationHash, MsgAddressInt, Serializable}; -use ton_types::{BuilderData, UInt256}; -use tycho_types::cell::HashBytes; +use tycho_types::abi::{ + AbiHeaderType, AbiValue, AbiVersion, Function, NamedAbiType, NamedAbiValue, + UnsignedExternalMessage, +}; +use tycho_types::boc::Boc; +use tycho_types::cell::{CellBuilder, HashBytes}; +use tycho_types::models::{OwnedMessage, StdAddr, StdAddrFormat}; use uuid::Uuid; use crate::api::*; @@ -55,9 +55,8 @@ impl TonService { // Resend transactions for transaction in transactions { - let account = UInt256::from_be_bytes(&hex::decode(transaction.account_hex.clone())?); - let message_hash = - UInt256::from_be_bytes(&hex::decode(transaction.message_hash.clone())?); + let account = HashBytes::from_str(&transaction.account_hex)?; + let message_hash = HashBytes::from_str(&transaction.message_hash)?; let expire_at = transaction.created_at.and_utc().timestamp() as u32 + DEFAULT_EXPIRATION_TIMEOUT; @@ -99,9 +98,7 @@ impl TonService { } pub async fn check_address(&self, address: Address) -> Result { - Ok(MsgAddressInt::from_str(&address.0).is_ok() - || (unpack_std_smc_addr(&address.0, false).is_ok()) - || (unpack_std_smc_addr(&address.0, true).is_ok())) + Ok(StdAddr::from_str(&address.0).is_ok()) } pub async fn get_address_balance( @@ -109,13 +106,14 @@ impl TonService { service_id: &ServiceId, address: Address, ) -> Result<(AddressDb, NetworkAddressData), Error> { - let account = repack_address(&address.0)?; + let (account, _) = + StdAddr::from_str_ext(&address.0, StdAddrFormat::any()).map_err(anyhow::Error::from)?; let address = self .sqlx_client .get_address( *service_id, - account.workchain_id(), - account.address().to_hex_string(), + account.workchain as i32, + account.address.to_string(), ) .await?; let network = self.ton_api_client.get_address_info(&account).await?; @@ -128,13 +126,14 @@ impl TonService { service_id: &ServiceId, address: Address, ) -> Result { - let account = repack_address(&address.0)?; + let (account, _) = + StdAddr::from_str_ext(&address.0, StdAddrFormat::any()).map_err(anyhow::Error::from)?; let address = self .sqlx_client .get_address( *service_id, - account.workchain_id(), - account.address().to_hex_string(), + account.workchain as i32, + account.address.to_string(), ) .await?; @@ -146,7 +145,8 @@ impl TonService { service_id: &ServiceId, input: TransactionSend, ) -> Result { - let address = repack_address(&input.from_address.0)?; + let (address, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let network = self.ton_api_client.get_address_info(&address).await?; for transaction_output in input.outputs.iter() { @@ -173,8 +173,8 @@ impl TonService { .sqlx_client .get_address( *service_id, - address.workchain_id(), - address.address().to_hex_string(), + address.workchain as i32, + address.address.to_string(), ) .await?; @@ -188,7 +188,11 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_transaction( input, @@ -201,16 +205,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -225,14 +230,15 @@ impl TonService { service_id: &ServiceId, input: TransactionConfirm, ) -> Result { - let address = repack_address(&input.address.0)?; + let (address, _) = StdAddr::from_str_ext(&input.address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - address.workchain_id(), - address.address().to_hex_string(), + address.workchain as i32, + address.address.to_string(), ) .await?; @@ -252,23 +258,28 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_confirm_transaction(input, &public_key, &private_key) .await?; let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -535,19 +546,21 @@ impl TonService { service_id: &ServiceId, address: &Address, ) -> Result, Error> { - let account = repack_address(&address.0)?; + let (account, _) = + StdAddr::from_str_ext(&address.0, StdAddrFormat::any()).map_err(anyhow::Error::from)?; let balances = self .sqlx_client .get_token_balances( *service_id, - account.workchain_id(), - account.address().to_hex_string(), + account.workchain as i32, + account.address.to_string(), ) .await?; let mut result = Vec::with_capacity(balances.len()); for balance in balances { - let root_address = repack_address(&balance.root_address)?; + let root_address = + StdAddr::from_str(&balance.root_address).map_err(anyhow::Error::from)?; let network = self .ton_api_client @@ -570,13 +583,15 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = repack_address(&input.from_address.0)?; + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let address_db = self .sqlx_client .get_address( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), ) .await?; @@ -593,8 +608,8 @@ impl TonService { .sqlx_client .get_token_balance( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; @@ -620,7 +635,11 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_token_transaction( input, @@ -633,16 +652,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -662,13 +682,15 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = repack_address(&input.from_address.0)?; + let (owner, _) = StdAddr::from_str_ext(&input.from_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let address_db = self .sqlx_client .get_address( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), ) .await?; @@ -685,8 +707,8 @@ impl TonService { .sqlx_client .get_token_balance( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), input.root_address.0.clone(), ) .await?; @@ -712,7 +734,11 @@ impl TonService { .await?; } - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_token_burn( input, @@ -725,16 +751,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -759,13 +786,15 @@ impl TonService { return Err(TonServiceError::WrongInput("Invalid value".to_string()).into()); } - let owner = repack_address(&input.owner_address.0)?; + let (owner, _) = StdAddr::from_str_ext(&input.owner_address.0, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; + let address_db = self .sqlx_client .get_address( *service_id, - owner.workchain_id(), - owner.address().to_hex_string(), + owner.workchain as i32, + owner.address.to_string(), ) .await?; @@ -783,7 +812,11 @@ impl TonService { let public_key = hex::decode(address_db.public_key.clone())?; let private_key = decrypt_private_key(&address_db.private_key, key, &address_db.id)?; - let (payload, signed_message) = self + let PrepareResult { + sent_transaction, + owned_message, + expire_at, + } = self .ton_api_client .prepare_token_mint( input, @@ -796,16 +829,17 @@ impl TonService { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -858,43 +892,46 @@ impl TonService { account_addr: &str, function_name: &str, inputs: Vec, - outputs: Vec, - headers: Vec, + outputs: Vec, + headers: Vec, responsible: bool, - ) -> Result { - let account_addr = UInt256::from_str(account_addr)?; + ) -> Result, Error> { + let account_addr = HashBytes::from_str(account_addr).map_err(anyhow::Error::from)?; - let input_params: Vec = inputs.iter().map(|x| x.param.clone()).collect(); + let input_params: Vec = inputs.iter().map(|x| x.abi_type.clone()).collect(); - let function = nekoton_abi::FunctionBuilder::new(function_name) - .abi_version(ABI_VERSION_2_2) - .headers(headers) - .inputs(input_params) - .outputs(outputs) + let inputs: Result, anyhow::Error> = inputs + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let inputs = inputs?; + + let function = Function::builder(AbiVersion::V2_2, function_name) + .with_headers(headers) + .with_inputs(input_params) + .with_outputs(outputs) .build(); - let input = parse_abi_tokens(inputs)?; - let output = match self + let tokens = match self .ton_api_client - .run_local(account_addr, function, input.as_slice(), responsible) + .run_local(account_addr, function, &inputs, responsible) .await? { Some(output) => output, None => return Err(TonServiceError::ExecuteContract.into()), }; - let tokens = match output.tokens { - Some(tokens) => { - if tokens.is_empty() { - tracing::warn!("No response tokens in execution output") - } - tokens - } - None => return Err(TonServiceError::ExecuteContract.into()), - }; + if tokens.is_empty() { + tracing::warn!("No response tokens in execution output") + } - let res = nekoton_abi::make_abi_tokens(tokens.as_slice())?; - Ok(res) + Ok(tokens) } pub async fn prepare_and_send_signed_generic_message( @@ -910,37 +947,49 @@ impl TonService { function_details: Option, transaction_id: Uuid, ) -> Result { - let (function, values) = match function_details { + let (function, inputs) = match function_details { Some(details) => { - let function = nekoton_abi::FunctionBuilder::new(&details.function_name) - .abi_version(ABI_VERSION_2_2) - .headers(details.headers) - .inputs( - details - .input_params - .clone() - .into_iter() - .map(|x| x.param) - .collect::>(), - ) - .outputs(details.output_params) - .build(); - - let tokens = parse_abi_tokens(details.input_params)?; - - (Some(function), Some(tokens)) + let function = + Function::builder(AbiVersion::V2_2, details.function_name.to_string()) + .with_headers(details.headers) + .with_inputs( + details + .input_params + .clone() + .into_iter() + .map(|x| x.abi_type) + .collect::>(), + ) + .with_outputs(details.output_params) + .build(); + + let inputs: Result, anyhow::Error> = details + .input_params + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let inputs = inputs?; + + (Some(function), Some(inputs)) } None => (None, None), }; - let sender = repack_address(sender_addr)?; + let (sender, _) = StdAddr::from_str_ext(sender_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; let address_db = self .sqlx_client .get_address( *service_id, - sender.workchain_id(), - sender.address().to_hex_string(), + sender.workchain as i32, + sender.address.to_string(), ) .await?; @@ -949,7 +998,7 @@ impl TonService { let public_key = hex::decode(address_db.public_key.clone())?; let private_key = decrypt_private_key(&address_db.private_key, key, &address_db.id)?; - let signed_message = self + let (owned_message, expire_at) = self .ton_api_client .prepare_signed_generic_message( sender_addr, @@ -962,15 +1011,19 @@ impl TonService { account_type, custodians, function, - values, + inputs, ) .await?; + let cell_builder = + CellBuilder::build_from(owned_message.clone()).map_err(anyhow::Error::from)?; + let message_hash = *cell_builder.repr_hash(); + let sent_transaction = SentTransaction { id: transaction_id, - message_hash: signed_message.message.hash()?.to_hex_string(), - account_workchain_id: sender.workchain_id(), - account_hex: sender.address().to_hex_string(), + message_hash: message_hash.to_string(), + account_workchain_id: sender.workchain as i32, + account_hex: sender.address.to_string(), original_value: Some(value), original_outputs: None, aborted: false, @@ -986,9 +1039,10 @@ impl TonService { transaction.message_hash.clone(), transaction.account_hex.clone(), transaction.account_workchain_id, - signed_message, + owned_message, true, true, + expire_at, ) .await?; @@ -1009,26 +1063,37 @@ impl TonService { account_type: &AccountType, custodians: &Option, function_details: Option, - ) -> Result, Error> { - let (function, values) = match function_details { + ) -> Result { + let (function, inputs) = match function_details { Some(details) => { - let function = nekoton_abi::FunctionBuilder::new(&details.function_name) - .abi_version(ABI_VERSION_2_2) - .headers(details.headers) - .inputs( - details - .input_params - .clone() - .into_iter() - .map(|x| x.param) - .collect::>(), - ) - .outputs(details.output_params) - .build(); - - let tokens = parse_abi_tokens(details.input_params)?; - - (Some(function), Some(tokens)) + let function = + Function::builder(AbiVersion::V2_2, details.function_name.to_string()) + .with_headers(details.headers) + .with_inputs( + details + .input_params + .clone() + .into_iter() + .map(|x| x.abi_type) + .collect::>(), + ) + .with_outputs(details.output_params) + .build(); + + let inputs: Result, anyhow::Error> = details + .input_params + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let inputs = inputs?; + + (Some(function), Some(inputs)) } None => (None, None), }; @@ -1045,7 +1110,7 @@ impl TonService { account_type, custodians, function, - values, + inputs, ) .await?; @@ -1053,58 +1118,65 @@ impl TonService { } pub fn encode_tvm_cell(&self, data: Vec) -> Result { - let mut tokens: Vec = Vec::new(); - for d in data { - let token_value = ton_abi::token::Tokenizer::tokenize_parameter( - &d.param.kind, - &d.value, - &d.param.name, - )?; - let token = Token::new(&d.param.name, token_value); - tokens.push(token); - } - let initial = if tokens.is_empty() { - BuilderData::default() + let tokens: Result, anyhow::Error> = data + .into_iter() + .map(|x| { + Ok(NamedAbiValue { + name: x.abi_type.name, + value: AbiValue::from_json_str(&x.value, &x.abi_type.ty) + .map_err(anyhow::Error::from)?, + }) + }) + .collect(); + let tokens = tokens?; + + let cell = if tokens.is_empty() { + CellBuilder::default() + .build() + .map_err(anyhow::Error::from)? } else { - TokenValue::pack_values_into_chain( - tokens.as_slice(), - Default::default(), - &ABI_VERSION_2_2, - )? + NamedAbiValue::tuple_to_cell(tokens.as_slice(), AbiVersion::V2_2) + .map_err(anyhow::Error::from)? }; - let cell = initial.into_cell()?; - Ok(base64::encode(cell.write_to_bytes()?)) + Ok(Boc::encode_base64(cell)) } pub async fn send_signed_message( self: &Arc, sender_addr: String, hash: String, - msg: SignedMessage, + owned_message: OwnedMessage, + expire_at: u32, ) -> Result { - let addr = MsgAddressInt::from_str(&sender_addr)?; + let (addr, _) = StdAddr::from_str_ext(&sender_addr, StdAddrFormat::any()) + .map_err(anyhow::Error::from)?; self.ton_api_client - .add_ton_account_subscription(addr.hash()?); + .add_ton_account_subscription(addr.address); + + let cell_builder = + CellBuilder::build_from(owned_message.clone()).map_err(anyhow::Error::from)?; + let message_hash = *cell_builder.repr_hash(); self.send_transaction( hash, - addr.address().to_hex_string(), - addr.workchain_id(), - msg.clone(), + addr.address.to_string(), + addr.workchain as i32, + owned_message, true, false, + expire_at, ) .await?; - let hash = msg.message.hash().map(|x| x.to_hex_string())?; - - Ok(hash) + Ok(message_hash.to_string()) } pub async fn add_account_subscription(self: &Arc, address: String) -> Result<(), Error> { - let address = MsgAddressInt::from_str(&address)?; + let (addr, _) = + StdAddr::from_str_ext(&address, StdAddrFormat::any()).map_err(anyhow::Error::from)?; + self.ton_api_client - .add_ton_account_subscription(address.hash()?); + .add_ton_account_subscription(addr.address); Ok(()) } @@ -1116,7 +1188,7 @@ impl TonService { .into_iter() .map(|item| { let mut result = HashBytes::default(); - hex::decode_to_slice(item.hex, &mut result.0).trust_me(); + hex::decode_to_slice(item.hex, &mut result.0).unwrap(); result }); @@ -1137,7 +1209,7 @@ impl TonService { } let addresses = address_dbs.into_iter().map(|item| { let mut result = HashBytes::default(); - hex::decode_to_slice(item.hex, &mut result.0).trust_me(); + hex::decode_to_slice(item.hex, &mut result.0).unwrap(); result }); @@ -1179,9 +1251,10 @@ impl TonService { message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: SignedMessage, + owned_message: OwnedMessage, non_blocking: bool, with_db_update: bool, + expire_at: u32, ) -> Result<(), Error> { let ton_service = Arc::downgrade(self); @@ -1192,8 +1265,9 @@ impl TonService { message_hash, account_hex, account_workchain_id, - signed_message, + owned_message, with_db_update, + expire_at, ) .await? } @@ -1205,8 +1279,9 @@ impl TonService { message_hash, account_hex, account_workchain_id, - signed_message, + owned_message, with_db_update, + expire_at, ), ); } @@ -1227,19 +1302,25 @@ impl TonService { .prepare_deploy(address, public_key, private_key) .await?; - if let Some((payload, signed_message)) = payload { + if let Some(PrepareResult { + sent_transaction, + owned_message, + expire_at, + }) = payload + { let (transaction, event) = self .sqlx_client - .create_send_transaction(CreateSendTransaction::new(payload, *service_id)) + .create_send_transaction(CreateSendTransaction::new(sent_transaction, *service_id)) .await?; self.send_transaction( transaction.message_hash, transaction.account_hex, transaction.account_workchain_id, - signed_message, + owned_message, false, true, + expire_at, ) .await?; @@ -1359,19 +1440,20 @@ async fn send_transaction( message_hash: String, account_hex: String, account_workchain_id: i32, - signed_message: SignedMessage, + owned_message: OwnedMessage, with_db_update: bool, + expire_at: u32, ) -> Result<(), Error> { let ton_service = match ton_service.upgrade() { Some(ton_service) => ton_service, None => return Err(TonServiceError::ServiceUnavailable.into()), }; - let account = UInt256::from_be_bytes(&hex::decode(&account_hex)?); + let account = HashBytes::from_str(&account_hex).map_err(anyhow::Error::from)?; let status = ton_service .ton_api_client - .send_transaction(account, signed_message) + .send_transaction(account, owned_message, expire_at) .await?; if status == MessageStatus::Expired && with_db_update { @@ -1388,16 +1470,6 @@ async fn send_transaction( Ok(()) } -fn parse_abi_tokens(params: Vec) -> Result, Error> { - let mut tokens = Vec::::new(); - for i in params { - let token = nekoton_abi::parse_abi_token(&i.param, i.value)?; - tokens.push(token); - } - - Ok(tokens) -} - enum NotifyType { Transaction, TokenTransaction, diff --git a/src/settings.rs b/src/settings.rs index 1790d93..d14a09a 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -2,8 +2,7 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::path::Path; use anyhow::{Context, Result}; -use argon2::password_hash::PasswordHasher; -use nekoton_utils::TrustMe; +use argon2::password_hash::{PasswordHasher, Salt}; use regex; use serde::{Deserialize, Deserializer, Serialize}; @@ -72,16 +71,17 @@ fn default_key() -> Vec { let mut options = argon2::ParamsBuilder::default(); let options = options .output_len(32) //chacha key size - .and_then(|x| x.clone().params()) - .trust_me(); + .build() + .unwrap(); // Argon2 with default params (Argon2id v19) let argon2 = argon2::Argon2::new(argon2::Algorithm::Argon2id, argon2::Version::V0x13, options); + let salt = Salt::from_b64(&salt).unwrap(); let key = argon2 - .hash_password(secret.as_bytes(), &salt) - .trust_me() + .hash_password(secret.as_bytes(), salt) + .unwrap() .hash .context("No hash")? .as_bytes() diff --git a/src/sqlx_client/token_transactions.rs b/src/sqlx_client/token_transactions.rs index 5a62183..0eae5f6 100644 --- a/src/sqlx_client/token_transactions.rs +++ b/src/sqlx_client/token_transactions.rs @@ -271,7 +271,7 @@ impl SqlxClient { #[cfg(test)] async fn prepare_test() -> SqlxClient { let pg_pool = - PgPool::connect("postgresql://everscale:everscale@localhost:5432/tycho_wallet_api_rs") + PgPool::connect("postgresql://tycho:tycho@localhost:5432/tycho_wallet_api_rs") .await .unwrap(); diff --git a/src/sqlx_client/transactions.rs b/src/sqlx_client/transactions.rs index 68b75de..ddcc82e 100644 --- a/src/sqlx_client/transactions.rs +++ b/src/sqlx_client/transactions.rs @@ -1,12 +1,13 @@ use anyhow::{Context, Result}; use chrono::prelude::*; +use tycho_types::models::StdAddr; +use tycho_types::models::StdAddrFormat; use uuid::Uuid; use crate::models::*; use crate::sqlx_client::*; use itertools::Itertools; -use nekoton_utils::{repack_address, TrustMe}; use sqlx::postgres::PgArguments; use sqlx::Arguments; use sqlx::Row; @@ -295,7 +296,7 @@ impl SqlxClient { let mut tx = self.pool.begin().await?; let transaction_id = Uuid::new_v4(); let transaction_timestamp = - DateTime::from_timestamp(payload.transaction_timestamp.trust_me() as i64, 0) + DateTime::from_timestamp(payload.transaction_timestamp.unwrap_or_default() as i64, 0) .context("Invalid transaction timestamp")? .naive_utc(); @@ -711,14 +712,13 @@ pub fn filter_transaction_query( } if let Some(account) = account { - if let Ok(account) = repack_address(&account) { + if let Ok((account, _)) = StdAddr::from_str_ext(&account, StdAddrFormat::any()) { updates.push(format!(" AND account_workchain_id = ${} ", *args_len + 1,)); *args_len += 1; - args.add(account.workchain_id()) - .map_err(sqlx::Error::Encode)?; + args.add(account.workchain).map_err(sqlx::Error::Encode)?; updates.push(format!(" AND account_hex = ${} ", *args_len + 1,)); *args_len += 1; - args.add(account.address().to_hex_string()) + args.add(account.address.to_string()) .expect("Failed to add query") } } diff --git a/src/ton_core/mod.rs b/src/ton_core/mod.rs index 5685bac..89d34da 100644 --- a/src/ton_core/mod.rs +++ b/src/ton_core/mod.rs @@ -2,23 +2,20 @@ use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; -use nekoton::transport::models::*; -use nekoton_abi::*; -use nekoton_utils::Clock; -use nekoton_utils::SimpleClock; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use parking_lot::Mutex; use tokio::sync::{mpsc, oneshot}; -use ton_block::Serializable; -use ton_types::UInt256; use tycho_core::blockchain_rpc::BlockchainRpcClient; use tycho_core::storage::CoreStorage; use tycho_executor::ExecutorParams; use tycho_executor::ParsedConfig; use tycho_types::boc::Boc; +use tycho_types::cell::CellBuilder; use tycho_types::cell::HashBytes; use tycho_types::cell::Lazy; use tycho_types::cell::Load; use tycho_types::models::*; +use tycho_util::time::now_sec; use self::monitoring::*; use self::ton_subscriber::*; @@ -44,9 +41,16 @@ impl TonCore { token_transaction_producer: TokenTransactionTx, storage: CoreStorage, blockchain_rpc_client: BlockchainRpcClient, + last_block_id: &BlockId, ) -> Result> { - let context = - TonCoreContext::new(sqlx_client, owners_cache, storage, blockchain_rpc_client).await?; + let context = TonCoreContext::new( + sqlx_client, + owners_cache, + storage, + blockchain_rpc_client, + last_block_id, + ) + .await?; let ton_transaction = TonTransaction::new(context.clone(), ton_transaction_producer).await?; @@ -61,14 +65,6 @@ impl TonCore { })) } - pub async fn start(&self, last_block_id: &BlockId) -> Result<()> { - // Sync node and subscribers - self.context.start(last_block_id).await?; - - // Done - Ok(()) - } - pub fn add_ton_account_subscription(&self, accounts: I) where I: IntoIterator, @@ -78,25 +74,25 @@ impl TonCore { .add_account_subscription(accounts); } - pub fn get_contract_state(&self, account: &UInt256) -> Result { + pub fn get_contract_state(&self, account: &HashBytes) -> Result { self.context.get_contract_state(account) } pub async fn send_ton_message( &self, - account: &UInt256, - message: &ton_block::Message, + account: HashBytes, + owned_message: OwnedMessage, expire_at: u32, ) -> Result { self.context - .send_ton_message(account, message, expire_at) + .send_ton_message(account, owned_message, expire_at) .await } pub fn add_pending_message( &self, - account: UInt256, - message_hash: UInt256, + account: HashBytes, + message_hash: HashBytes, expire_at: u32, ) -> Result> { self.context @@ -110,6 +106,12 @@ impl TonCore { pub fn signature_id(&self) -> Option { self.context.ton_subscriber.signature_id() } + pub fn capabilities(&self) -> u64 { + self.context.ton_subscriber.capabilities() + } + pub fn blockchain_context(&self) -> BlockchainContext { + self.context.ton_subscriber.blockchain_context() + } } pub struct TonCoreContext { @@ -127,58 +129,49 @@ impl TonCoreContext { owners_cache: OwnersCache, storage: CoreStorage, blockchain_rpc_client: BlockchainRpcClient, + last_block_id: &BlockId, ) -> Result> { let messages_queue = PendingMessagesQueue::new(512); - let ton_subscriber = TonSubscriber::new(messages_queue.clone()); + let mc_state = storage + .shard_state_storage() + .load_state(last_block_id.seqno, last_block_id) + .await?; - Ok(Arc::new(Self { - sqlx_client, - owners_cache, - messages_queue, - ton_subscriber, - storage, - blockchain_rpc_client, - })) - } + let config = mc_state.config_params()?.clone(); + let global_version = config.get_global_version()?; + + let ton_subscriber = TonSubscriber::new( + messages_queue.clone(), + global_version.capabilities.into_inner(), + mc_state.state().global_id, + config, + ); - async fn start(&self, last_block_id: &BlockId) -> Result<()> { // Load last states if exists - let block_ids = self.sqlx_client.get_last_key_blocks().await?; + let block_ids = sqlx_client.get_last_key_blocks().await?; for block_id in block_ids { let block_id = BlockId::from_str(&block_id.block_id)?; - if let Ok(state) = self - .storage + if let Ok(state) = storage .shard_state_storage() .load_state(last_block_id.seqno, &block_id) .await { - self.ton_subscriber - .update_shards_accounts_cache(block_id.shard, state)?; + ton_subscriber.update_shards_accounts_cache(block_id.shard, state)?; } } - let mc_state = self - .storage - .shard_state_storage() - .load_state(last_block_id.seqno, last_block_id) - .await?; - - let config = mc_state.config_params()?; - let global_version = config.get_global_version()?; - - self.ton_subscriber - .start( - global_version.capabilities.into_inner(), - mc_state.state().global_id, - ) - .await - .context("Failed to start ton_subscriber")?; - - Ok(()) + Ok(Arc::new(Self { + sqlx_client, + owners_cache, + messages_queue, + ton_subscriber, + storage, + blockchain_rpc_client, + })) } - fn get_contract_state(&self, account: &UInt256) -> Result { + fn get_contract_state(&self, account: &HashBytes) -> Result { let account = HashBytes::from_slice(account.as_slice()); match self .ton_subscriber @@ -192,23 +185,22 @@ impl TonCoreContext { async fn send_ton_message( &self, - account: &UInt256, - message: &ton_block::Message, + account: HashBytes, + owned_message: OwnedMessage, expire_at: u32, ) -> Result { - match message.header() { - ton_block::CommonMsgInfo::ExtInMsgInfo(header) => header.dst.workchain_id(), + match &owned_message.info { + MsgInfo::ExtIn(ext) => ext.dst.workchain(), _ => return Err(TonCoreError::ExternalTonMessageExpected.into()), }; - let cells = message.write_to_new_cell()?.into_cell()?; - let serialized = ton_types::serialize_toc(&cells)?; + let cell = CellBuilder::build_from(owned_message)?; + let message_hash = *cell.repr_hash(); + let serialized = Boc::encode(cell); - let rx = self.messages_queue.add_message( - HashBytes::from_slice(account.as_slice()), - HashBytes::from_slice(cells.repr_hash().as_slice()), - expire_at, - )?; + let rx = self + .messages_queue + .add_message(account, message_hash, expire_at)?; self.blockchain_rpc_client .broadcast_external_message(&serialized) @@ -221,7 +213,7 @@ impl TonCoreContext { #[allow(unused)] fn send_local( &self, - account: &UInt256, + account: &HashBytes, message_base64: &str, expire_at: u32, ) -> Result> { @@ -237,21 +229,16 @@ impl TonCoreContext { let account = tycho_types::models::OptionalAccount::load_from(&mut account_state.data.as_slice()?)?; - let LastTransactionId::Exact(last_transaction_id) = account_state.last_transaction_id - else { - return Ok(None); - }; - let shard_account = tycho_types::models::ShardAccount { account: Lazy::new(&account).unwrap(), - last_trans_hash: HashBytes::from_slice(last_transaction_id.hash.as_slice()), - last_trans_lt: last_transaction_id.lt, + last_trans_hash: account_state.last_transaction_hash, + last_trans_lt: account.last_trans_lt(), }; let config_cell = Boc::decode_base64("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").unwrap(); let config = config_cell.parse::()?; - let config = ParsedConfig::parse(config, SimpleClock.now_sec_u64() as u32)?; + let config = ParsedConfig::parse(config, now_sec())?; let cell = Boc::decode_base64(message_base64)?; let mut cs = cell.as_slice()?; @@ -280,15 +267,12 @@ impl TonCoreContext { fn add_pending_message( &self, - account: UInt256, - message_hash: UInt256, + account: HashBytes, + message_hash: HashBytes, expire_at: u32, ) -> Result> { - self.messages_queue.add_message( - HashBytes::from_slice(account.as_slice()), - HashBytes::from_slice(message_hash.as_slice()), - expire_at, - ) + self.messages_queue + .add_message(account, message_hash, expire_at) } } diff --git a/src/ton_core/monitoring/token_transaction.rs b/src/ton_core/monitoring/token_transaction.rs index 6dff406..0294b1d 100644 --- a/src/ton_core/monitoring/token_transaction.rs +++ b/src/ton_core/monitoring/token_transaction.rs @@ -1,12 +1,12 @@ use std::sync::Arc; use anyhow::Result; -use nekoton::core::models::*; + use tokio::sync::mpsc; -use ton_types::UInt256; use crate::ton_core::monitoring::*; use crate::ton_core::*; +use crate::utils::token_wallets::models::TokenWalletTransaction; pub struct TokenTransaction { context: Arc, @@ -51,11 +51,17 @@ impl TokenTransaction { } }; + let Some(context) = event.ctx.blockchain_context.clone() else { + tracing::error!("Failed to parse received token transaction: Failed to get blockchain context"); + continue; + }; + match token_transaction_parser::parse_token_transaction( event.ctx, event.parsed, &token_transaction.context.sqlx_client, &token_transaction.context.owners_cache, + context.blockchain_context, ) .await { @@ -80,13 +86,14 @@ impl TokenTransaction { #[derive(Debug)] pub struct TokenTransactionContext { - pub account: UInt256, - pub block_hash: UInt256, + pub account: HashBytes, + pub block_hash: HashBytes, pub block_utime: u32, - pub transaction_hash: UInt256, - pub transaction: ton_block::Transaction, + pub transaction_hash: HashBytes, + pub transaction: Transaction, pub token_state: ExistingContract, - pub in_msg: ton_block::Message, + pub in_msg: OwnedMessage, + pub blockchain_context: Option, } #[derive(Debug)] @@ -118,6 +125,7 @@ impl ReadFromTransaction for TokenTransactionEvent { transaction: ctx.transaction.clone(), token_state: token_state.clone(), in_msg: ctx.in_msg.clone(), + blockchain_context: ctx.blockchain_context.clone(), }, parsed: parsed.clone(), state, diff --git a/src/ton_core/monitoring/token_transaction_parser.rs b/src/ton_core/monitoring/token_transaction_parser.rs index c0a5463..247748c 100644 --- a/src/ton_core/monitoring/token_transaction_parser.rs +++ b/src/ton_core/monitoring/token_transaction_parser.rs @@ -1,12 +1,10 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{TokenIncomingTransfer, TokenWalletTransaction}; -use num_bigint::BigUint; -use ton_block::{GetRepresentationHash, MsgAddressInt}; -use ton_types::{AccountId, BuilderData}; +use tycho_types::cell::Cell; use uuid::Uuid; use crate::ton_core::*; +use crate::utils::token_wallets::models::{TokenIncomingTransfer, TokenWalletTransaction}; struct ParseContext<'a> { sqlx_client: &'a SqlxClient, @@ -18,6 +16,7 @@ pub async fn parse_token_transaction( parsed_token_transaction: TokenWalletTransaction, sqlx_client: &SqlxClient, owners_cache: &OwnersCache, + context: BlockchainContext, ) -> Result { let parse_ctx = ParseContext { sqlx_client, @@ -26,10 +25,10 @@ pub async fn parse_token_transaction( let parsed = match parsed_token_transaction { TokenWalletTransaction::IncomingTransfer(transfer) => { - internal_transfer_receive(token_transaction_ctx, transfer, parse_ctx).await? + internal_transfer_receive(token_transaction_ctx, transfer, parse_ctx, context).await? } TokenWalletTransaction::Accept(tokens) => { - internal_transfer_mint(token_transaction_ctx, tokens, parse_ctx).await? + internal_transfer_mint(token_transaction_ctx, tokens, parse_ctx, context).await? } TokenWalletTransaction::OutgoingTransfer(token_transfer) => { internal_transfer_send( @@ -37,15 +36,23 @@ pub async fn parse_token_transaction( token_transfer.tokens, Some(token_transfer.payload), parse_ctx, + context, ) .await? } TokenWalletTransaction::SwapBack(swap_back) => { - internal_transfer_send(token_transaction_ctx, swap_back.tokens, None, parse_ctx).await? + internal_transfer_send( + token_transaction_ctx, + swap_back.tokens, + None, + parse_ctx, + context, + ) + .await? } TokenWalletTransaction::TransferBounced(tokens) | TokenWalletTransaction::SwapBackBounced(tokens) => { - internal_transfer_bounced(token_transaction_ctx, tokens, parse_ctx).await? + internal_transfer_bounced(token_transaction_ctx, tokens, parse_ctx, context).await? } }; @@ -54,49 +61,49 @@ pub async fn parse_token_transaction( async fn internal_transfer_send( token_transaction_ctx: TokenTransactionContext, - tokens: BigUint, - payload_cell: Option, + tokens: u128, + payload_cell: Option, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { - let address = MsgAddressInt::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); - let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let mut message_hash = Default::default(); - let _ = token_transaction_ctx - .transaction - .out_msgs - .iterate(|ton_block::InRefValue(message)| { - message_hash = message.hash().unwrap_or_default().to_hex_string(); - Ok(false) - }); + for message in token_transaction_ctx.transaction.iter_out_msgs() { + let message = message?; + let cell_builder = CellBuilder::build_from(&message)?; + message_hash = cell_builder.repr_hash().to_string(); + } let in_message_hash = token_transaction_ctx .transaction .in_msg - .clone() - .map(|message| message.hash().to_hex_string()) + .as_ref() + .map(|message| message.repr_hash().to_string()) .unwrap_or_default(); let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, - account_workchain_id: owner_info.owner_address.workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, root_address: owner_info.root_address.to_string(), value: -BigDecimal::new(tokens.into(), 0), - payload: payload_cell.map(|c| c.write_to_bytes()).transpose()?, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + payload: payload_cell.map(Boc::encode), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Send, status: TonTokenTransactionStatus::Done, @@ -111,50 +118,42 @@ async fn internal_transfer_receive( token_transaction_ctx: TokenTransactionContext, token_transfer: TokenIncomingTransfer, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { - let address = MsgAddressInt::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); - let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let message_hash = token_transaction_ctx .transaction .in_msg .clone() - .map(|message| message.hash().to_hex_string()) + .map(|message| message.repr_hash().to_string()) .unwrap_or_default(); - let payload: Option = { - let mut bd = BuilderData::new(); - if token_transaction_ctx.in_msg.write_to(&mut bd).is_ok() { - Some(bd.into_cell()?) - } else { - None - } - }; + let payload = CellBuilder::build_from(token_transaction_ctx.in_msg)?; let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, - account_workchain_id: owner_info.owner_address.get_workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), - sender_workchain_id: Some(token_transfer.sender_address.workchain_id()), - sender_hex: Some(token_transfer.sender_address.address().to_hex_string()), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), + sender_workchain_id: Some(token_transfer.sender_address.workchain as i32), + sender_hex: Some(token_transfer.sender_address.address.to_string()), value: BigDecimal::new(token_transfer.tokens.into(), 0), root_address: owner_info.root_address.to_string(), - payload: payload - .map(|m| m.write_to_bytes()) - .transpose() - .unwrap_or(None), + payload: Some(Boc::encode(payload)), error: None, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Receive, status: TonTokenTransactionStatus::Done, @@ -166,39 +165,41 @@ async fn internal_transfer_receive( async fn internal_transfer_bounced( token_transaction_ctx: TokenTransactionContext, - tokens: BigUint, + tokens: u128, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { - let address = MsgAddressInt::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); - let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let message_hash = token_transaction_ctx .transaction .in_msg .clone() - .map(|message| message.hash().to_hex_string()) + .map(|message| message.repr_hash().to_string()) .unwrap_or_default(); let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, message_hash, owner_message_hash: None, - account_workchain_id: owner_info.owner_address.workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, root_address: owner_info.root_address.to_string(), value: BigDecimal::new(tokens.into(), 0), payload: None, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Send, status: TonTokenTransactionStatus::Done, @@ -211,40 +212,42 @@ async fn internal_transfer_bounced( async fn internal_transfer_mint( token_transaction_ctx: TokenTransactionContext, - tokens: BigUint, + tokens: u128, parse_ctx: ParseContext<'_>, + context: BlockchainContext, ) -> Result { - let address = MsgAddressInt::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(token_transaction_ctx.account), - )?; + let address = StdAddr::new(0, token_transaction_ctx.account); - let owner_info = - get_token_wallet_info(&address, &parse_ctx, &token_transaction_ctx.token_state).await?; + let owner_info = get_token_wallet_info( + &address, + &parse_ctx, + token_transaction_ctx.token_state, + context, + ) + .await?; let message_hash = token_transaction_ctx .transaction .in_msg .clone() - .map(|message| message.hash()) + .map(|message| *message.repr_hash()) .unwrap_or_default(); let transaction = CreateTokenTransaction { id: Uuid::new_v4(), - transaction_hash: Some(token_transaction_ctx.transaction_hash.to_hex_string()), + transaction_hash: Some(token_transaction_ctx.transaction_hash.to_string()), transaction_timestamp: token_transaction_ctx.block_utime, - message_hash: message_hash.to_hex_string(), + message_hash: message_hash.to_string(), owner_message_hash: None, - account_workchain_id: owner_info.owner_address.get_workchain_id(), - account_hex: owner_info.owner_address.address().to_hex_string(), + account_workchain_id: owner_info.owner_address.workchain as i32, + account_hex: owner_info.owner_address.address.to_string(), sender_workchain_id: None, sender_hex: None, value: BigDecimal::new(tokens.into(), 0), root_address: owner_info.root_address.to_string(), payload: None, error: None, - block_hash: token_transaction_ctx.block_hash.to_hex_string(), + block_hash: token_transaction_ctx.block_hash.to_string(), block_time: token_transaction_ctx.block_utime as i32, direction: TonTransactionDirection::Receive, status: TonTokenTransactionStatus::Done, @@ -255,17 +258,18 @@ async fn internal_transfer_mint( } async fn get_token_wallet_info( - contract_address: &MsgAddressInt, + contract_address: &StdAddr, parse_ctx: &ParseContext<'_>, - contract: &ExistingContract, + contract: ExistingContract, + context: BlockchainContext, ) -> Result { let res = match parse_ctx.owners_cache.get(contract_address).await { None => { - let (wallet, version, hash) = get_token_wallet_details(contract)?; + let (wallet, version, hash) = get_token_wallet_details(contract, context)?; let info = OwnerInfo { owner_address: wallet.owner_address, root_address: wallet.root_address, - code_hash: hash.to_vec(), + code_hash: hash, version, }; diff --git a/src/ton_core/monitoring/ton_transaction.rs b/src/ton_core/monitoring/ton_transaction.rs index 87d1c0c..5307a71 100644 --- a/src/ton_core/monitoring/ton_transaction.rs +++ b/src/ton_core/monitoring/ton_transaction.rs @@ -85,10 +85,10 @@ impl TonTransaction { #[derive(Debug)] pub struct TonTransactionEvent { - pub account: UInt256, + pub account: HashBytes, pub block_utime: u32, - pub transaction_hash: UInt256, - pub transaction: ton_block::Transaction, + pub transaction_hash: HashBytes, + pub transaction: Transaction, pub state: HandleTransactionStatusTx, } diff --git a/src/ton_core/monitoring/ton_transaction_parser.rs b/src/ton_core/monitoring/ton_transaction_parser.rs index a4a35dc..0379ffa 100644 --- a/src/ton_core/monitoring/ton_transaction_parser.rs +++ b/src/ton_core/monitoring/ton_transaction_parser.rs @@ -1,44 +1,50 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{MultisigTransaction, TransactionError}; -use nekoton::core::ton_wallet::MultisigType; use num_traits::FromPrimitive; use serde::{Deserialize, Serialize}; -use ton_block::{CommonMsgInfo, GetRepresentationHash, MsgAddressInt}; -use ton_types::AccountId; use uuid::Uuid; -use crate::ton_core::*; +use crate::{ + ton_core::*, + utils::{ + multisig::{models::MultisigTransaction, parsing}, + ton_wallet::MultisigType, + }, +}; + +#[derive(thiserror::Error, Debug, Copy, Clone)] +pub enum TransactionError { + #[error("Invalid transaction structure")] + InvalidStructure, + #[error("Unsupported transaction type")] + Unsupported, +} pub async fn parse_ton_transaction( - account: UInt256, + account: HashBytes, block_utime: u32, - transaction_hash: UInt256, - transaction: ton_block::Transaction, + transaction_hash: HashBytes, + transaction: Transaction, ) -> Result { - let in_msg = match &transaction.in_msg { - Some(message) => message - .read_struct() - .map_err(|_| TransactionError::InvalidStructure)?, + let in_msg = match transaction.in_msg.as_ref() { + Some(in_msg_cell) => OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?, None => return Err(TransactionError::Unsupported.into()), }; - let address = MsgAddressInt::with_standart( - None, - ton_block::BASE_WORKCHAIN_ID as i8, - AccountId::from(account), - )?; + + let address = StdAddr::new(0, account); let sender_address = get_sender_address(&transaction)?; let (sender_workchain_id, sender_hex) = match &sender_address { Some(address) => ( - Some(address.workchain_id()), - Some(address.address().to_hex_string()), + Some(address.workchain as i32), + Some(address.address.to_string()), ), None => (None, None), }; - let message_hash = in_msg.hash()?.to_hex_string(); - let transaction_hash = Some(transaction_hash.to_hex_string()); + let cell_builder = CellBuilder::build_from(&in_msg).map_err(anyhow::Error::from)?; + let message_hash = cell_builder.repr_hash().to_string(); + let transaction_hash = Some(transaction_hash.to_string()); let transaction_lt = BigDecimal::from_u64(transaction.lt); let transaction_scan_lt = Some(transaction.lt as i64); let transaction_timestamp = block_utime; @@ -46,19 +52,19 @@ pub async fn parse_ton_transaction( let messages_hash = Some(serde_json::to_value(get_messages_hash(&transaction)?)?); let fee = BigDecimal::from_u128(compute_fees(&transaction)); let value = BigDecimal::from_u128(compute_value(&transaction)); - let balance_change = BigDecimal::from_i128(nekoton_utils::compute_balance_change(&transaction)); - let multisig_transaction_id = nekoton::core::parsing::parse_multisig_transaction( - MultisigType::SafeMultisigWallet, - &transaction, - ) - .and_then(|transaction| match transaction { - MultisigTransaction::Confirm(transaction) => Some(transaction.transaction_id as i64), - MultisigTransaction::Submit(transaction) => Some(transaction.trans_id as i64), - _ => None, - }); - - let parsed = match in_msg.header() { - CommonMsgInfo::IntMsgInfo(header) => { + let balance_change = BigDecimal::from_i128(compute_balance_change(&transaction)); + let multisig_transaction_id = + parsing::parse_multisig_transaction(MultisigType::SafeMultisigWallet, &transaction) + .and_then(|transaction| match transaction { + MultisigTransaction::Confirm(transaction) => { + Some(transaction.transaction_id as i64) + } + MultisigTransaction::Submit(transaction) => Some(transaction.trans_id as i64), + _ => None, + }); + + let parsed = match in_msg.info { + MsgInfo::Int(header) => { CaughtTonTransaction::Create(CreateReceiveTransaction { id: Uuid::new_v4(), message_hash, @@ -69,8 +75,8 @@ pub async fn parse_ton_transaction( transaction_timestamp, sender_workchain_id, sender_hex, - account_workchain_id: address.workchain_id(), - account_hex: address.address().to_hex_string(), + account_workchain_id: address.workchain as i32, + account_hex: address.address.to_string(), messages, messages_hash, data: None, // TODO @@ -87,11 +93,11 @@ pub async fn parse_ton_transaction( multisig_transaction_id, }) } - CommonMsgInfo::ExtInMsgInfo(_) => { + MsgInfo::ExtIn(_) => { CaughtTonTransaction::UpdateSent(UpdateSentTransaction { message_hash, - account_workchain_id: address.workchain_id(), - account_hex: address.address().to_hex_string(), + account_workchain_id: address.workchain as i32, + account_hex: address.address.to_string(), input: UpdateSendTransaction { transaction_hash, transaction_lt, @@ -111,118 +117,155 @@ pub async fn parse_ton_transaction( }, }) } - CommonMsgInfo::ExtOutMsgInfo(_) => return Err(TransactionError::InvalidStructure.into()), + MsgInfo::ExtOut(_) => return Err(TransactionError::InvalidStructure.into()), }; Ok(parsed) } -fn get_sender_address(transaction: &ton_block::Transaction) -> Result> { +fn get_sender_address(transaction: &Transaction) -> Result> { let in_msg = transaction - .in_msg - .as_ref() - .ok_or(TransactionError::InvalidStructure)? - .read_struct()?; - Ok(in_msg.src()) + .load_in_msg()? + .ok_or(TransactionError::InvalidStructure)?; + match in_msg.info { + MsgInfo::Int(info) => Ok(info.src.as_std().cloned()), + MsgInfo::ExtIn(_) => Ok(None), + MsgInfo::ExtOut(info) => Ok(info.src.as_std().cloned()), + } } -fn get_messages(transaction: &ton_block::Transaction) -> Result> { +fn get_messages(transaction: &Transaction) -> Result> { let mut out_msgs = Vec::new(); - transaction - .out_msgs - .iterate(|ton_block::InRefValue(item)| { - let fee = match item.get_fee()? { - Some(fee) => Some( - BigDecimal::from_u128(fee.as_u128()) + for message in transaction.iter_out_msgs() { + let message = message?; + let (fee, value, recipient) = match &message.info { + MsgInfo::Int(info) => ( + Some( + BigDecimal::from_u128(info.fwd_fee.into_inner()) .ok_or(TransactionError::InvalidStructure)?, ), - None => None, - }; - - let value = match item.get_value() { - Some(value) => Some( - BigDecimal::from_u128(value.grams.as_u128()) + Some( + BigDecimal::from_u128(info.value.tokens.into_inner()) .ok_or(TransactionError::InvalidStructure)?, ), - None => None, - }; - - let recipient = match item.header().get_dst_address() { - Some(dst) => Some(MessageRecipient { - hex: dst.address().to_hex_string(), - base64url: nekoton_utils::pack_std_smc_addr(true, &dst, true)?, - workchain_id: dst.workchain_id(), + info.dst.as_std().map(|dst| MessageRecipient { + hex: dst.address.to_string(), + base64url: dst.display_base64_url(true).to_string(), + workchain_id: dst.workchain as i32, }), - None => None, - }; - - out_msgs.push(Message { - fee, - value, - recipient, - message_hash: item.hash()?.to_hex_string(), - }); - - Ok(true) - }) - .map_err(|_| TransactionError::InvalidStructure)?; + ), + MsgInfo::ExtIn(info) => ( + None, + None, + info.dst.as_std().map(|dst| MessageRecipient { + hex: dst.address.to_string(), + base64url: dst.display_base64_url(true).to_string(), + workchain_id: dst.workchain as i32, + }), + ), + MsgInfo::ExtOut(_) => (None, None, None), + }; + + let cell_builder = CellBuilder::build_from(&message)?; + let message_hash = cell_builder.repr_hash().to_string(); + + out_msgs.push(Message { + fee, + value, + recipient, + message_hash, + }); + } Ok(out_msgs) } -fn get_messages_hash(transaction: &ton_block::Transaction) -> Result> { +fn get_messages_hash(transaction: &Transaction) -> Result> { let mut hashes = Vec::new(); - transaction - .out_msgs - .iterate(|ton_block::InRefValue(item)| { - hashes.push(item.hash()?.to_hex_string()); - Ok(true) - }) - .map_err(|_| TransactionError::InvalidStructure)?; + for message in transaction.iter_out_msgs() { + let message = message?; + let cell_builder = CellBuilder::build_from(&message)?; + hashes.push(cell_builder.repr_hash().to_string()); + } Ok(hashes) } -fn compute_value(transaction: &ton_block::Transaction) -> u128 { +fn compute_value(transaction: &Transaction) -> u128 { let mut value = 0; - if let Some(in_msg) = transaction - .in_msg - .as_ref() - .and_then(|data| data.read_struct().ok()) - { - if let ton_block::CommonMsgInfo::IntMsgInfo(header) = in_msg.header() { - value += header.value.grams.as_u128(); + if let Ok(Some(in_msg)) = transaction.load_in_msg() { + if let MsgInfo::Int(header) = in_msg.info { + value += header.value.tokens.into_inner(); } } - let _ = transaction.out_msgs.iterate(|out_msg| { - if let CommonMsgInfo::IntMsgInfo(header) = out_msg.0.header() { - value += header.value.grams.as_u128(); + for message in transaction.iter_out_msgs() { + let message = message.unwrap(); + if let MsgInfo::Int(header) = message.info { + value += header.value.tokens.into_inner(); } - Ok(true) - }); + } value } -fn compute_fees(transaction: &ton_block::Transaction) -> u128 { - let mut fees = 0; - if let Ok(ton_block::TransactionDescr::Ordinary(description)) = - transaction.description.read_struct() - { - fees = nekoton_utils::compute_total_transaction_fees(transaction, &description) +fn compute_fees(transaction: &Transaction) -> u128 { + let mut total_fees = 0; + if let Ok(TxInfo::Ordinary(info)) = transaction.load_info() { + total_fees += compute_total_transaction_fees(transaction, &info); + } + total_fees +} + +pub fn compute_balance_change(transaction: &Transaction) -> i128 { + let mut diff = 0; + + if let Ok(Some(in_msg)) = transaction.load_in_msg() { + if let MsgInfo::Int(header) = in_msg.info { + diff += header.value.tokens.into_inner() as i128; + } + } + + for message in transaction.iter_out_msgs() { + let message = message.unwrap(); + if let MsgInfo::Int(header) = message.info { + diff -= header.value.tokens.into_inner() as i128; + } + } + + if let Ok(TxInfo::Ordinary(info)) = transaction.load_info() { + diff -= compute_total_transaction_fees(transaction, &info) as i128; + } + + diff +} + +pub fn compute_total_transaction_fees(transaction: &Transaction, info: &OrdinaryTxInfo) -> u128 { + let mut total_fees = transaction.total_fees.tokens.into_inner(); + if let Some(phase) = &info.action_phase { + total_fees += phase + .total_fwd_fees + .as_ref() + .map(|tokens| tokens.into_inner()) + .unwrap_or_default(); + total_fees -= phase + .total_action_fees + .as_ref() + .map(|tokens| tokens.into_inner()) + .unwrap_or_default(); + }; + if let Some(BouncePhase::Executed(phase)) = &info.bounce_phase { + total_fees += phase.fwd_fees.into_inner(); } - fees + total_fees } -fn is_aborted(transaction: &ton_block::Transaction) -> bool { +fn is_aborted(transaction: &Transaction) -> bool { let mut aborted = false; - if let Ok(ton_block::TransactionDescr::Ordinary(description)) = - transaction.description.read_struct() - { - aborted = description.aborted + if let Ok(TxInfo::Ordinary(info)) = transaction.load_info() { + aborted = info.aborted } aborted } @@ -247,10 +290,9 @@ struct MessageRecipient { #[cfg(test)] mod tests { use super::*; - use ton_block::{Deserializable, MsgAddressInt, Transaction}; fn mock_transaction_with_message() -> Transaction { - Transaction::construct_from_base64( + let binding = Boc::decode_base64( "te6ccgECEAEAAwgAA7d+QDCcWfS7Pd3OhqYgoQVempmo2OKQO5sOYx6EZBcyIbAAAuGThxKAhf1hAS\ h02tBmYeWRHurQLFdhsiPgWeGNTbabaiPlZZ9gAALhk4cSgGZnCjIwADSAIfqQaAUEAQIXBAkFUFwjGIAhHJARA\ wIAb8mKaBBMG8AMAAAAAAAEAAIAAAADVRiS8otLi359fajChkMh4j7YPNNVzsOUbNa9QsXWtVZBkDxsAJ5IegwV\ @@ -265,26 +307,26 @@ mod tests { wQAAgBBa0QhOP1bKLS5gSbcEj5AP6sELkypNssupdc0rEL8tMA4BQ4AQWtEITj9Wyi0uYEm3BI+QD+rBC5MqTbL\ LqXXNKxC/LTgPAAA=", ) - .unwrap() - } - - fn mock_transaction_without_message() -> Transaction { - Transaction::default() + .unwrap(); + let mut cell = binding.as_slice().unwrap(); + Transaction::load_from(&mut cell).unwrap() } fn mock_native_transaction() -> Transaction { - Transaction::construct_from_base64( + let binding = Boc::decode_base64( "te6ccgECBQEAAQ8AA7VxLMcNYtT0Y0vHvF0Y6p6uYuZ3ru6E15MPbdMAiDOW+TAAAxF6kJyoOBX6Ew\ /7kDzBL0X5vbiyJUQxs8oqMCx81lJVpHEGWGhQAALk17a3eDZv7sCQAABgJyfoAwIBABUMwE5PyQF9eEABIACCc\ qeMvpds7qXtp0X7fcfK29e715cYDMD4djDoZFaoV2+IniF4UEqnl0mRBkkJUofiHH0OEnxt4bqWdhOvrktU02MB\ AaAEALFIAQWtEITj9Wyi0uYEm3BI+QD+rBC5MqTbLLqXXNKxC/LTAASzHDWLU9GNLx7xdGOqermLmd67uhNeTD2\ 3TAIgzlvk0BfXhAAGCiwwAABiL1ITlQTN/dgSQA==", ) - .unwrap() + .unwrap(); + let mut cell = binding.as_slice().unwrap(); + Transaction::load_from(&mut cell).unwrap() } fn mock_tip3_transaction() -> Transaction { - Transaction::construct_from_base64( + let binding = Boc::decode_base64( "te6ccgECDAEAAl0AA7V/QK7VX0Cd/1ZlF9CjnQU/zjx5R/+gPcjjC/w75jghPeAAAxF68tckc9Q9f/\ cDtEaGB89WcFPg7Kg/ufjqtloFybIORllBjolwAAMRevLXJGZv7tMQADR8gi0IBQQBAhUECQT+XD4YfDDMEQMCA\ G/Jg9CQTAosIAAAAAAABAACAAAAA/Sl/SUL5ko0FMc/s2rL0MTaDiZjYIA0X+j0FcjV3p3wQFAWDACeRzeMFHQo\ @@ -296,7 +338,9 @@ mod tests { N/dpiwAkBa2eguV8AAAAAAAAAAAAACRhOcqAAgBBa0QhOP1bKLS5gSbcEj5AP6sELkypNssupdc0rEL8tMAoBQ4\ AQWtEITj9Wyi0uYEm3BI+QD+rBC5MqTbLLqXXNKxC/LSgLAAA=", ) - .unwrap() + .unwrap(); + let mut cell = binding.as_slice().unwrap(); + Transaction::load_from(&mut cell).unwrap() } #[test] @@ -307,7 +351,7 @@ mod tests { assert_eq!( result.unwrap(), Some( - MsgAddressInt::from_str( + StdAddr::from_str( "0:fd7cb9aa109bec4fd39f3b8c3a21b661caacbc161a8c6331be6bb88a4e7ff720" ) .unwrap() @@ -323,7 +367,7 @@ mod tests { assert_eq!( result.unwrap(), Some( - MsgAddressInt::from_str( + StdAddr::from_str( "0:fd7cb9aa109bec4fd39f3b8c3a21b661caacbc161a8c6331be6bb88a4e7ff720" ) .unwrap() @@ -339,19 +383,11 @@ mod tests { assert_eq!( result.unwrap(), Some( - MsgAddressInt::from_str( + StdAddr::from_str( "0:82d6884271fab6516973024db8247c807f56085c99526d965d4bae695885f969" ) .unwrap() ) ); } - - #[test] - fn test_get_sender_address_without_message() { - // Simulate a transaction without an incoming message - let transaction = mock_transaction_without_message(); - let result = get_sender_address(&transaction); - assert!(result.is_err()); // Expect an error - } } diff --git a/src/ton_core/ton_subscriber/mod.rs b/src/ton_core/ton_subscriber/mod.rs index b39f472..41f4d54 100644 --- a/src/ton_core/ton_subscriber/mod.rs +++ b/src/ton_core/ton_subscriber/mod.rs @@ -5,38 +5,56 @@ use std::sync::{Arc, Weak}; use anyhow::Result; use futures::stream::FuturesUnordered; use futures::StreamExt; -use nekoton::core::models::TokenWalletVersion; -use nekoton::transport::models::ExistingContract; -use nekoton_utils::TrustMe; use parking_lot::{Mutex, RwLock, RwLockReadGuard}; use rustc_hash::FxHashMap; -use tycho_types::boc::Boc; use tycho_types::cell::{Cell, CellBuilder, HashBytes, Load}; -use ton_block::Deserializable; -use ton_types::SliceData; +use nekoton_core::contracts::blockchain_context::{BlockchainContext, BlockchainContextBuilder}; +use nekoton_core::transport::SimpleTransport; use tycho_block_util::block::BlockStuff; use tycho_block_util::state::{RefMcStateHandle, ShardStateStuff}; use tycho_vm::StackValue; +use crate::models::ExistingContract; use crate::ton_core::*; +use crate::utils::token_wallets::models::TokenWalletVersion; +use crate::utils::token_wallets::parsing; pub struct TonSubscriber { - // tip block timestamp current_utime: AtomicU32, signature_id: SignatureId, + capabilities: Capabilities, state_subscriptions: RwLock>, token_subscription: RwLock>, sc_accounts: RwLock>, mc_block_awaiters: Mutex>>, messages_queue: Arc, + blockchain_context: RwLock, } impl TonSubscriber { - pub fn new(messages_queue: Arc) -> Arc { + pub fn new( + messages_queue: Arc, + capabilities_bits: u64, + global_id: i32, + config: BlockchainConfig, + ) -> Arc { + let signature_id = SignatureId::default(); + signature_id.store(capabilities_bits, global_id); + + let capabilities = Capabilities(AtomicU64::new(capabilities_bits)); + + let transport = SimpleTransport::new(vec![], config.clone()).unwrap(); + + let blockchain_context = BlockchainContextBuilder::new() + .with_config(config) + .with_transport(Arc::new(transport)) + .build() + .unwrap(); + Arc::new(Self { current_utime: AtomicU32::new(0), - signature_id: SignatureId::default(), + signature_id, state_subscriptions: RwLock::new(FxHashMap::with_capacity_and_hasher( 1024, Default::default(), @@ -48,6 +66,8 @@ impl TonSubscriber { Default::default(), )), messages_queue, + capabilities, + blockchain_context: RwLock::new(blockchain_context), }) } @@ -60,11 +80,6 @@ impl TonSubscriber { } } - pub async fn start(self: &Arc, capabilities: u64, global_id: i32) -> Result<()> { - self.update_signature_id(capabilities, global_id)?; - Ok(()) - } - pub fn current_utime(&self) -> u32 { self.current_utime.load(Ordering::Acquire) } @@ -73,6 +88,14 @@ impl TonSubscriber { self.signature_id.load() } + pub fn capabilities(&self) -> u64 { + self.capabilities.0.load(Ordering::Acquire) + } + + pub fn blockchain_context(&self) -> BlockchainContext { + self.blockchain_context.read().clone() + } + pub fn add_transactions_subscription(&self, accounts: I, subscription: &Arc) where I: IntoIterator, @@ -156,6 +179,17 @@ impl TonSubscriber { } }, ); + if block_info.key_block { + let extra = block.load_extra()?; + let custom = extra.load_custom()?; + if let Some(custom) = custom { + if let Some(config) = custom.config { + let global_version = config.get_global_version()?; + self.update_capabilies(global_version.capabilities.into_inner())?; + self.update_blockchain_context(config)?; + } + } + } Ok(()) } @@ -231,6 +265,7 @@ impl TonSubscriber { let state_subscriptions = self.state_subscriptions.read(); let token_subscription = self.token_subscription.read(); let shards_accounts_cache = self.sc_accounts.read(); + let blockchain_context = self.blockchain_context(); for account_block in account_blocks.iter() { let (account, _, account_block) = account_block?; @@ -254,7 +289,9 @@ impl TonSubscriber { }; } None => { - let token_subscription = token_subscription.as_ref().trust_me(); + let Some(token_subscription) = token_subscription.as_ref() else { + continue; + }; match token_subscription.handle_block( &state_subscriptions, @@ -263,6 +300,7 @@ impl TonSubscriber { &account_block, &account, block_hash, + blockchain_context.clone(), ) { Ok(rx_states) => { if !rx_states.is_empty() { @@ -283,8 +321,22 @@ impl TonSubscriber { Ok(states) } - fn update_signature_id(&self, capabilities: u64, global_id: i32) -> Result<()> { - self.signature_id.store(capabilities, global_id); + fn update_capabilies(&self, capabilities: u64) -> Result<()> { + self.capabilities.0.store(capabilities, Ordering::Release); + Ok(()) + } + + fn update_blockchain_context(&self, config: BlockchainConfig) -> Result<()> { + let mut blockchain_context = self.blockchain_context.write(); + + let transport = SimpleTransport::new(vec![], config.clone())?; + + let context = BlockchainContextBuilder::new() + .with_config(config) + .with_transport(Arc::new(transport)) + .build()?; + + *blockchain_context = context; Ok(()) } } @@ -340,7 +392,7 @@ impl StateSubscription { let hash = *value.repr_hash(); value.load().map(|tx| (tx, hash)) }); - let (transaction, hash) = match result { + let (transaction, transaction_hash) = match result { Ok((tx, transaction_hash)) => (tx, transaction_hash), Err(e) => { tracing::error!( @@ -353,29 +405,24 @@ impl StateSubscription { } }; - let account = UInt256::with_array(account.0); - let transaction_hash = UInt256::with_array(hash.0); - let block_hash = UInt256::with_array(block_hash.0); - let transaction = conver_to_old_transaction(&transaction)?; + let account = *account; + let block_hash = *block_hash; // Skip non-ordinary transactions - let transaction_info = match transaction.description.read_struct() { - Ok(ton_block::TransactionDescr::Ordinary(info)) => info, + let transaction_info = match transaction.load_info() { + Ok(TxInfo::Ordinary(info)) => info, _ => continue, }; - let in_msg = match transaction - .in_msg - .as_ref() - .map(|message| (message, message.read_struct())) - { - Some((message_cell, Ok(message))) => { - if matches!(message.header(), ton_block::CommonMsgInfo::ExtInMsgInfo(_)) { + let in_msg = match transaction.in_msg.as_ref() { + Some(in_msg_cell) => { + let in_msg = OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?; + if matches!(in_msg.info, MsgInfo::ExtIn(_)) { messages_queue.deliver_message( - HashBytes::from_slice(account.as_slice()), - HashBytes::from_slice(message_cell.hash().as_slice()), + account, + *CellBuilder::build_from(&in_msg)?.repr_hash(), ); } - message + in_msg } _ => continue, }; @@ -390,6 +437,7 @@ impl StateSubscription { in_msg: &in_msg, token_transaction: &None, token_state: &None, + blockchain_context: &None, }; // Handle transaction @@ -402,7 +450,7 @@ impl StateSubscription { Err(e) => { tracing::error!( "Failed to handle transaction {} for account {}: {:?}", - hash.to_string(), + transaction_hash.to_string(), account.to_string(), e ); @@ -428,6 +476,7 @@ impl TokenSubscription { account_block: &AccountBlock, account: &HashBytes, block_hash: &HashBytes, + blockchain_context: BlockchainContext, ) -> Result> { let states = FuturesUnordered::new(); @@ -436,7 +485,7 @@ impl TokenSubscription { let hash = *value.repr_hash(); value.load().map(|tx| (tx, hash)) }); - let (transaction, hash) = match result { + let (transaction, transaction_hash) = match result { Ok((tx, transaction_hash)) => (tx, transaction_hash), Err(e) => { tracing::error!( @@ -449,23 +498,21 @@ impl TokenSubscription { } }; - let account = UInt256::with_array(account.0); - let transaction_hash = UInt256::with_array(hash.0); - let block_hash = UInt256::with_array(block_hash.0); - let transaction = conver_to_old_transaction(&transaction)?; + let account = *account; + let block_hash = *block_hash; // Skip non-ordinary transactions - let transaction_info = match transaction.description.read_struct() { - Ok(ton_block::TransactionDescr::Ordinary(info)) => info, + let transaction_info = match transaction.load_info() { + Ok(TxInfo::Ordinary(info)) => info, _ => continue, }; - let parsed_token_transaction = match nekoton::core::parsing::parse_token_transaction( + let parsed_token_transaction = match parsing::parse_token_transaction( &transaction, &transaction_info, TokenWalletVersion::Tip3, ) { Some(parsed_token_transaction) => Some(parsed_token_transaction), - None => nekoton::core::parsing::parse_token_transaction( + None => parsing::parse_token_transaction( &transaction, &transaction_info, TokenWalletVersion::OldTip3v4, @@ -477,24 +524,13 @@ impl TokenSubscription { .find_account(&HashBytes::from_slice(account.as_slice()))? .ok_or_else(|| TonCoreError::AccountNotExist(account.to_string()))?; - let (token_wallet_details, ..) = get_token_wallet_details(&token_contract)?; - let owner_account = UInt256::from_be_bytes( - &token_wallet_details - .owner_address - .address() - .get_bytestring(0), - ); - - if state_subscriptions - .get(&HashBytes::from_slice(owner_account.as_slice())) - .is_some() - { - let in_msg = match transaction - .in_msg - .as_ref() - .map(|message| (message, message.read_struct())) - { - Some((_, Ok(message))) => message, + let (token_wallet_details, ..) = + get_token_wallet_details(token_contract.clone(), blockchain_context.clone())?; + let owner_account = &token_wallet_details.owner_address.address; + + if state_subscriptions.get(owner_account).is_some() { + let in_msg = match transaction.in_msg.as_ref() { + Some(in_msg_cell) => OwnedMessage::load_from(&mut in_msg_cell.as_slice()?)?, _ => continue, }; @@ -508,6 +544,9 @@ impl TokenSubscription { in_msg: &in_msg, token_transaction: &Some(parsed), token_state: &Some(token_contract), + blockchain_context: &Some(BlockchainContextWrapper { + blockchain_context: blockchain_context.clone(), + }), }; if let Some(transaction_subscription) = self.transaction_subscription.upgrade() @@ -521,7 +560,7 @@ impl TokenSubscription { Err(e) => { tracing::error!( "Failed to handle token transaction {} for account {}: {:?}", - hash.to_string(), + transaction_hash.to_string(), account.to_string(), e ); @@ -588,7 +627,7 @@ where pub struct ShardAccount { pub(crate) data: Cell, - pub(crate) last_transaction_id: LastTransactionId, + pub(crate) last_transaction_hash: HashBytes, _state_handle: RefMcStateHandle, } @@ -599,28 +638,10 @@ pub fn make_existing_contract(state: Option) -> Result Result> { - let cell = CellBuilder::build_from(account)?; - let bytes = Boc::encode(cell); - let cell = ton_types::deserialize_tree_of_cells(&mut &*bytes)?; - match ton_block::Account::construct_from(&mut SliceData::load_cell(cell)?)? { - ton_block::Account::AccountNone => Ok(None), - ton_block::Account::Account(stuff) => Ok(Some(stuff)), - } + Ok(account.0.map(|account| ExistingContract { + account, + last_transaction_hash: state.last_transaction_hash, + })) } pub struct CachedAccounts { @@ -633,10 +654,7 @@ impl CachedAccounts { match self.accounts.get(account)? { Some((_, account)) => Ok(Some(ShardAccount { data: account.account.as_cell().unwrap().clone(), - last_transaction_id: LastTransactionId::Exact(TransactionId { - lt: account.last_trans_lt, - hash: UInt256::with_array(account.last_trans_hash.0), - }), + last_transaction_hash: account.last_trans_hash, _state_handle: self.state_handle.clone(), })), None => Ok(None), @@ -653,17 +671,12 @@ impl ShardAccountsMapExt for FxHashMap { match item { Some((_, shard)) => { if let Some((_, account)) = shard.accounts.get(account)? { - let last_transaction_id = LastTransactionId::Exact(TransactionId { - lt: account.last_trans_lt, - hash: UInt256::with_array(account.last_trans_hash.0), - }); - + let last_transaction_hash = account.last_trans_hash; let account = account.account.load()?; - if let Some(stuff) = convert_to_old_account(account)? { + if let Some(account) = account.0 { return Ok(Some(ExistingContract { - account: stuff, - timings: GenTimings::Unknown, - last_transaction_id, + account, + last_transaction_hash, })); } } @@ -674,6 +687,9 @@ impl ShardAccountsMapExt for FxHashMap { } } +#[derive(Default)] +struct Capabilities(pub AtomicU64); + #[derive(Default)] struct SignatureId(AtomicU64); diff --git a/src/utils/encoding.rs b/src/utils/encoding.rs index 5a5ffd4..f44c39e 100644 --- a/src/utils/encoding.rs +++ b/src/utils/encoding.rs @@ -1,4 +1,5 @@ use anyhow::{Error, Result}; +use base64::{engine::general_purpose, Engine as _}; use chacha20poly1305::aead::AeadMut; use chacha20poly1305::{ChaCha20Poly1305, Nonce}; @@ -8,7 +9,7 @@ pub fn encrypt_private_key(private_key: &[u8], key: [u8; 32], id: &uuid::Uuid) - let key = chacha20poly1305::Key::from_slice(&key[..]); let mut encryptor = ChaCha20Poly1305::new(key); let res = encryptor.encrypt(nonce, private_key).map_err(Error::msg)?; - Ok(base64::encode(res)) + Ok(general_purpose::STANDARD.encode(res)) } pub fn decrypt_private_key(private_key: &str, key: [u8; 32], id: &uuid::Uuid) -> Result> { @@ -17,6 +18,9 @@ pub fn decrypt_private_key(private_key: &str, key: [u8; 32], id: &uuid::Uuid) -> let key = chacha20poly1305::Key::from_slice(&key[..]); let mut decrypter = ChaCha20Poly1305::new(key); decrypter - .decrypt(nonce, base64::decode(private_key)?.as_slice()) + .decrypt( + nonce, + general_purpose::STANDARD.decode(private_key)?.as_slice(), + ) .map_err(Error::msg) } diff --git a/src/utils/existing_contract.rs b/src/utils/existing_contract.rs index 68a1807..2ffe68a 100644 --- a/src/utils/existing_contract.rs +++ b/src/utils/existing_contract.rs @@ -1,7 +1,12 @@ use anyhow::Result; -use nekoton::transport::models::ExistingContract; -use nekoton_abi::{ExecutionOutput, FunctionExt, GenTimings, LastTransactionId, TransactionId}; -use ton_block::{Account, ShardAccount}; +use nekoton_core::contracts::blockchain_context::BlockchainContext; +use nekoton_core::contracts::function_ext::{ExecutionOutput, FunctionExt}; +use tycho_types::{ + abi::{Function, NamedAbiValue}, + models::ShardAccount, +}; + +use crate::models::ExistingContract; pub trait ExistingContractExt { fn from_shard_account(shard_account: &ShardAccount) -> Result>; @@ -10,25 +15,24 @@ pub trait ExistingContractExt { ) -> Result>; fn run_local( - &self, - function: &ton_abi::Function, - input: &[ton_abi::Token], - ) -> Result>; + &mut self, + function: &Function, + input: &[NamedAbiValue], + responsible: bool, + context: &mut BlockchainContext, + ) -> Result>; } impl ExistingContractExt for ExistingContract { fn from_shard_account(shard_account: &ShardAccount) -> Result> { - Ok(match shard_account.read_account()? { - Account::Account(account) => Some(Self { + if let Some(account) = shard_account.load_account()? { + Ok(Some(Self { account, - timings: GenTimings::Unknown, - last_transaction_id: LastTransactionId::Exact(TransactionId { - lt: shard_account.last_trans_lt(), - hash: *shard_account.last_trans_hash(), - }), - }), - Account::AccountNone => None, - }) + last_transaction_hash: shard_account.last_trans_hash, + })) + } else { + Ok(None) + } } fn from_shard_account_opt(shard_account: &Option) -> Result> { @@ -39,21 +43,20 @@ impl ExistingContractExt for ExistingContract { } fn run_local( - &self, - function: &ton_abi::Function, - input: &[ton_abi::Token], - ) -> Result> { - let ExecutionOutput { - tokens, - result_code, - } = function.run_local( - &nekoton_utils::SimpleClock, - self.account.clone(), - input, - &[], - )?; + &mut self, + function: &Function, + input: &[NamedAbiValue], + responsible: bool, + context: &mut BlockchainContext, + ) -> Result> { + let ExecutionOutput { values, exit_code } = + function.run_local(&mut self.account, input, responsible, context)?; + + if exit_code != 0 { + return Err(ExistingContractError::NonZeroResultCode(exit_code).into()); + } - tokens.ok_or_else(|| ExistingContractError::NonZeroResultCode(result_code).into()) + Ok(values) } } diff --git a/src/utils/mnemonic/labs.rs b/src/utils/mnemonic/labs.rs new file mode 100644 index 0000000..6c3c05c --- /dev/null +++ b/src/utils/mnemonic/labs.rs @@ -0,0 +1,75 @@ +use std::convert::TryInto; + +use anyhow::Result; + +use super::{Bip39MnemonicData, LANGUAGE}; + +#[allow(unused)] +pub fn derive_master_key(phrase: &str) -> Result<[u8; 64]> { + let mnemonic = bip39::Mnemonic::from_phrase(phrase, LANGUAGE)?; + let hd = bip39::Seed::new(&mnemonic, ""); + Ok(hd.as_bytes().try_into().unwrap()) +} + +pub fn derive_from_phrase( + phrase: &str, + mnemonic_data: Bip39MnemonicData, +) -> Result { + let mnemonic = bip39::Mnemonic::from_phrase(phrase, LANGUAGE)?; + let hd = bip39::Seed::new(&mnemonic, ""); + let seed_bytes = hd.as_bytes(); + + let account_id = mnemonic_data.account_id; + let derived = mnemonic_data.path.derive(seed_bytes, account_id)?; + + Ok(ed25519_dalek::SigningKey::from_bytes(&derived)) +} + +#[cfg(test)] +mod tests { + use crate::utils::mnemonic::{Bip39Entropy, Bip39Path}; + + use super::*; + + #[test] + fn invalid_bip39_phrase() { + let key = derive_from_phrase( + "pioneer fever hazard scam install wise reform corn bubble leisure amazing note", + Bip39MnemonicData { + account_id: 0, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + }, + ); + assert!(key.is_err()); + } + + #[test] + fn correct_bip39_derive() { + let signing_key = derive_from_phrase( + "pioneer fever hazard scan install wise reform corn bubble leisure amazing note", + Bip39MnemonicData { + account_id: 0, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + }, + ) + .unwrap(); + + let key_hex = + hex::decode("e371ef1d7266fc47b30d49dc886861598f09e2e6294d7f0520fe9aa460114e51") + .unwrap(); + let mut key = [0u8; 32]; + key.copy_from_slice(&key_hex); + + let target_secret = ed25519_dalek::SigningKey::from_bytes(&key); + + assert_eq!(signing_key.as_bytes(), target_secret.as_bytes()) + } + + #[test] + fn master_key_derive() { + let ph = "pioneer fever hazard scan install wise reform corn bubble leisure amazing note"; + derive_master_key(ph).unwrap(); + } +} diff --git a/src/utils/mnemonic/legacy.rs b/src/utils/mnemonic/legacy.rs new file mode 100644 index 0000000..e4d3293 --- /dev/null +++ b/src/utils/mnemonic/legacy.rs @@ -0,0 +1,57 @@ +use anyhow::Error; +use pbkdf2::{ + hmac::{Hmac, Mac}, + pbkdf2_hmac, +}; + +use super::LANGUAGE; + +pub fn derive_from_phrase(phrase: &str) -> Result { + const PBKDF_ITERATIONS: u32 = 100_000; + const SALT: &[u8] = b"TON default seed"; + + let wordmap = LANGUAGE.wordmap(); + let mut word_count = 0; + for word in phrase.split_whitespace() { + word_count += 1; + if word_count > 24 { + anyhow::bail!("Expected 24 words") + } + + wordmap.get_bits(word)?; + } + if word_count != 24 { + anyhow::bail!("Expected 24 words") + } + + let password = Hmac::::new_from_slice(phrase.as_bytes()) + .unwrap() + .finalize() + .into_bytes(); + + let mut res = [0; 512 / 8]; + pbkdf2_hmac::(&password, SALT, PBKDF_ITERATIONS, &mut res); + Ok(ed25519_dalek::SigningKey::from_bytes( + &res[0..32].try_into().unwrap(), + )) +} + +#[cfg(test)] +mod tests { + use super::*; + + use base64::{engine::general_purpose, Engine as _}; + + #[test] + fn correct_legacy_derive() { + let keypair = derive_from_phrase("unaware face erupt ceiling frost shiver crumble know party before brisk skirt fence boat powder copy plastic until butter fluid property concert say verify").unwrap(); + let expected = "o0kpHL39KRq0KX11zZ0/sCwJL66t+gA4vnfuwBjhAWU="; + let pub_expecteed = "lHW4ZS8QvCHcgR4uChD7QJWU2kf5JRMtUnZ2p1GSZjg="; + assert_eq!( + general_purpose::STANDARD.encode(keypair.verifying_key().as_bytes()), + pub_expecteed + ); + let got = general_purpose::STANDARD.encode(keypair.as_bytes()); + assert_eq!(got, expected); + } +} diff --git a/src/utils/mnemonic/mod.rs b/src/utils/mnemonic/mod.rs new file mode 100644 index 0000000..902e204 --- /dev/null +++ b/src/utils/mnemonic/mod.rs @@ -0,0 +1,246 @@ +use anyhow::Error; +use rand::RngExt; +use serde::de::{MapAccess, Unexpected, Visitor}; +use serde::{de, Deserialize, Deserializer, Serialize}; +use sha2::Digest; +use slip10_ed25519::derive_ed25519_private_key; +use tiny_hderive::bip32::ExtendedPrivKey; + +pub(super) mod labs; +pub(super) mod legacy; + +const LANGUAGE: bip39::Language = bip39::Language::English; + +#[derive(Serialize, Copy, Clone, Debug, Eq, PartialEq)] +pub enum MnemonicType { + /// Phrase with 24 words, used in Crystal Wallet + Legacy, + /// Phrase with 12 or 24 words, used everywhere else. The additional parameter is used in + /// derivation path to create multiple keys from one mnemonic + Bip39(Bip39MnemonicData), +} + +impl MnemonicType { + pub fn account_id(self) -> u16 { + match self { + Self::Legacy => 0, + Self::Bip39(item) => item.account_id, + } + } +} + +impl<'de> Deserialize<'de> for MnemonicType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct MnemonicTypeVisitor; + + impl<'de> Visitor<'de> for MnemonicTypeVisitor { + type Value = MnemonicType; + + fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + formatter.write_str("string 'Legacy' or object with 'Labs' or 'Bip39' key") + } + + fn visit_str(self, value: &str) -> Result + where + E: de::Error, + { + match value { + "Legacy" => Ok(MnemonicType::Legacy), + _ => Err(de::Error::invalid_value(Unexpected::Str(value), &"Legacy")), + } + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + match map.next_key::()? { + Some(key) => match key.as_str() { + "Labs" => { + let account_id = map.next_value()?; + Ok(MnemonicType::Bip39(Bip39MnemonicData { + account_id, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + })) + } + "Bip39" => { + let data: Bip39MnemonicData = map.next_value()?; + Ok(MnemonicType::Bip39(data)) + } + _ => Err(de::Error::unknown_field(&key, &["Labs", "Bip39"])), + }, + None => Err(de::Error::missing_field("type")), + } + } + } + + deserializer.deserialize_any(MnemonicTypeVisitor) + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +pub struct Bip39MnemonicData { + pub account_id: u16, + pub path: Bip39Path, + pub entropy: Bip39Entropy, +} + +impl Bip39MnemonicData { + pub fn labs_old(account_id: u16) -> Self { + Self { + account_id, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + } + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum Bip39Path { + Ever, + Ton, +} + +impl Bip39Path { + pub fn derive(&self, seed_bytes: &[u8], account_id: u16) -> anyhow::Result<[u8; 32]> { + let derived = match self { + Self::Ever => { + let derived = ExtendedPrivKey::derive( + seed_bytes, + format!("m/44'/396'/0'/0/{account_id}").as_str(), + ) + .map_err(|_| anyhow::anyhow!("Invalid derivation path"))?; + + derived.secret() + } + Self::Ton => derive_ed25519_private_key(seed_bytes, &[44, 607, account_id as u32]), + }; + + Ok(derived) + } +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub enum Bip39Entropy { + Bits128, + Bits256, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GeneratedKey { + pub words: Vec<&'static str>, + pub account_type: MnemonicType, +} + +pub fn derive_from_phrase( + phrase: &str, + mnemonic_type: MnemonicType, +) -> Result { + match mnemonic_type { + MnemonicType::Legacy => legacy::derive_from_phrase(phrase), + MnemonicType::Bip39(data) => labs::derive_from_phrase(phrase, data), + } +} + +/// Generates mnemonic and keypair. +pub fn generate_key(account_type: MnemonicType) -> GeneratedKey { + use bip39::util::{Bits11, IterExt}; + + let rng = &mut rand::rng(); + + pub fn generate_words(entropy: &[u8]) -> Vec<&'static str> { + let wordlist = LANGUAGE.wordlist(); + + let checksum_byte = sha2::Sha256::digest(entropy)[0]; + + entropy + .iter() + .chain(Some(&checksum_byte)) + .bits() + .map(|bits: Bits11| wordlist.get_word(bits)) + .collect() + } + + let entropy_size = match account_type { + MnemonicType::Legacy => 32, + MnemonicType::Bip39(data) => match data.entropy { + Bip39Entropy::Bits128 => 16, + Bip39Entropy::Bits256 => 32, + }, + }; + + let entropy = (0..entropy_size) + .map(|_| rng.random::()) + .collect::>(); + + GeneratedKey { + account_type, + words: generate_words(&entropy), + } +} + +#[cfg(test)] +mod tests { + use serde::Deserialize; + + use crate::utils::mnemonic::{ + Bip39Entropy, Bip39MnemonicData, Bip39Path, MnemonicType, LANGUAGE, + }; + + #[test] + fn mnemonic_type_deserialize() { + #[derive(Deserialize)] + struct Test { + mnemonic_type: MnemonicType, + } + + let legacy = r#"{"mnemonic_type":"Legacy"}"#; + let legacy_mnemonic: Test = serde_json::from_str(legacy).unwrap(); + assert_eq!(legacy_mnemonic.mnemonic_type, MnemonicType::Legacy); + + let labs = r#"{"mnemonic_type":{"Labs":2}}"#; + let labs_mnemonic: Test = serde_json::from_str(labs).unwrap(); + assert_eq!( + labs_mnemonic.mnemonic_type, + MnemonicType::Bip39(Bip39MnemonicData::labs_old(2)) + ); + + let bip39 = + r#"{"mnemonic_type":{"Bip39":{"account_id":0,"path":"ever","entropy":"bits128"}}}"#; + let bip39_mnemonic: Test = serde_json::from_str(bip39).unwrap(); + assert_eq!( + bip39_mnemonic.mnemonic_type, + MnemonicType::Bip39(Bip39MnemonicData { + account_id: 0, + path: Bip39Path::Ever, + entropy: Bip39Entropy::Bits128, + }) + ); + } + + const TEST_MNEMONIC: &str = "moral ill excuse avoid only father maid cash coin fat replace roast damage egg garment slot begin wreck elephant sea left marble afford drip"; + + #[test] + fn ton_derive() { + let mnemonic = bip39::Mnemonic::from_phrase(TEST_MNEMONIC, LANGUAGE).unwrap(); + let hd = bip39::Seed::new(&mnemonic, ""); + let seed_bytes = hd.as_bytes(); + + let derived = Bip39Path::Ton.derive(seed_bytes, 0).unwrap(); + + let secret = ed25519_dalek::SigningKey::from_bytes(&derived); + let public = secret.verifying_key(); + + assert_eq!( + hex::encode(public.to_bytes()), + "09304d7bd820598794b1be73c95d7bcdd749ef583c8a6a243487dcf4156212a5" + ) + } +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 24f5898..82be48c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,11 +1,13 @@ use std::hash::BuildHasherDefault; -use anyhow::Result; use rustc_hash::FxHasher; -use ton_block::Deserializable; -use tycho_types::boc::Boc; -use tycho_types::cell::CellBuilder; -use tycho_types::models::Transaction; +use tycho_types::abi::AbiType; +use tycho_types::abi::AbiValue; +use tycho_types::abi::FromAbi; +use tycho_types::abi::IntoAbi; +use tycho_types::abi::NamedAbiType; +use tycho_types::abi::NamedAbiValue; +use tycho_types::abi::WithAbiType; pub use self::encoding::*; pub use self::existing_contract::*; @@ -16,17 +18,207 @@ pub use self::tx_context::*; mod encoding; mod existing_contract; +pub mod mnemonic; +pub mod multisig; mod pending_messages_queue; mod shard_utils; mod token_wallet; +pub mod token_wallets; +pub mod ton_wallet; mod tx_context; +mod wallets; pub type FxDashMap = dashmap::DashMap>; pub type FxDashSet = dashmap::DashSet>; -pub fn conver_to_old_transaction(transaction: &Transaction) -> Result { - let cell = CellBuilder::build_from(transaction)?; - let bytes = Boc::encode(cell); - let cell = ton_types::deserialize_tree_of_cells(&mut &*bytes)?; - ton_block::Transaction::construct_from_cell(cell) +macro_rules! declare_function { + ( + $(abi: $abi:ident,)? + $(function_id: $id:literal,)? + $(header: [$($header:ident),+],)? + name: $name:literal, + inputs: $inputs:expr, + outputs: $outputs:expr$(,)? + ) => { + static ONCE: std::sync::OnceLock = std::sync::OnceLock::new(); + ONCE.get_or_init(|| { + let builder = tycho_types::abi::Function::builder($crate::utils::declare_function!(@abi_version $($abi)?), ($name).to_string()) + .with_headers($crate::utils::declare_function!(@header $($($header),+)?)) + .with_inputs($inputs as Vec) + .with_outputs($outputs as Vec); + + $crate::utils::declare_function!(@function_id builder $($id)?); + + builder + .build() + }) + }; + + (@function_id $builder:ident $id:literal) => { let $builder = $builder.with_id($id); }; + (@function_id $builder:ident ) => {}; + + (@abi_version) => { tycho_types::abi::AbiVersion::V2_2 }; + (@abi_version v2_0) => { tycho_types::abi::AbiVersion::V2_0 }; + (@abi_version v2_1) => { tycho_types::abi::AbiVersion::V2_1 }; + (@abi_version v2_2) => { tycho_types::abi::AbiVersion::V2_2 }; + (@abi_version v2_3) => { tycho_types::abi::AbiVersion::V2_3 }; + (@abi_version v2_7) => { tycho_types::abi::AbiVersion::V2_7 }; + + (@header) => { Vec::new() }; + (@header $($header:ident),+) => { + vec![$($crate::utils::declare_function!(@header_item $header)),+] + }; + (@header_item pubkey) => { + tycho_types::abi::AbiHeaderType::PublicKey + }; + (@header_item time) => { + tycho_types::abi::AbiHeaderType::Time + }; + (@header_item expire) => { + tycho_types::abi::AbiHeaderType::Expire + }; +} + +pub(crate) use declare_function; + +pub mod serde_string { + use std::str::FromStr; + + use serde::de::Error; + use serde::{Deserialize, Serialize}; + + pub fn serialize(data: &dyn std::fmt::Display, serializer: S) -> Result + where + S: serde::Serializer, + { + data.to_string().serialize(serializer) + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + T: FromStr, + T::Err: std::fmt::Display, + { + String::deserialize(deserializer) + .and_then(|data| T::from_str(&data).map_err(D::Error::custom)) + } +} + +pub mod serde_address { + use serde::de::Error; + use serde::Deserialize; + use tycho_types::models::{StdAddr, StdAddrFormat}; + + pub fn serialize(data: &StdAddr, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&data.to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data = String::deserialize(deserializer)?; + + let (address, _) = StdAddr::from_str_ext(&data, StdAddrFormat::any()) + .map_err(|_| D::Error::custom("Invalid address"))?; + Ok(address) + } +} + +pub mod serde_optional_string { + use std::fmt; + use std::str::FromStr; + + use serde::de::Error; + use serde::{Deserialize, Serialize}; + + pub fn serialize(data: &Option, serializer: S) -> Result + where + S: serde::Serializer, + T: fmt::Display, + { + data.as_ref().map(ToString::to_string).serialize(serializer) + } + + pub fn deserialize<'de, D, T>(deserializer: D) -> Result, D::Error> + where + D: serde::Deserializer<'de>, + T: FromStr, + T::Err: fmt::Display, + { + Option::::deserialize(deserializer).and_then(|data| { + data.map(|data| T::from_str(&data).map_err(Error::custom)) + .transpose() + }) + } +} + +pub mod serde_cell { + use serde::de::Error; + use serde::Deserialize; + use tycho_types::boc::Boc; + use tycho_types::cell::Cell; + + pub fn serialize(data: &Cell, serializer: S) -> Result + where + S: serde::ser::Serializer, + { + let bytes = Boc::encode_base64(data); + serializer.serialize_str(&bytes) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let data = String::deserialize(deserializer)?; + let cell = Boc::decode_base64(&data).map_err(|_| D::Error::custom("Invalid cell"))?; + Ok(cell) + } +} + +pub struct InputMessage(pub Vec); + +pub struct ContractCall { + pub inputs: Vec, + pub outputs: Vec, +} + +pub trait IntoAbiPlain: IntoAbi { + fn into_abi_plain(self) -> Vec + where + Self: Sized, + { + let tuple = self.into_abi(); + match tuple { + AbiValue::Tuple(tuple) => tuple, + _ => vec![], + } + } + fn as_abi_plain(&self) -> Vec { + let tuple = self.as_abi(); + match tuple { + AbiValue::Tuple(tuple) => tuple, + _ => vec![], + } + } +} + +pub trait FromAbiPlain: FromAbi { + fn from_abi_plain(value: Vec) -> anyhow::Result { + Self::from_abi(AbiValue::Tuple(value)) + } +} + +pub trait WithAbiTypePlain: WithAbiType { + fn abi_type_plain() -> Vec { + match Self::abi_type() { + AbiType::Tuple(tuple) => tuple.iter().cloned().collect(), + _ => vec![], + } + } } diff --git a/src/utils/multisig/mod.rs b/src/utils/multisig/mod.rs new file mode 100644 index 0000000..3efaa2d --- /dev/null +++ b/src/utils/multisig/mod.rs @@ -0,0 +1,2 @@ +pub mod models; +pub mod parsing; diff --git a/src/utils/multisig/models.rs b/src/utils/multisig/models.rs new file mode 100644 index 0000000..ef39ab1 --- /dev/null +++ b/src/utils/multisig/models.rs @@ -0,0 +1,288 @@ +use anyhow::{anyhow, Result}; +use num_traits::ToPrimitive; +use serde::{Deserialize, Serialize}; +use tycho_types::{ + abi::{AbiValue, FromAbi, IntoAbi, NamedAbiValue}, + cell::{Cell, HashBytes}, + models::StdAddr, +}; + +use crate::utils::{ + serde_address, serde_cell, serde_string, ton_wallet::multisig::UnpackerError, + wallets::multisig2, ContractCall, FromAbiPlain, InputMessage, IntoAbiPlain, +}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type", content = "data")] +pub enum MultisigTransaction { + Send(MultisigSendTransaction), + Submit(MultisigSubmitTransaction), + Confirm(MultisigConfirmTransaction), + SubmitUpdate(MultisigSubmitUpdate), + ConfirmUpdate(MultisigConfirmUpdate), + ExecuteUpdate(MultisigExecuteUpdate), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, IntoAbi, FromAbi)] +#[serde(rename_all = "camelCase")] +pub struct MultisigConfirmTransaction { + pub custodian: HashBytes, + + #[serde(with = "serde_string")] + pub transaction_id: u64, +} + +impl IntoAbiPlain for MultisigConfirmTransaction { + fn into_abi_plain(self) -> Vec { + vec![AbiValue::Uint(64, self.transaction_id.into()).named("transactionId")] + } + fn as_abi_plain(&self) -> Vec { + vec![AbiValue::Uint(64, self.transaction_id.into()).named("transactionId")] + } +} +impl FromAbiPlain for MultisigConfirmTransaction { + fn from_abi_plain(value: Vec) -> anyhow::Result { + let AbiValue::Uint(_, transaction_id) = &value[0].value else { + return Err(anyhow!("Invalid transactionId")); + }; + + Ok(MultisigConfirmTransaction { + custodian: Default::default(), + transaction_id: transaction_id + .to_u64() + .ok_or(anyhow!("Invalid transaction id"))?, + }) + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, IntoAbi, FromAbi)] +#[serde(rename_all = "camelCase")] +pub struct MultisigSubmitTransaction { + #[serde(with = "serde_string")] + pub custodian: HashBytes, + + #[serde(with = "serde_address")] + pub dest: StdAddr, + + #[serde(with = "serde_string")] + pub value: u128, + + pub bounce: bool, + + pub all_balance: bool, + + #[serde(with = "serde_cell")] + pub payload: Cell, + + #[serde(with = "serde_string")] + pub trans_id: u64, +} +impl IntoAbiPlain for MultisigSubmitTransaction {} +impl FromAbiPlain for MultisigSubmitTransaction {} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, IntoAbi, FromAbi)] +pub struct MultisigSendTransaction { + #[serde(with = "serde_address")] + pub dest: StdAddr, + + #[serde(with = "serde_string")] + pub value: u128, + + pub bounce: bool, + + pub flags: u8, + + #[serde(with = "serde_cell")] + pub payload: Cell, +} +impl IntoAbiPlain for MultisigSendTransaction {} +impl FromAbiPlain for MultisigSendTransaction {} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigSubmitUpdate { + pub custodian: HashBytes, + pub new_code_hash: Option, + pub new_owners: bool, + pub new_req_confirms: bool, + pub new_lifetime: bool, + #[serde(with = "serde_string")] + pub update_id: u64, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigConfirmUpdate { + pub custodian: HashBytes, + #[serde(with = "serde_string")] + pub update_id: u64, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigExecuteUpdate { + pub custodian: HashBytes, + #[serde(with = "serde_string")] + pub update_id: u64, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigPendingTransaction { + #[serde(with = "serde_string")] + pub id: u64, + + pub confirmations: Vec, + + pub signs_required: u8, + pub signs_received: u8, + + #[serde(with = "serde_string")] + pub creator: HashBytes, + + pub index: u8, + + #[serde(with = "serde_address")] + pub dest: StdAddr, + + #[serde(with = "serde_string")] + pub value: u128, + + pub send_flags: u16, + + #[serde(with = "serde_cell")] + pub payload: Cell, + + pub bounce: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct MultisigPendingUpdate { + #[serde(with = "serde_string")] + pub id: u64, + + pub confirmations: Vec, + + pub signs_received: u8, + + #[serde(with = "serde_string")] + pub creator: HashBytes, + + pub index: u8, + + pub new_code_hash: Option, + pub new_custodians: Option>, + pub new_req_confirms: Option, + pub new_lifetime: Option, +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, InputMessage)) -> Result { + let output = MultisigConfirmTransaction::from_abi_plain(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Ok(Self { + custodian, + transaction_id: output.transaction_id, + }) + } +} + +#[derive(Clone, Debug, FromAbi)] +struct MultisigSubmitTransactionInput { + dest: StdAddr, + value: u128, + bounce: bool, + all_balance: bool, + payload: Cell, +} + +impl FromAbiPlain for MultisigSubmitTransactionInput {} + +#[derive(Clone, Debug, FromAbi)] +struct MultisigSubmitTransactionOutput { + trans_id: u64, +} + +impl FromAbiPlain for MultisigSubmitTransactionOutput {} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitTransaction { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + let input = MultisigSubmitTransactionInput::from_abi_plain(value.inputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + let output = MultisigSubmitTransactionOutput::from_abi_plain(value.outputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + + Ok(Self { + custodian, + dest: input.dest, + value: input.value, + bounce: input.bounce, + all_balance: input.all_balance, + payload: input.payload, + trans_id: output.trans_id, + }) + } +} + +impl TryFrom for MultisigSendTransaction { + type Error = UnpackerError; + + fn try_from(value: InputMessage) -> Result { + let input = MultisigSendTransaction::from_abi_plain(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + + Ok(Self { + dest: input.dest, + value: input.value, + bounce: input.bounce, + flags: input.flags, + payload: input.payload, + }) + } +} + +impl TryFrom<(HashBytes, ContractCall)> for MultisigSubmitUpdate { + type Error = UnpackerError; + + fn try_from((custodian, value): (HashBytes, ContractCall)) -> Result { + let input = multisig2::SubmitUpdateParams::from_abi_plain(value.inputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + let output = multisig2::SubmitUpdateOutput::from_abi_plain(value.outputs) + .map_err(|_| UnpackerError::InvalidAbi)?; + + Ok(Self { + custodian, + new_code_hash: input.code_hash, + new_owners: input.owners.is_some(), + new_req_confirms: input.req_confirms.is_some(), + new_lifetime: input.lifetime.is_some(), + update_id: output.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigConfirmUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + let input = multisig2::ConfirmUpdateParams::from_abi_plain(input.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} + +impl TryFrom<(HashBytes, InputMessage)> for MultisigExecuteUpdate { + type Error = UnpackerError; + + fn try_from((custodian, input): (HashBytes, InputMessage)) -> Result { + let input = multisig2::ExecuteUpdateParams::from_abi_plain(input.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Ok(Self { + custodian, + update_id: input.update_id, + }) + } +} diff --git a/src/utils/multisig/parsing.rs b/src/utils/multisig/parsing.rs new file mode 100644 index 0000000..5b12c29 --- /dev/null +++ b/src/utils/multisig/parsing.rs @@ -0,0 +1,263 @@ +use std::convert::TryFrom; + +use tycho_types::{ + abi::Function, + cell::{CellSlice, HashBytes, Load}, + models::{MsgInfo, OwnedMessage, Transaction}, +}; + +use crate::utils::{ + multisig::models::{ + MultisigConfirmTransaction, MultisigConfirmUpdate, MultisigExecuteUpdate, + MultisigSendTransaction, MultisigSubmitTransaction, MultisigSubmitUpdate, + MultisigTransaction, + }, + ton_wallet::MultisigType, + wallets::{ + multisig::{self}, + multisig2, + }, + ContractCall, InputMessage, +}; + +pub fn parse_multisig_transaction( + multisig_type: MultisigType, + tx: &Transaction, +) -> Option { + let in_msg_cell = tx.in_msg.as_ref()?; + + let Ok(mut slice) = in_msg_cell.as_slice() else { + return None; + }; + let Ok(in_msg) = OwnedMessage::load_from(&mut slice) else { + return None; + }; + + if !matches!(in_msg.info, MsgInfo::ExtIn(_)) { + return None; + } + parse_multisig_transaction_impl(multisig_type, in_msg, tx) +} + +fn parse_multisig_transaction_impl( + multisig_type: MultisigType, + in_msg: OwnedMessage, + tx: &Transaction, +) -> Option { + const PUBKEY_OFFSET: usize = 1 + ed25519_dalek::SIGNATURE_LENGTH * 8 + 1; + const PUBKEY_LENGTH: usize = 256; + const TIME_LENGTH: usize = 64; + const EXPIRE_LENGTH: usize = 32; + + let body = in_msg.body; + let Ok(mut body_slice) = body.1.as_slice() else { + return None; + }; + + // Shift body by Maybe(signature), Maybe(pubkey), time and expire + body_slice + .skip_first( + (PUBKEY_OFFSET + PUBKEY_LENGTH + TIME_LENGTH + EXPIRE_LENGTH) as u16, + 0, + ) + .ok()?; + + let Ok(function_id) = body_slice.get_u32(0) else { + return None; + }; + + let parse_tx_input = + |function: &Function, mut slice: CellSlice<'_>| -> Option<(HashBytes, InputMessage)> { + let inputs = function.decode_internal_input(slice).ok()?; + slice.skip_first(PUBKEY_OFFSET as u16, 0).ok()?; + let custodian = slice.load_u256().ok()?; + Some((custodian, InputMessage(inputs))) + }; + + let parse_tx_full = + |function: &Function, body_slice: CellSlice| -> Option<(HashBytes, ContractCall)> { + let (custodian, InputMessage(inputs)) = parse_tx_input(function, body_slice)?; + let mut output = None; + for out_msg in tx.iter_out_msgs() { + let Ok(out_msg) = out_msg else { + continue; + }; + + if !matches!(out_msg.info, MsgInfo::ExtOut(_)) { + continue; + } + + let body = out_msg.body; + + let Ok(function_id) = body.get_u32(0) else { + continue; + }; + + if function.output_id == function_id { + let Ok(tokens) = function.decode_output(body) else { + continue; + }; + + output = Some(tokens); + break; + } + } + + Some(( + custodian, + ContractCall { + inputs, + outputs: output.unwrap_or_default(), + }, + )) + }; + + let functions = MultisigFunctions::instance(multisig_type); + + if function_id == functions.send_transaction.input_id { + let inputs = functions + .send_transaction + .decode_external_input(body_slice) + .ok()?; + MultisigSendTransaction::try_from(InputMessage(inputs)) + .map(MultisigTransaction::Send) + .ok() + } else if function_id == functions.submit_transaction.input_id { + let (custodian, value) = parse_tx_full(functions.submit_transaction, body_slice)?; + MultisigSubmitTransaction::try_from((custodian, value)) + .map(MultisigTransaction::Submit) + .ok() + } else if function_id == functions.confirm_transaction.input_id { + let (custodian, value) = parse_tx_input(functions.confirm_transaction, body_slice)?; + MultisigConfirmTransaction::try_from((custodian, value)) + .map(MultisigTransaction::Confirm) + .ok() + } else if let Some(functions) = &functions.update_functions { + if function_id == functions.submit_update.input_id { + let (custodian, value) = parse_tx_full(functions.submit_update, body_slice)?; + MultisigSubmitUpdate::try_from((custodian, value)) + .map(MultisigTransaction::SubmitUpdate) + .ok() + } else if function_id == functions.confirm_update.input_id { + let (custodian, value) = parse_tx_input(functions.confirm_update, body_slice)?; + MultisigConfirmUpdate::try_from((custodian, value)) + .map(MultisigTransaction::ConfirmUpdate) + .ok() + } else if function_id == functions.execute_update.input_id { + let (custodian, value) = parse_tx_input(functions.execute_update, body_slice)?; + MultisigExecuteUpdate::try_from((custodian, value)) + .map(MultisigTransaction::ExecuteUpdate) + .ok() + } else { + None + } + } else { + None + } +} + +struct MultisigFunctions { + send_transaction: &'static Function, + submit_transaction: &'static Function, + confirm_transaction: &'static Function, + update_functions: Option, +} + +struct UpdateFunctions { + submit_update: &'static Function, + confirm_update: &'static Function, + execute_update: &'static Function, +} + +impl MultisigFunctions { + fn instance(multisig_type: MultisigType) -> &'static Self { + static OLD_FUNCTIONS: std::sync::OnceLock = std::sync::OnceLock::new(); + static NEW_FUNCTIONS: std::sync::OnceLock = std::sync::OnceLock::new(); + + match multisig_type { + ty if ty.is_multisig2() => NEW_FUNCTIONS.get_or_init(|| MultisigFunctions { + send_transaction: multisig2::send_transaction(), + submit_transaction: multisig2::submit_transaction(), + confirm_transaction: multisig2::confirm_transaction(), + update_functions: Some(UpdateFunctions { + submit_update: multisig2::submit_update(), + confirm_update: multisig2::confirm_update(), + execute_update: multisig2::execute_update(), + }), + }), + _ => OLD_FUNCTIONS.get_or_init(|| MultisigFunctions { + send_transaction: multisig::send_transaction(), + submit_transaction: multisig::submit_transaction(), + confirm_transaction: multisig::confirm_transaction(), + update_functions: None, + }), + } + } +} + +#[cfg(test)] +mod tests { + + // use tycho_types::{ + // boc::Boc, + // cell::Load, + // models::{OrdinaryTxInfo, TxInfo}, + // }; + + // use super::*; + + // fn parse_transaction(data: &str) -> (Transaction, OrdinaryTxInfo) { + // let binding = Boc::decode_base64(data).unwrap(); + // let mut cell = binding.as_slice().unwrap(); + // let transaction = Transaction::load_from(&mut cell).unwrap(); + // let info = match transaction.load_info().unwrap() { + // TxInfo::Ordinary(info) => info, + // _ => panic!(), + // }; + // (transaction, info) + // } + + //#[test] + //fn test_parse_multisig_submit() { + // let tx = Transaction::construct_from_base64("te6ccgECDAEAAkMAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAEv38uN8H+CfBrFklcU0i9Vs4RZzxi5vtTa9PqJ/LpPctz/rat2wAABIjJ0UsBX2sytAADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAOylU78GhKKYOUuj1Rh3dLpOOzgJUEyoySchhaM60lDREBQDowAnUfXAxOIAAAAAAAAAABtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnJ5QDnTzA46E1KOsPz7QLrshaiw53aaaTNY7TZfFM9uf9wCstMqmz8MmfSmYLSpRuMah9ruqiOVsRPjzhTEdu9aAgHgCAYBAd8HAHXn+7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u4AAAJfv5cb4S+1mVoSY7BZq+1mVo/lxvgwAFFif7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7gwJAeGUlZeW3g4p7fOroeyZUZdj1hWrKWusR/Na6V9uRhKJvV3dgWDQ1/YR5hQfYLaM861DgLJMku/LPDKMt43TyJUH+ToLdTA3yCwRnsc9IMg9JIXlsbI92/1mZ+RrZF1GGY1AAABdLq+AHhfazLsEx2CzYAoBY4AVKmRhQN1a9YbnwdGmdH0KtPv2SINcG4FpEDjh70ON2qAAAAAAAAAAAAAdjv+NHoAUCwAA").unwrap(); + // + // let custodian = + // HashBytes::from_str("e4e82dd4c0df20b0467b1cf48320f4921796c6c8f76ff5999f91ad9175186635") + // .unwrap(); + // + // assert!(matches!( + // parse_transaction_additional_info( + // &tx, + // WalletType::Multisig(MultisigType::SafeMultisigWallet) + // ) + // .unwrap(), + // TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + // recipient: Some(_), + // known_payload: None, + // method: WalletInteractionMethod::Multisig(data) + // }) if matches!(&*data, MultisigTransaction::Submit(submit) if submit.custodian == custodian) + // )); + //} + // + //#[test] + //fn test_parse_multisig_confirm() { + // let tx = Transaction::construct_from_base64("te6ccgECCgEAAjAAA693d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3d3AAAJcbrc/8GSsRcwsaEKUmFwdbT9tmaf3vKqKpeWIR9/9GyMA8r2+gAACXGutDTBYBvSYwADQIBQQBAgcMBgRAAwIAYcAAAAAAAAIAAAAAAAI1K3sqU+I63UTJ+xkdHcyrkM2hxcBJu//z7hF+/hEtukBQFcwAnUYtYxOIAAAAAAAAAABSwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAgnITMJnhiVklA89yLWhQU+4BB1tJ3iPLRRZoWlPVKSkbvYENWnQphG03/JbEJJWwJbdhZCl+oH7UI7ARqCUcU6H/AgHgCAYBAd8HAK9J/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7vACa4ZyAEEjOHCY7aEkcDRTMruTfdNxrg9GyWxKU18Pes2WvMQekAAAAAABLjdbn/hMA3pMZAAUWJ/u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7u7uDAkA8c+cpxQ8FYd2C/XWiibmIX4wPfvHIultapCNOhW5dJ5hl2YD+PHO24RUXdbY669yR8BUfGNuxVTwVkV1K0HA7QByTARuQhGj9eozhRteIImtsExhdcFckfL9FqBq5uNuaoAAAF3bK3Ps2Ab0p4ap0DtYBvF9mf0BgGA=").unwrap(); + // + // let custodian = + // HashBytes::from_str("c93011b908468fd7a8ce146d788226b6c13185d7057247cbf45a81ab9b8db9aa") + // .unwrap(); + // + // assert!(matches!( + // parse_transaction_additional_info( + // &tx, + // WalletType::Multisig(MultisigType::SafeMultisigWallet) + // ) + // .unwrap(), + // TransactionAdditionalInfo::WalletInteraction(WalletInteractionInfo { + // recipient: None, + // known_payload: None, + // method: WalletInteractionMethod::Multisig(data) + // }) if matches!(&*data, MultisigTransaction::Confirm(confirm) if confirm.custodian == custodian) + // )) + //} +} diff --git a/src/utils/shard_utils.rs b/src/utils/shard_utils.rs index 10741e9..76eced0 100644 --- a/src/utils/shard_utils.rs +++ b/src/utils/shard_utils.rs @@ -1,16 +1,7 @@ -use std::collections::HashMap; - use anyhow::Result; -use nekoton::transport::models::ExistingContract; use tycho_types::{cell::HashBytes, models::ShardIdent}; -pub type ShardsMap = HashMap; - -#[derive(Debug, Clone)] -pub struct LatestShardBlocks { - pub current_utime: u32, - pub block_ids: ShardsMap, -} +use crate::models::ExistingContract; /// Helper trait to reduce boilerplate for getting accounts from shards state pub trait ShardAccountsMapExt { @@ -85,7 +76,7 @@ mod tests { .unwrap(), ); - let mut shards = vec![ShardIdent::new(0, ton_block::SHARD_FULL).unwrap()]; + let mut shards = vec![ShardIdent::new(0, ShardIdent::PREFIX_FULL).unwrap()]; for _ in 0..4 { let mut new_shards = vec![]; for shard in &shards { diff --git a/src/utils/token_wallet.rs b/src/utils/token_wallet.rs index 8df2a1d..7be562a 100644 --- a/src/utils/token_wallet.rs +++ b/src/utils/token_wallet.rs @@ -1,77 +1,63 @@ use anyhow::Result; use bigdecimal::BigDecimal; -use nekoton::core::models::{ - RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, TransferRecipient, -}; -use nekoton::core::InternalMessage; -use nekoton::transport::models::ExistingContract; -use nekoton_abi::{BigUint128, BigUint256, ExecutionContext, MessageBuilder}; -use nekoton_contracts::tip3_any::{RootTokenContractState, TokenWalletContractState}; -use nekoton_contracts::{old_tip3, tip3_1}; -use nekoton_utils::SimpleClock; +use nekoton_core::contracts::blockchain_context::BlockchainAccount; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use num_bigint::BigUint; -use ton_block::MsgAddressInt; -use ton_types::{SliceData, UInt256}; +use num_traits::ToPrimitive; +use tycho_types::abi::AbiValue; +use tycho_types::cell::{Cell, HashBytes}; +use tycho_types::models::{AccountState, StdAddr}; + +use crate::models::ExistingContract; +use crate::utils::token_wallets::models::RootTokenContractState; +use crate::utils::token_wallets::models::TokenWalletContractState; +use crate::utils::token_wallets::models::{ + RootTokenContractDetails, TokenWalletDetails, TokenWalletVersion, +}; -const INITIAL_BALANCE: u64 = 100_000_000; // 0.1 TON +const INITIAL_BALANCE: u128 = 100_000_000; // 0.1 + +#[derive(Clone, Debug)] +pub struct InternalMessage { + pub source: Option, + pub destination: StdAddr, + pub amount: u128, + pub bounce: bool, + pub body: Cell, +} pub fn prepare_token_transfer( - owner: MsgAddressInt, - token_wallet: MsgAddressInt, + owner: StdAddr, + token_wallet: StdAddr, version: TokenWalletVersion, - destination: TransferRecipient, + destination: StdAddr, tokens: BigUint, - send_gas_to: MsgAddressInt, + send_gas_to: StdAddr, notify_receiver: bool, attached_amount: u128, - payload: ton_types::Cell, + payload: Cell, ) -> Result { - let (function, input) = match version { + let (function, tokens) = match version { TokenWalletVersion::OldTip3v4 => { - use old_tip3::token_wallet_contract; - match destination { - TransferRecipient::TokenWallet(token_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer()) - .arg(token_wallet) // to - .arg(BigUint128(tokens)) // tokens - } - TransferRecipient::OwnerWallet(owner_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer_to_recipient()) - .arg(BigUint256(Default::default())) // recipient_public_key - .arg(owner_wallet) // recipient_address - .arg(BigUint128(tokens)) // tokens - .arg(BigUint128(INITIAL_BALANCE.into())) // deploy_grams - } - } - .arg(BigUint128(Default::default())) // grams / transfer_grams - .arg(send_gas_to) // send_gas_to - .arg(notify_receiver) // notify_receiver - .arg(payload) // payload - .build() + return Err(TokenWalletError::NotSupported.into()); } TokenWalletVersion::Tip3 => { - use tip3_1::token_wallet_contract; - match destination { - TransferRecipient::TokenWallet(token_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer_to_wallet()) - .arg(BigUint128(tokens)) // amount - .arg(token_wallet) // recipient token wallet - } - TransferRecipient::OwnerWallet(owner_wallet) => { - MessageBuilder::new(token_wallet_contract::transfer()) - .arg(BigUint128(tokens)) // amount - .arg(owner_wallet) // recipient - .arg(BigUint128(INITIAL_BALANCE.into())) // deployWalletValue - } - } - .arg(send_gas_to) // remainingGasTo - .arg(notify_receiver) // notify - .arg(payload) // payload - .build() + use crate::utils::token_wallets; + let function = token_wallets::transfer(); + let tokens = [ + AbiValue::uint(128, tokens.to_u128().unwrap()).named("amount"), + AbiValue::address(destination).named("recipient"), + AbiValue::uint(128, INITIAL_BALANCE).named("deployWalletValue"), + AbiValue::address(send_gas_to).named("remainingGasTo"), + AbiValue::Bool(notify_receiver).named("notify"), + AbiValue::Cell(payload).named("payload"), + ] + .to_vec(); + (function, tokens) } }; - let body = SliceData::load_builder(function.encode_internal_input(&input)?)?; + let body = function.encode_internal_input(&tokens)?.build()?; Ok(InternalMessage { source: Some(owner), @@ -83,38 +69,35 @@ pub fn prepare_token_transfer( } pub fn prepare_token_burn( - owner: MsgAddressInt, - token_wallet: MsgAddressInt, + owner: StdAddr, + token_wallet: StdAddr, version: TokenWalletVersion, tokens: BigUint, - send_gas_to: MsgAddressInt, - callback_to: MsgAddressInt, + send_gas_to: StdAddr, + callback_to: StdAddr, attached_amount: u128, - payload: ton_types::Cell, + payload: Cell, ) -> Result { - let (function, input) = match version { + let (function, tokens) = match version { TokenWalletVersion::OldTip3v4 => { - use old_tip3::token_wallet_contract; - MessageBuilder::new(token_wallet_contract::burn_by_owner()) - .arg(BigUint128(tokens)) // amount - .arg(0u128) // grams - .arg(send_gas_to) // remainingGasTo - .arg(callback_to) // callback_address - .arg(payload) // payload - .build() + return Err(TokenWalletError::NotSupported.into()); } TokenWalletVersion::Tip3 => { - use tip3_1::token_wallet_contract; - MessageBuilder::new(token_wallet_contract::burnable::burn()) - .arg(BigUint128(tokens)) // amount - .arg(send_gas_to) // remainingGasTo - .arg(callback_to) // callbackTo - .arg(payload) // payload - .build() + use crate::utils::token_wallets; + + let function = token_wallets::burnable::burn(); + let tokens = [ + AbiValue::uint(128, tokens.to_u128().unwrap()).named("amount"), + AbiValue::address(send_gas_to).named("remainingGasTo"), + AbiValue::address(callback_to).named("callbackTo"), + AbiValue::Cell(payload).named("payload"), + ] + .to_vec(); + (function, tokens) } }; - let body = SliceData::load_builder(function.encode_internal_input(&input)?)?; + let body = function.encode_internal_input(&tokens)?.build()?; Ok(InternalMessage { source: Some(owner), @@ -126,33 +109,36 @@ pub fn prepare_token_burn( } pub fn prepare_token_mint( - owner: MsgAddressInt, - root_token: MsgAddressInt, + owner: StdAddr, + root_token: StdAddr, version: TokenWalletVersion, tokens: BigUint, - recipient: MsgAddressInt, + recipient: StdAddr, deploy_wallet_value: BigUint, - send_gas_to: MsgAddressInt, + send_gas_to: StdAddr, notify: bool, attached_amount: u128, - payload: ton_types::Cell, + payload: Cell, ) -> Result { - let (function, input) = match version { - TokenWalletVersion::OldTip3v4 => return Err(TokenWalletError::MintNotSupported.into()), + let (function, tokens) = match version { + TokenWalletVersion::OldTip3v4 => return Err(TokenWalletError::NotSupported.into()), TokenWalletVersion::Tip3 => { - use tip3_1::root_token_contract; - MessageBuilder::new(root_token_contract::mint()) - .arg(BigUint128(tokens)) // amount - .arg(recipient) // recipient - .arg(BigUint128(deploy_wallet_value)) // deployWalletValue - .arg(send_gas_to) // remainingGasTo - .arg(notify) // notify - .arg(payload) // payload - .build() + use crate::utils::token_wallets; + let function = token_wallets::mint(); + let tokens = [ + AbiValue::uint(128, tokens.to_u128().unwrap()).named("amount"), + AbiValue::address(recipient).named("recipient"), + AbiValue::uint(128, deploy_wallet_value).named("deployWalletValue"), + AbiValue::address(send_gas_to).named("remainingGasTo"), + AbiValue::Bool(notify).named("notify"), + AbiValue::Cell(payload).named("payload"), + ] + .to_vec(); + (function, tokens) } }; - let body = SliceData::load_builder(function.encode_internal_input(&input)?)?; + let body = function.encode_internal_input(&tokens)?.build()?; Ok(InternalMessage { source: Some(owner), @@ -164,74 +150,78 @@ pub fn prepare_token_mint( } pub fn get_token_wallet_address( - root_contract: &ExistingContract, - owner: &MsgAddressInt, -) -> Result { - let root_contract_state = RootTokenContractState(ExecutionContext { - clock: &SimpleClock, - account_stuff: &root_contract.account, - libraries: &[], - }); + root_contract: ExistingContract, + context: BlockchainContext, + owner: &StdAddr, +) -> Result { + let mut root_contract_state = + RootTokenContractState(&mut BlockchainAccount::new(context, root_contract.account)); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; root_contract_state.get_wallet_address(version, owner) } pub fn get_token_wallet_account( - root_contract: &ExistingContract, - owner: &MsgAddressInt, -) -> Result { - let root_contract_state = RootTokenContractState(ExecutionContext { - clock: &SimpleClock, - account_stuff: &root_contract.account, - libraries: &[], - }); + root_contract: ExistingContract, + context: BlockchainContext, + owner: &StdAddr, +) -> Result { + let mut root_contract_state = + RootTokenContractState(&mut BlockchainAccount::new(context, root_contract.account)); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; let token_wallet_address = root_contract_state.get_wallet_address(version, owner)?; - let token_wallet_account = - UInt256::from_be_bytes(&token_wallet_address.address().get_bytestring(0)); + let token_wallet_account = token_wallet_address.address; Ok(token_wallet_account) } pub fn get_token_wallet_basic_info( - token_contract: &ExistingContract, + token_contract: ExistingContract, + context: BlockchainContext, ) -> Result<(TokenWalletVersion, BigDecimal)> { - let token_wallet_state = TokenWalletContractState(ExecutionContext { - clock: &SimpleClock, - account_stuff: &token_contract.account, - libraries: &[], - }); + let mut token_wallet_state = + TokenWalletContractState(&mut BlockchainAccount::new(context, token_contract.account)); - let version = token_wallet_state.get_version()?; - let balance = BigDecimal::new(token_wallet_state.get_balance(version)?.into(), 0); + let balance = BigDecimal::new( + token_wallet_state + .get_balance(TokenWalletVersion::Tip3)? + .into(), + 0, + ); - Ok((version, balance)) + Ok((TokenWalletVersion::Tip3, balance)) } pub fn get_token_wallet_details( - token_contract: &ExistingContract, -) -> Result<(TokenWalletDetails, TokenWalletVersion, [u8; 32])> { - let contract_state = TokenWalletContractState(ExecutionContext { - clock: &SimpleClock, - account_stuff: &token_contract.account, - libraries: &[], - }); - - let hash = *contract_state.get_code_hash()?.as_slice(); - let version = contract_state.get_version()?; - let details = contract_state.get_details(version)?; - - Ok((details, version, hash)) + token_contract: ExistingContract, + context: BlockchainContext, +) -> Result<(TokenWalletDetails, TokenWalletVersion, HashBytes)> { + let hash = match &token_contract.account.state { + AccountState::Active(state_init) => { + let code = state_init + .code + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Wallet not deployed"))?; + *code.repr_hash() + } + _ => anyhow::bail!("Wallet not deployed"), + }; + + let mut contract_state = + TokenWalletContractState(&mut BlockchainAccount::new(context, token_contract.account)); + + let details = contract_state.get_details(TokenWalletVersion::Tip3)?; + + Ok((details, TokenWalletVersion::Tip3, hash)) } -pub fn get_root_token_version(root_contract: &ExistingContract) -> Result { - let root_contract_state = RootTokenContractState(ExecutionContext { - clock: &SimpleClock, - account_stuff: &root_contract.account, - libraries: &[], - }); +pub fn get_root_token_version( + root_contract: ExistingContract, + context: BlockchainContext, +) -> Result { + let mut root_contract_state = + RootTokenContractState(&mut BlockchainAccount::new(context, root_contract.account)); let RootTokenContractDetails { version, .. } = root_contract_state.guess_details()?; Ok(version) @@ -239,6 +229,6 @@ pub fn get_root_token_version(root_contract: &ExistingContract) -> Result &'static Function { + declare_function! { + name: "owner", + inputs: vec![AbiType::Uint(32).named("answerId")], + outputs: vec![AbiType::Address.named("owner")], + } +} + +#[derive(Debug, Clone)] +pub struct TransferInputs { + pub amount: u128, + pub recipient: StdAddr, + pub deploy_wallet_value: u128, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl TransferInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("recipient"), + AbiType::Uint(128).named("deployWalletValue"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 6 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let recipient_abi_value = &values[1]; + let deploy_wallet_value_abi_value = &values[2]; + let remaining_gas_to_abi_value = &values[3]; + let notify_abi_value = &values[4]; + let payload_abi_value = &values[5]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*recipient_abi_value.name != "recipient" + && recipient_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid recipient")); + } + let AbiValue::Address(recipient) = &recipient_abi_value.value else { + return Err(anyhow!("Invalid recipient")); + }; + + let AnyAddr::Std(recipient) = *recipient.clone() else { + return Err(anyhow!("Invalid recipient")); + }; + + if &*deploy_wallet_value_abi_value.name != "deployWalletValue" + && deploy_wallet_value_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid deployWalletValue")); + } + let AbiValue::Uint(_, deploy_wallet_value) = &deploy_wallet_value_abi_value.value else { + return Err(anyhow!("Invalid deployWalletValue")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + recipient, + deploy_wallet_value: deploy_wallet_value.to_u128().unwrap(), + remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } +} + +/// Transfer tokens and optionally deploy token wallet for the recipient +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - amount of tokens to transfer +/// * `recipient: address` - tokens recipient address +/// * `deployWalletValue: uint128` - how much EVERs to attach to the token wallet deploy +/// * `remainingGasTo: address` - remaining gas receiver +/// * `notify: bool` - notify receiver on incoming transfer +/// * `payload: cell` - arbitrary payload +/// +pub fn transfer() -> &'static Function { + declare_function! { + name: "transfer", + inputs: TransferInputs::abi_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct TransferToWalletInputs { + pub amount: u128, + pub recipient_token_wallet: StdAddr, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl TransferToWalletInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("recipientTokenWallet"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 5 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let recipient_token_wallet_abi_value = &values[1]; + let remaining_gas_to_abi_value = &values[2]; + let notify_abi_value = &values[3]; + let payload_abi_value = &values[4]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*recipient_token_wallet_abi_value.name != "recipientTokenWallet" + && recipient_token_wallet_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid recipientTokenWallet")); + } + let AbiValue::Address(recipient_token_wallet) = &recipient_token_wallet_abi_value.value + else { + return Err(anyhow!("Invalid recipientTokenWallet")); + }; + + let AnyAddr::Std(recipient_token_wallet) = *recipient_token_wallet.clone() else { + return Err(anyhow!("Invalid recipientTokenWallet")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + recipient_token_wallet, + remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } +} + +/// Transfer tokens using token wallet address +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - amount of tokens to transfer +/// * `recipientTokenWallet: address` - recipient token wallet +/// * `remainingGasTo: address` - remaining gas receiver +/// * `notify: bool` - notify receiver on incoming transfer +/// * `payload: cell` - arbitrary payload +/// +pub fn transfer_to_wallet() -> &'static Function { + declare_function! { + name: "transferToWallet", + inputs: TransferToWalletInputs::abi_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct AcceptTransferInputs { + pub amount: u128, + pub sender: StdAddr, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl AcceptTransferInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("sender"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 5 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let sender_abi_value = &values[1]; + let remaining_gas_to_abi_value = &values[2]; + let notify_abi_value = &values[3]; + let payload_abi_value = &values[4]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*sender_abi_value.name != "sender" + && sender_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid sender")); + } + let AbiValue::Address(sender) = &sender_abi_value.value else { + return Err(anyhow!("Invalid sender")); + }; + + let AnyAddr::Std(sender) = *sender.clone() else { + return Err(anyhow!("Invalid sender")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + sender, + remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } +} + +/// Callback for transfer operation +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - how much tokens to receive +/// * `sender: address` - token wallet owner address +/// * `remainingGasTo` - +/// * `notify` - +/// +/// +/// TODO: fill docs +/// +pub fn accept_transfer() -> &'static Function { + declare_function! { + function_id: 0x67A0B95F, + name: "acceptTransfer", + inputs: AcceptTransferInputs::abi_type(), + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct AcceptMintInputs { + pub amount: u128, + pub remaining_gas_to: StdAddr, + pub notify: bool, + pub payload: Cell, +} + +impl AcceptMintInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ] + } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 4 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let remaining_gas_to_abi_value = &values[1]; + let notify_abi_value = &values[2]; + let payload_abi_value = &values[3]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*notify_abi_value.name != "notify" && notify_abi_value.value.get_type() != AbiType::Bool + { + return Err(anyhow!("Invalid notify")); + } + let AbiValue::Bool(notify) = ¬ify_abi_value.value else { + return Err(anyhow!("Invalid notify")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + remaining_gas_to, + notify: *notify, + payload: payload.clone(), + }) + } +} + +/// Accept minted tokens from root +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - how much tokens to receive +/// * `remainingGasTo: address` - remaining gas receiver +/// * `notify: bool` - notify receiver on incoming mint +/// * `payload: cell` - arbitrary payload +/// +pub fn accept_mint() -> &'static Function { + declare_function! { + function_id: 0x4384F298, + name: "acceptMint", + inputs: AcceptMintInputs::abi_type(), + outputs: Vec::new(), + } +} + +pub mod burnable { + use num_traits::ToPrimitive; + use tycho_types::{ + abi::{AbiValue, NamedAbiValue}, + models::AnyAddr, + }; + + use super::*; + + #[derive(Debug, Clone)] + pub struct BurnInputs { + pub amount: u128, + pub remaining_gas_to: StdAddr, + pub callback_to: StdAddr, + pub payload: Cell, + } + + impl BurnInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("remainingGasTo"), + AbiType::Address.named("callbackTo"), + AbiType::Cell.named("payload"), + ] + } + + pub fn unpack(values: Vec) -> Result { + if values.len() != 4 { + return Err(anyhow!("Invalid number of arguments")); + } + let amount_abi_value = &values[0]; + let remaining_gas_to_abi_value = &values[1]; + let callback_to_abi_value = &values[2]; + let payload_abi_value = &values[3]; + + if &*amount_abi_value.name != "amount" + && amount_abi_value.value.get_type() != AbiType::Uint(128) + { + return Err(anyhow!("Invalid amount")); + } + let AbiValue::Uint(_, amount) = &amount_abi_value.value else { + return Err(anyhow!("Invalid amount")); + }; + + if &*remaining_gas_to_abi_value.name != "remainingGasTo" + && remaining_gas_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid remainingGasTo")); + } + let AbiValue::Address(remaining_gas_to) = &remaining_gas_to_abi_value.value else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + let AnyAddr::Std(remaining_gas_to) = *remaining_gas_to.clone() else { + return Err(anyhow!("Invalid remainingGasTo")); + }; + + if &*callback_to_abi_value.name != "callbackTo" + && callback_to_abi_value.value.get_type() != AbiType::Address + { + return Err(anyhow!("Invalid callbackTo")); + } + let AbiValue::Address(callback_to) = &callback_to_abi_value.value else { + return Err(anyhow!("Invalid callbackTo")); + }; + + let AnyAddr::Std(callback_to) = *callback_to.clone() else { + return Err(anyhow!("Invalid callback_to")); + }; + + if &*payload_abi_value.name != "payload" + && payload_abi_value.value.get_type() != AbiType::Cell + { + return Err(anyhow!("Invalid payload")); + } + let AbiValue::Cell(payload) = &payload_abi_value.value else { + return Err(anyhow!("Invalid payload")); + }; + + Ok(Self { + amount: amount.to_u128().unwrap(), + remaining_gas_to, + callback_to, + payload: payload.clone(), + }) + } + } + + /// TODO: fill docs + pub fn burn() -> &'static Function { + declare_function! { + name: "burn", + inputs: BurnInputs::abi_type(), + outputs: Vec::new(), + } + } +} + +/// Mint tokens to recipient with deploy wallet optional +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - how much tokens to mint +/// * `recipient: address` - minted tokens owner address +/// * `deployWalletValue: uint128` - how much EVERs to send to wallet on deployment +/// * `remainingGasTo: address` - address where to send excess gas +/// * `notify: bool` - whether to notify the recipient +/// * `payload: cell` - arbitrary payload +/// +pub fn mint() -> &'static Function { + declare_function! { + name: "mint", + inputs: vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("recipient"), + AbiType::Uint(128).named("deployWalletValue"), + AbiType::Address.named("remainingGasTo"), + AbiType::Bool.named("notify"), + AbiType::Cell.named("payload"), + ], + outputs: Vec::new(), + } +} + +#[derive(Debug, Clone)] +pub struct AcceptBurnInputs { + pub amount: u128, + pub wallet_owner: StdAddr, + pub remaining_gas_to: StdAddr, + pub callback_to: StdAddr, + pub payload: Cell, +} + +impl AcceptBurnInputs { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(128).named("amount"), + AbiType::Address.named("walletOwner"), + AbiType::Address.named("remainingGasTo"), + AbiType::Address.named("callbackTo"), + AbiType::Cell.named("payload"), + ] + } +} + +/// Called by token wallet on burn +/// +/// # Type +/// Internal method +/// +/// # Inputs +/// * `amount: uint128` - amount of tokens +/// * `walletOwner: address` - token wallet owner +/// * `remainingGasTo: address` - address where to send excess gas +/// * `callbackTo: address` - address where to send callback +/// * `payload: cell` - arbitrary payload +/// +pub fn accept_burn() -> &'static Function { + declare_function! { + function_id: 0x192B51B1, + name: "acceptBurn", + inputs: AcceptBurnInputs::abi_type(), + outputs: Vec::new(), + } +} + +/// Returns the token wallet code. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `walletCode: cell` +/// +pub fn wallet_code() -> &'static Function { + declare_function! { + name: "walletCode", + inputs: vec![ + AbiType::Uint(32).named("answerId"),], + outputs: vec![ + AbiType::Cell.named("walletCode")], + } +} + +/// Returns the total token supply. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `totalSupply: string` +/// +pub fn total_supply() -> &'static Function { + declare_function! { + name: "totalSupply", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + AbiType::Uint(128).named("totalSupply"), + ], + } +} + +/// Returns the name of the token - e.g. `MyToken`. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `name: string` +/// +pub fn name() -> &'static Function { + declare_function! { + name: "name", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + AbiType::String.named("name"), + ], + } +} + +/// Returns the symbol of the token. E.g. "HIX". +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `symbol: string` +/// +pub fn symbol() -> &'static Function { + declare_function! { + name: "symbol", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::String.named("symbol"), + ], + } +} + +/// Returns the number of decimals the token uses - e.g. 8, +/// means to divide the token amount by 100000000 to get its user representation. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `decimals: uint8` +/// +pub fn decimals() -> &'static Function { + declare_function! { + name: "decimals", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::Uint(8).named("decimals"),], + } +} + +/// A contract that is compliant with TIP6 shall implement the following interface +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// * `interfaceID: bytes4` - interface ID +/// +/// # Outputs +/// * `name: string` +/// +pub fn supports_interface() -> &'static Function { + declare_function! { + name: "supportsInterface", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + AbiType::Uint(32).named("interfaceID"), + ], + outputs: vec![ + AbiType::Bool.named("supports"), + ], + } +} + +/// Returns the token root address. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `root: address` +/// +pub fn root() -> &'static Function { + declare_function! { + name: "root", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + AbiType::Address.named("root"), + ], + } +} + +/// Returns the token wallet balance. +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `balance: uint128` +/// +pub fn balance() -> &'static Function { + declare_function! { + name: "balance", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::Uint(128).named("balance"), + ], + } +} + +/// Get root owner +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// +/// # Outputs +/// * `owner: address` - owner wallet address +/// +pub fn root_owner() -> &'static Function { + declare_function! { + name: "rootOwner", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + ], + outputs: vec![ + + AbiType::Address.named("rootOwner"),], + } +} + +/// Derive `TokenWallet` address from owner address +/// +/// # Type +/// Responsible getter method +/// +/// # Inputs +/// * `answerId: uint32` - responsible answer id +/// * `owner: address` - owner address +/// +/// # Outputs +/// * `walletAddress: address` - owner wallet address +/// +pub fn wallet_of() -> &'static Function { + declare_function! { + name: "walletOf", + inputs: vec![ + AbiType::Uint(32).named("answerId"), + AbiType::Address.named("owner"), + ], + outputs: vec![ + AbiType::Address.named("walletAddress"), + ], + } +} diff --git a/src/utils/token_wallets/models.rs b/src/utils/token_wallets/models.rs new file mode 100644 index 0000000..7a4f640 --- /dev/null +++ b/src/utils/token_wallets/models.rs @@ -0,0 +1,558 @@ +use std::fmt::Display; + +use nekoton_core::contracts::blockchain_context::BlockchainAccount; +use nekoton_core::contracts::function_ext::ExecutionOutput; +use num_traits::ToPrimitive; +use serde::{Deserialize, Serialize}; +use tycho_types::{ + abi::{AbiValue, NamedAbiValue}, + cell::Cell, + models::{AnyAddr, StdAddr}, +}; + +use crate::utils::{serde_address, serde_cell, serde_string}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case", tag = "type", content = "data")] +pub enum TokenWalletTransaction { + IncomingTransfer(TokenIncomingTransfer), + OutgoingTransfer(TokenOutgoingTransfer), + SwapBack(TokenSwapBack), + #[serde(with = "serde_string")] + Accept(u128), + #[serde(with = "serde_string")] + TransferBounced(u128), + #[serde(with = "serde_string")] + SwapBackBounced(u128), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenIncomingTransfer { + #[serde(with = "serde_string")] + pub tokens: u128, + /// Not the address of the token wallet, but the address of its owner + #[serde(with = "serde_address")] + pub sender_address: StdAddr, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct TokenOutgoingTransfer { + pub to: TransferRecipient, + #[serde(with = "serde_string")] + pub tokens: u128, + /// token transfer payload + #[serde(with = "serde_cell")] + pub payload: Cell, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase", tag = "type", content = "data")] +pub enum TransferRecipient { + #[serde(with = "serde_address")] + OwnerWallet(StdAddr), + #[serde(with = "serde_address")] + TokenWallet(StdAddr), +} + +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct TokenSwapBack { + #[serde(with = "serde_string")] + pub tokens: u128, + #[serde(with = "serde_address")] + pub callback_address: StdAddr, + /// ETH address or something else + #[serde(with = "serde_cell")] + pub callback_payload: Cell, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum TokenWalletVersion { + /// Third iteration of token wallets, but with fixed bugs + /// [implementation](https://github.com/broxus/ton-eth-bridge-token-contracts/tree/74905260499d79cf7cb0d89a6eb572176fc1fcd5) + OldTip3v4, + /// Latest iteration with completely new standard + /// [implementation](https://github.com/broxus/ton-eth-bridge-token-contracts/tree/9168190f218fd05a64269f5f24295c69c4840d94) + Tip3, +} + +impl Display for TokenWalletVersion { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + TokenWalletVersion::OldTip3v4 => "OldTip3v4", + TokenWalletVersion::Tip3 => "Tip3", + }) + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct RootTokenContractDetails { + /// Token ecosystem version + pub version: TokenWalletVersion, + /// Full currency name + pub name: String, + /// Short currency name + pub symbol: String, + /// Decimals + pub decimals: u8, + /// Root owner contract address. Used as proxy address in Tip3v1 + #[serde(with = "serde_address")] + pub owner_address: StdAddr, + #[serde(with = "serde_string")] + pub total_supply: u128, +} + +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct TokenWalletDetails { + /// Linked root token contract address + #[serde(with = "serde_address")] + pub root_address: StdAddr, + + /// Owner wallet address + #[serde(with = "serde_address")] + pub owner_address: StdAddr, + + #[serde(with = "serde_string")] + pub balance: u128, +} + +#[derive(thiserror::Error, Debug)] +pub enum Tip3Error { + #[error("Unknown version")] + UnknownVersion, + #[error("Wallet not deployed")] + WalletNotDeployed, +} + +pub struct TokenWalletContractState<'a>(pub &'a mut BlockchainAccount); + +impl TokenWalletContractState<'_> { + pub fn get_balance(&mut self, version: TokenWalletVersion) -> anyhow::Result { + match version { + TokenWalletVersion::OldTip3v4 => Err(Tip3Error::UnknownVersion.into()), + TokenWalletVersion::Tip3 => TokenWalletContract(self.0).balance(), + } + } + + pub fn get_details( + &mut self, + version: TokenWalletVersion, + ) -> anyhow::Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + return Err(Tip3Error::UnknownVersion.into()); + } + TokenWalletVersion::Tip3 => { + let mut token_wallet = TokenWalletContract(self.0); + let root_address = token_wallet.root()?; + let balance = token_wallet.balance()?; + + let mut token_wallet = TokenWalletContract(self.0); + let owner_address = token_wallet.owner()?; + + TokenWalletDetails { + root_address, + owner_address, + balance, + } + } + }) + } + + pub fn get_version(&mut self) -> anyhow::Result { + if let Ok(true) = SidContract(self.0).supports_interfaces(&[0x2a4ac43e, 0x4F479FA3]) { + Ok(TokenWalletVersion::Tip3) + } else { + Err(Tip3Error::UnknownVersion.into()) + } + } +} + +pub struct RootTokenContractState<'a>(pub &'a mut BlockchainAccount); + +impl RootTokenContractState<'_> { + /// Calculates token wallet address + pub fn get_wallet_address( + &mut self, + version: TokenWalletVersion, + owner: &StdAddr, + ) -> anyhow::Result { + match version { + TokenWalletVersion::OldTip3v4 => { + anyhow::bail!(Tip3Error::UnknownVersion) + } + TokenWalletVersion::Tip3 => RootTokenContract(self.0).wallet_of(owner.clone()), + } + } + + /// Tries to guess version and retrieve details + pub fn guess_details(&mut self) -> anyhow::Result { + if let Ok(true) = SidContract(self.0).supports_interfaces(&[0x4371D8ED, 0x0b1fd263]) { + return self.get_details(TokenWalletVersion::Tip3); + } + + self.get_details(TokenWalletVersion::Tip3) + } + + /// Retrieve details using specified version + pub fn get_details( + &mut self, + version: TokenWalletVersion, + ) -> anyhow::Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + anyhow::bail!(Tip3Error::UnknownVersion) + } + TokenWalletVersion::Tip3 => { + let mut root_contract = RootTokenContract(self.0); + let name = root_contract.name()?; + let symbol = root_contract.symbol()?; + let decimals = root_contract.decimals()?; + let total_supply = root_contract.total_supply()?; + let owner_address = root_contract.root_owner()?; + + RootTokenContractDetails { + version, + name, + symbol, + decimals, + owner_address, + total_supply, + } + } + }) + } +} + +pub struct RootTokenContract<'a>(pub &'a mut BlockchainAccount); + +impl RootTokenContract<'_> { + pub fn name(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::name(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get name with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get name"))? + .clone(); + + let AbiValue::String(name) = result.value else { + return Err(anyhow::anyhow!("Failed to get name")); + }; + + Ok(name) + } + + pub fn symbol(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::symbol(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get symbol with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get symbol"))? + .clone(); + + let AbiValue::String(symbol) = result.value else { + return Err(anyhow::anyhow!("Failed to get symbol")); + }; + + Ok(symbol) + } + + pub fn decimals(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::decimals(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get decimals with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get decimals"))? + .clone(); + + let AbiValue::Uint(_, decimals) = result.value else { + return Err(anyhow::anyhow!("Failed to get decimals")); + }; + + Ok(decimals.to_u8().unwrap()) + } + + pub fn total_supply(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::total_supply(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get total_supply with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get total_supply"))? + .clone(); + + let AbiValue::Uint(_, total_supply) = result.value else { + return Err(anyhow::anyhow!("Failed to get total_supply")); + }; + + Ok(total_supply.to_u128().unwrap()) + } + + pub fn wallet_code(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::wallet_code(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get wallet_code with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get wallet_code"))? + .clone(); + + let AbiValue::Cell(wallet_code) = result.value else { + return Err(anyhow::anyhow!("Failed to get wallet_code")); + }; + + Ok(wallet_code) + } + + pub fn root_owner(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::root_owner(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get root_owner with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get root_owner"))? + .clone(); + + let AbiValue::Address(root_owner) = result.value else { + return Err(anyhow::anyhow!("Failed to get root_owner")); + }; + + let AnyAddr::Std(root_owner) = &*root_owner else { + return Err(anyhow::anyhow!("Failed to get root_owner")); + }; + + Ok(root_owner.clone()) + } + + pub fn wallet_of(&mut self, owner: StdAddr) -> anyhow::Result { + let inputs = [ + AbiValue::uint(32, 0u32).named("answerId"), + AbiValue::address(owner).named("owner"), + ]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::wallet_of(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get wallet_of with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get wallet_of"))? + .clone(); + + let AbiValue::Address(wallet_of) = result.value else { + return Err(anyhow::anyhow!("Failed to get wallet_of")); + }; + let AnyAddr::Std(wallet_of) = &*wallet_of else { + return Err(anyhow::anyhow!("Failed to get wallet_of")); + }; + + Ok(wallet_of.clone()) + } +} + +pub struct TokenWalletContract<'a>(pub &'a mut BlockchainAccount); + +impl TokenWalletContract<'_> { + pub fn owner(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::owner(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get owner with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get owner"))? + .clone(); + + let AbiValue::Address(owner) = result.value else { + return Err(anyhow::anyhow!("Failed to get owner")); + }; + let AnyAddr::Std(owner) = &*owner else { + return Err(anyhow::anyhow!("Failed to get owner")); + }; + + Ok(owner.clone()) + } + pub fn root(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::root(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get root with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get root"))? + .clone(); + + let AbiValue::Address(root) = result.value else { + return Err(anyhow::anyhow!("Failed to get owrootner")); + }; + let AnyAddr::Std(root) = &*root else { + return Err(anyhow::anyhow!("Failed to get root")); + }; + + Ok(root.clone()) + } + + pub fn balance(&mut self) -> anyhow::Result { + let inputs = [AbiValue::uint(32, 0u32).named("answerId")]; + let ExecutionOutput { values, exit_code } = + self.0.run_local_responsible(super::balance(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get balance with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get balance"))? + .clone(); + + let AbiValue::Uint(_, balance) = result.value else { + return Err(anyhow::anyhow!("Failed to get balance")); + }; + + Ok(balance.to_u128().unwrap()) + } +} + +pub struct SidContract<'a>(pub &'a mut BlockchainAccount); + +impl SidContract<'_> { + pub fn supports_interfaces(&mut self, interfaces: &[u32]) -> anyhow::Result { + let mut inputs: Option<[NamedAbiValue; 2]> = None; + + for &interface in interfaces { + let inputs = match &mut inputs { + Some(inputs) => { + inputs[1] = make_interface_id(interface); + inputs + } + None => inputs.insert([ + AbiValue::uint(32, 0u32).named("answerId"), + make_interface_id(interface), + ]), + }; + + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::supports_interface(), inputs.as_ref())?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get name with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get name"))? + .clone(); + + let AbiValue::Bool(b) = result.value else { + return Err(anyhow::anyhow!("Failed to get name")); + }; + + if !b { + return Ok(false); + } + } + + Ok(true) + } + + pub fn supports_interface(&mut self, interface: u32) -> anyhow::Result { + let inputs = [ + AbiValue::uint(32, 0u32).named("answerId"), + make_interface_id(interface), + ]; + let ExecutionOutput { values, exit_code } = self + .0 + .run_local_responsible(super::supports_interface(), &inputs)?; + + if exit_code != 0 { + return Err(anyhow::anyhow!( + "Failed to get supports_interface with exit code {exit_code}" + )); + } + + let result = values + .first() + .ok_or_else(|| anyhow::anyhow!("Failed to get supports_interface"))? + .clone(); + + let AbiValue::Bool(supports_interface) = result.value else { + return Err(anyhow::anyhow!("Failed to get supports_interface")); + }; + + Ok(supports_interface) + } +} + +fn make_interface_id(interface: u32) -> NamedAbiValue { + AbiValue::uint(32, interface).named("interfaceID") +} diff --git a/src/utils/token_wallets/parsing.rs b/src/utils/token_wallets/parsing.rs new file mode 100644 index 0000000..f12bbdd --- /dev/null +++ b/src/utils/token_wallets/parsing.rs @@ -0,0 +1,304 @@ +use std::{convert::TryFrom, sync::OnceLock}; + +use anyhow::Result; +use tycho_types::{ + abi::Function, + cell::Load, + models::{MsgInfo, OrdinaryTxInfo, OwnedMessage, Transaction}, +}; + +use crate::utils::{ + token_wallets::{ + self, + models::{ + TokenIncomingTransfer, TokenOutgoingTransfer, TokenSwapBack, TokenWalletTransaction, + TokenWalletVersion, TransferRecipient, + }, + }, + ton_wallet::multisig::UnpackerError, + InputMessage, +}; + +pub fn parse_token_transaction( + tx: &Transaction, + info: &OrdinaryTxInfo, + version: TokenWalletVersion, +) -> Option { + if info.aborted { + return None; + } + + let in_msg_cell = tx.in_msg.as_ref()?; + + let Ok(mut slice) = in_msg_cell.as_slice() else { + return None; + }; + let Ok(in_msg) = OwnedMessage::load_from(&mut slice) else { + return None; + }; + + let body = in_msg.body; + let Ok(mut body_slice) = body.1.as_slice() else { + return None; + }; + + let Ok(function_id) = body_slice.get_u32(0) else { + return None; + }; + + let MsgInfo::Int(info) = in_msg.info else { + return None; + }; + + let Ok(functions) = TokenWalletFunctions::for_version(version) else { + return None; + }; + + if info.bounced { + body_slice.skip_first(32, 0).ok()?; + let Ok(function_id) = body_slice.get_u32(0) else { + return None; + }; + body_slice.skip_first(32, 0).ok()?; + + if function_id == functions.accept_transfer.input_id { + return Some(TokenWalletTransaction::TransferBounced( + body_slice.load_u128().ok()?, + )); + } + + if function_id == functions.accept_burn.input_id { + Some(TokenWalletTransaction::SwapBackBounced( + body_slice.load_u128().ok()?, + )) + } else { + None + } + } else if function_id == functions.accept_mint.input_id { + let inputs = functions + .accept_mint + .decode_internal_input(body_slice) + .ok()?; + + Accept::try_from((InputMessage(inputs), version)) + .map(|Accept { tokens }| TokenWalletTransaction::Accept(tokens)) + .ok() + } else if function_id == functions.transfer_to_wallet.input_id { + let inputs = functions + .transfer_to_wallet + .decode_internal_input(body_slice) + .ok()?; + + TokenOutgoingTransfer::try_from(( + InputMessage(inputs), + TransferType::ByTokenWalletAddress, + version, + )) + .map(TokenWalletTransaction::OutgoingTransfer) + .ok() + } else if function_id == functions.transfer.input_id { + let inputs = functions.transfer.decode_internal_input(body_slice).ok()?; + + TokenOutgoingTransfer::try_from(( + InputMessage(inputs), + TransferType::ByOwnerWalletAddress, + version, + )) + .map(TokenWalletTransaction::OutgoingTransfer) + .ok() + } else if function_id == functions.accept_transfer.input_id { + let inputs = functions + .accept_transfer + .decode_internal_input(body_slice) + .ok()?; + + TokenIncomingTransfer::try_from((InputMessage(inputs), version)) + .map(TokenWalletTransaction::IncomingTransfer) + .ok() + } else if function_id == functions.burn.input_id { + let inputs = functions.burn.decode_internal_input(body_slice).ok()?; + + TokenSwapBack::try_from((InputMessage(inputs), version)) + .map(TokenWalletTransaction::SwapBack) + .ok() + } else { + None + } +} + +struct TokenWalletFunctions { + // Incoming + accept_mint: &'static Function, + // Incoming + transfer: &'static Function, + // Incoming + transfer_to_wallet: &'static Function, + // Incoming + accept_transfer: &'static Function, + // Incoming + burn: &'static Function, + // Outgoing + accept_burn: &'static Function, +} + +impl TokenWalletFunctions { + pub fn for_version( + version: TokenWalletVersion, + ) -> Result<&'static TokenWalletFunctions, UnpackerError> { + match version { + TokenWalletVersion::OldTip3v4 => Err(UnpackerError::InvalidAbi), + TokenWalletVersion::Tip3 => { + static IDS: OnceLock = OnceLock::new(); + Ok(IDS.get_or_init(|| Self { + accept_mint: token_wallets::accept_mint(), + transfer: token_wallets::transfer(), + transfer_to_wallet: token_wallets::transfer_to_wallet(), + accept_transfer: token_wallets::accept_transfer(), + burn: token_wallets::burnable::burn(), + accept_burn: token_wallets::accept_burn(), + })) + } + } + } +} + +impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenSwapBack { + type Error = UnpackerError; + + fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + return Err(UnpackerError::InvalidAbi); + } + TokenWalletVersion::Tip3 => { + let input = token_wallets::burnable::BurnInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + + Self { + tokens: input.amount, + callback_address: input.callback_to, + callback_payload: input.payload, + } + } + }) + } +} + +struct Accept { + tokens: u128, +} + +impl TryFrom<(InputMessage, TokenWalletVersion)> for Accept { + type Error = UnpackerError; + + fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + return Err(UnpackerError::InvalidAbi); + } + TokenWalletVersion::Tip3 => { + let input = token_wallets::AcceptMintInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Self { + tokens: input.amount, + } + } + }) + } +} + +enum TransferType { + ByOwnerWalletAddress, + ByTokenWalletAddress, +} + +impl TryFrom<(InputMessage, TransferType, TokenWalletVersion)> for TokenOutgoingTransfer { + type Error = UnpackerError; + + fn try_from( + (value, transfer_type, version): (InputMessage, TransferType, TokenWalletVersion), + ) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + return Err(UnpackerError::InvalidAbi); + } + TokenWalletVersion::Tip3 => { + match transfer_type { + // "transfer" + TransferType::ByOwnerWalletAddress => { + let input = token_wallets::TransferInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Self { + to: TransferRecipient::OwnerWallet(input.recipient), + tokens: input.amount, + payload: input.payload, + } + } + // "transferToWallet" + TransferType::ByTokenWalletAddress => { + let input = token_wallets::TransferToWalletInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Self { + to: TransferRecipient::TokenWallet(input.recipient_token_wallet), + tokens: input.amount, + payload: input.payload, + } + } + } + } + }) + } +} + +impl TryFrom<(InputMessage, TokenWalletVersion)> for TokenIncomingTransfer { + type Error = UnpackerError; + + fn try_from((value, version): (InputMessage, TokenWalletVersion)) -> Result { + Ok(match version { + TokenWalletVersion::OldTip3v4 => { + return Err(UnpackerError::InvalidAbi); + } + TokenWalletVersion::Tip3 => { + let input = token_wallets::AcceptTransferInputs::unpack(value.0) + .map_err(|_| UnpackerError::InvalidAbi)?; + Self { + tokens: input.amount, + sender_address: input.sender, + } + } + }) + } +} + +#[cfg(test)] +mod tests { + + use tycho_types::{boc::Boc, cell::Load, models::TxInfo}; + + use crate::utils::token_wallets::models::TokenWalletVersion; + + use super::*; + + fn parse_transaction(data: &str) -> (Transaction, OrdinaryTxInfo) { + let binding = Boc::decode_base64(data).unwrap(); + let mut cell = binding.as_slice().unwrap(); + let transaction = Transaction::load_from(&mut cell).unwrap(); + let info = match transaction.load_info().unwrap() { + TxInfo::Ordinary(info) => info, + _ => panic!(), + }; + (transaction, info) + } + + #[test] + fn test_parse_wallet_v3_token_transfer_with_payload() { + let (transaction, description) = parse_transaction("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"); + + println!("tx: {transaction:#?}"); + + println!("description: {description:#?}"); + + let parsed = parse_token_transaction(&transaction, &description, TokenWalletVersion::Tip3); + println!("parsed tx: {parsed:#?}"); + } +} diff --git a/src/utils/ton_wallet/ever_wallet.rs b/src/utils/ton_wallet/ever_wallet.rs new file mode 100644 index 0000000..274921e --- /dev/null +++ b/src/utils/ton_wallet/ever_wallet.rs @@ -0,0 +1,175 @@ +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use tycho_types::{ + abi::{AbiHeaderType, AbiValue, AbiVersion, Function, NamedAbiType, UnsignedExternalMessage}, + cell::{CellBuilder, HashBytes}, + models::{ + Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, + StateInit, StdAddr, + }, +}; + +use crate::utils::ton_wallet::{Gift, TonWalletDetails}; +use crate::utils::wallets::code::ever_wallet; + +pub fn prepare_deploy( + public_key: &VerifyingKey, + workchain: i8, + expire_at: u32, +) -> Result { + let state_init = prepare_state_init(public_key)?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + + let dst = StdAddr::new(workchain, *hash); + + let headers = vec![ + AbiHeaderType::Time, + AbiHeaderType::Expire, + AbiHeaderType::PublicKey, + ]; + let function = Function::builder(AbiVersion::V2_3, "sendTransactionRaw") + .with_headers(headers) + .with_inputs(vec![] as Vec) + .with_outputs(vec![] as Vec) + .with_id(0x169e3e11) + .build(); + + let mut unsigned_message = function + .encode_external(&[]) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&dst)?; + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) +} + +pub fn prepare_transfer( + public_key: &VerifyingKey, + current_state: &Account, + address: StdAddr, + gifts: Vec, + expire_at: u32, +) -> Result { + use crate::utils::wallets::ever_wallet; + + if gifts.len() > MAX_MESSAGES { + return Err(EverWalletError::TooManyGifts.into()); + } + + let mut gifts = gifts.into_iter(); + let (function, tokens) = match (gifts.len(), gifts.next()) { + (1, Some(gift)) if gift.state_init.is_none() => { + let function = ever_wallet::send_transaction(); + let tokens = [ + AbiValue::address(gift.destination).named("destination"), + AbiValue::uint(128, gift.amount).named("value"), + AbiValue::Bool(gift.bounce).named("bounce"), + AbiValue::uint(8, gift.flags).named("flags"), + AbiValue::Cell(gift.body.unwrap_or_default()).named("body"), + ] + .to_vec(); + (function, tokens) + } + (len, gift) => { + let function = match len { + 0 => ever_wallet::send_transaction_raw_0(), + 1 => ever_wallet::send_transaction_raw_1(), + 2 => ever_wallet::send_transaction_raw_2(), + 3 => ever_wallet::send_transaction_raw_3(), + _ => ever_wallet::send_transaction_raw_4(), + }; + + let mut tokens = Vec::with_capacity(len * 2); + for gift in gift.into_iter().chain(gifts) { + let body = gift.body.unwrap_or(Default::default()); + let internal_message = Message { + info: MsgInfo::Int(IntMsgInfo { + ihr_disabled: true, + bounce: gift.bounce, + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), + ..Default::default() + }), + init: gift.state_init, + body: body.as_slice()?, + layout: None, + }; + + tokens.push(AbiValue::uint(8, gift.flags).named("flags")); + tokens.push( + AbiValue::Cell(CellBuilder::build_from(internal_message)?).named("message"), + ); + } + (function, tokens) + } + }; + + let mut unsigned_message = function + .encode_external(&tokens) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&address)?; + + match ¤t_state.state { + AccountState::Active { .. } => {} + AccountState::Frozen { .. } => return Err(EverWalletError::AccountIsFrozen.into()), + AccountState::Uninit => { + unsigned_message.set_state_init(Some(prepare_state_init(public_key)?)); + } + }; + + Ok(unsigned_message) +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x3b, 0xa6, 0x52, 0x8a, 0xb2, 0x69, 0x4c, 0x11, 0x81, 0x80, 0xaa, 0x3b, 0xd1, 0x0d, 0xd1, 0x9f, + 0xf4, 0x00, 0xb9, 0x09, 0xab, 0x4d, 0xcf, 0x58, 0xfc, 0x69, 0x92, 0x5b, 0x2c, 0x7b, 0x12, 0xa6, +]; + +pub fn is_ever_wallet(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { + let state = prepare_state_init(public_key)?; + let binding = CellBuilder::build_from(state)?; + let hash = binding.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) +} + +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { + let mut builder = CellBuilder::new(); + builder.store_u256(&HashBytes::from(public_key.to_bytes()))?; + builder.store_u64(0)?; + + let data = builder.build()?; + + Ok(StateInit { + code: Some(ever_wallet()), + data: Some(data), + ..Default::default() + }) +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 EVER + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 4; + +#[derive(thiserror::Error, Debug)] +enum EverWalletError { + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, +} diff --git a/src/utils/ton_wallet/highload_wallet_v2.rs b/src/utils/ton_wallet/highload_wallet_v2.rs new file mode 100644 index 0000000..c4f05e3 --- /dev/null +++ b/src/utils/ton_wallet/highload_wallet_v2.rs @@ -0,0 +1,380 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use tycho_types::{ + cell::{Cell, CellBuilder, CellFamily, HashBytes, Load, Store}, + dict::Dict, + models::{ + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OwnedMessage, + OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, + }, +}; + +use crate::utils::wallets::code::highload_wallet_v2; + +use super::{Gift, TonWalletDetails}; + +pub fn prepare_deploy( + public_key: &VerifyingKey, + workchain: i8, + expire_at: u32, +) -> Result { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + let dst = compute_contract_address(public_key, workchain)?; + let (hash, payload) = init_data.make_deploy_payload(expire_at)?; + let message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std(dst), + ..Default::default() + }), + body: Default::default(), + init: Some(init_data.make_state_init()?), + layout: None, + }; + Ok(UnsignedHighloadWalletV2Message { + init_data, + gifts: vec![], + payload, + hash, + expire_at, + message, + }) +} + +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + init_data.make_state_init() +} + +pub fn prepare_transfer( + public_key: &VerifyingKey, + current_state: &Account, + gifts: Vec, + expire_at: u32, +) -> Result { + if gifts.len() > DETAILS.max_messages { + return Err(HighloadWalletV2Error::TooManyGifts.into()); + } + + let (init_data, with_state_init) = match ¤t_state.state { + AccountState::Active(state_init) => match &state_init.data { + Some(data) => (InitData::try_from(data)?, false), + None => return Err(HighloadWalletV2Error::InvalidInitData.into()), + }, + AccountState::Frozen { .. } => return Err(HighloadWalletV2Error::AccountIsFrozen.into()), + AccountState::Uninit => ( + InitData::from_key(public_key).with_wallet_id(WALLET_ID), + true, + ), + }; + + if init_data.data.values().count() >= 500_usize { + return Err(HighloadWalletV2Error::InitDataTooLarge.into()); + } + + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; + let mut message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std( + current_state + .address + .as_std() + .ok_or(HighloadWalletV2Error::InvalidAddress)? + .clone(), + ), + ..Default::default() + }), + body: Default::default(), + init: None, + layout: None, + }; + + if with_state_init { + message.init = Some(init_data.make_state_init()?); + } + + Ok(UnsignedHighloadWalletV2Message { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }) +} + +pub struct UnsignedHighloadWalletV2Message { + init_data: InitData, + gifts: Vec, + payload: CellBuilder, + hash: HashBytes, + expire_at: u32, + message: OwnedMessage, +} + +impl UnsignedHighloadWalletV2Message { + pub fn expire_at(&self) -> u32 { + self.expire_at + } + + pub fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + pub fn gifts(&self) -> &[Gift] { + &self.gifts + } + + pub fn message(&self) -> &OwnedMessage { + &self.message + } + + pub fn init_data(&self) -> &InitData { + &self.init_data + } + + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; + + let mut message = self.message.clone(); + message.body = payload.build()?.into(); + + Ok(message) + } +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x0b, 0x3a, 0x88, 0x7a, 0xea, 0xcd, 0x2a, 0x7d, 0x40, 0xbb, 0x55, 0x50, 0xbc, 0x92, 0x53, 0x15, + 0x6a, 0x02, 0x90, 0x65, 0xae, 0xfb, 0x6d, 0x6b, 0x58, 0x37, 0x35, 0xd5, 0x8d, 0xa9, 0xd5, 0xbe, +]; + +pub fn is_highload_wallet_v2(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { + InitData::from_key(public_key) + .with_wallet_id(WALLET_ID) + .compute_addr(workchain_id) +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: 250, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +/// `HighloadWalletV2` init data +#[derive(Clone)] +pub struct InitData { + pub wallet_id: u32, + pub last_cleaned: u64, + pub public_key: HashBytes, + pub data: Dict, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + &self.public_key.0 + } + + pub fn from_key(key: &VerifyingKey) -> Self { + Self { + wallet_id: 0, + last_cleaned: 0, + public_key: HashBytes::from_slice(key.as_bytes()), + data: Dict::default(), + } + } + + pub fn with_wallet_id(mut self, id: u32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let state_init = self.make_state_init()?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) + } + + pub fn make_state_init(&self) -> Result { + Ok(StateInit { + code: Some(highload_wallet_v2()), + data: Some(self.serialize()?), + ..Default::default() + }) + } + + pub fn serialize(&self) -> Result { + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id)?; + builder.store_u64(self.last_cleaned)?; + builder.store_u256(&self.public_key)?; + self.data.store_into(&mut builder, Cell::empty_context())?; + let data = builder.build()?; + Ok(data) + } + + pub fn make_deploy_payload(&self, expire_at: u32) -> Result<(HashBytes, CellBuilder)> { + let mut builder = CellBuilder::new(); + + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_u32(u32::MAX)?; + builder.store_bit_zero()?; + + let payload = builder.clone().build()?; + + let hash = payload.repr_hash(); + + Ok((*hash, builder)) + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + ) -> Result<(HashBytes, CellBuilder)> { + // Prepare messages array + let mut messages = Dict::::new(); + for (i, gift) in gifts.into_iter().enumerate() { + let internal_message = OwnedRelaxedMessage { + info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { + ihr_disabled: true, + bounce: gift.bounce, + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), + ..Default::default() + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).into(), + layout: None, + }; + + let cell = CellBuilder::build_from(internal_message)?; + + let key = i as u16; + messages.set(key, (gift.flags, cell))?; + } + + let mut message_builder = CellBuilder::new(); + messages.store_into(&mut message_builder, Cell::empty_context())?; + + let messages_cell = message_builder.clone().build()?; + let messages_hash = messages_cell.repr_hash(); + + // Build payload + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_raw(&messages_hash.as_slice()[28..32], 32)?; + builder.store_builder(&message_builder)?; + + let payload = builder.clone().build()?; + let hash = payload.repr_hash(); + + Ok((*hash, builder)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut slice = data.as_slice()?; + let wallet_id = slice.load_u32()?; + let last_cleaned = slice.load_u64()?; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + let data = Dict::::load_from(&mut slice)?; + + Ok(Self { + wallet_id, + last_cleaned, + public_key, + data, + }) + } +} + +const WALLET_ID: u32 = 0x00000000; + +#[derive(thiserror::Error, Debug)] +enum HighloadWalletV2Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, + #[error("Account init data is too large")] + InitDataTooLarge, + #[error("Account address is not valid")] + InvalidAddress, +} + +#[cfg(test)] +pub mod tests { + use anyhow::Result; + use ed25519_dalek::VerifyingKey; + use tycho_types::{ + boc::Boc, + cell::Load, + models::{AccountState, StdAddr}, + }; + + use crate::utils::ton_wallet::{ + highload_wallet_v2::{InitData, WALLET_ID}, + Gift, + }; + + #[test] + fn check_state() -> Result<()> { + let account_base64 = "te6ccgICCBAAAQAAOOsAAAIBmggHAAEBWQAAAABij1ipvO9y33lafOQTY2Zjcpu/tM7FomMbSFDp4+8Ei8aUcpwDouTBwAACAgiLsUesAl4AAwIBYgA1AAQCAnAAFAAFAgFIABEABgIBIAAKAAcCAW4ACQAIAAm3P2/0YAAJty45OOACASAADAALAAm7gCmMCAIBIAAQAA0CASAADwAOAAm3SNaR4AAJt2IEcmAACbn59J8wAgEgABMAEgAJvH0s0sQACb0fFP0kAgEgACYAFQIBIAAfABYCASAAGgAXAgEgABkAGAAJulOvR9gACbr90p9IAgFYABwAGwAJuaPhPtACASAAHgAdAAm29FO6oAAJt4e9WeACASAAJQAgAgEgACQAIQIBIAAjACIACbg4HOPQAAm5N8GT0AAJumQHj3gACbxuQUbMAgEgACwAJwIBIAApACgACbxoeVqsAgEgACsAKgAJu3kh5AgACbuT7z2oAgEgADIALQIBIAAxAC4CAUgAMAAvAAm3D/yF4AAJttuTf6AACboR+FvYAgEgADQAMwAJu/xtoCgACbswljEoAgEgAVEANgIBIADGADcCASAAfQA4AgEgAFwAOQIBIABLADoCASAASgA7AgEgAD8APAIBIAA+AD0ACbv67XUoAAm728BYuAIBIABDAEACASAAQgBBAAm4XHbOEAAJuEX0C9ACASAASQBEAgEgAEgARQIBSABHAEYACLJFqpcACLIZDQgACbb/vM5gAAm5tBZx8AAJv6qZvYYCASAAVQBMAgEgAFIATQIBWABRAE4CAWYAUABPAAizva6wAAizJMbvAAm4yrfY0AIBIABUAFMACbv97cQIAAm7rFAKCAIBIABXAFYACbwdUD3MAgEgAFkAWAAJuqMTi4gCA5B3AFsAWgAHqXo6sAAHqTSr0AIBIABsAF0CASAAYwBeAgEgAGIAXwICcQBhAGAACbU816HAAAm0goAhwAAJvMcETxwCASAAawBkAgEgAGoAZQIBIABnAGYACbjQvmEQAgJxAGkAaAAHsBsUkQAHsGq37wAJu6lVp4gACb01zX/cAgEgAHYAbQIBIABzAG4CASAAcABvAAm6QVAyuAIBSAByAHEACbd6cIWgAAm2kdMmYAIBIAB1AHQACbotzlT4AAm66wR8CAIBIAB8AHcCASAAewB4AgEgAHoA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trax3CACASADAgMBAAm7WoeimAIBIAMEAwMACbm8yrWwAAm5HOVjEAIBSAMHAwYACbpktfloAAm7LsDquAIBIAMUAwkCAVgDCwMKAAm6hR6B6AIBIAMPAwwCASADDgMNAAm2f4hroAAJtllCn+ACASADEQMQAAm2Yx6y4AIBIAMTAxIACbSflolAAAm0QerswAIBIAMYAxUCASADFwMWAAm7tkoeSAAJuknHOugACb2TEThEAgEgAzkDGgIBIAMoAxsCASADIQMcAgEgAyADHQIBIAMfAx4ACbuaE9GIAAm76TwyuAAJvTF5SlwCASADJQMiAgFmAyQDIwAJt+MXWCAACbfdIXagAgEgAycDJgAJu+jFS4gACbqhfTLYAgEgAzIDKQIBIAMvAyoCAVgDLgMrAgFYAy0DLAAJtOPh2kAACbTQbPDAAAm5hunfsAIBIAMxAzAACbqF6XDoAAm7UeL9mAIBIAM2AzMCASADNQM0AAm7xZq5KAAJuxrvMUgCASADOAM3AAm7OY2quAAJumXrW7gCASADSwM6AgEgA0YDOwIBIANBAzwCASADPgM9AAm7wUzueAIBIANAAz8ACbmxiDdwAAm5KDyKEAIBIANDA0IACbupeOFIAgEgA0UDRAAJuNLLTRAACbir+oZQAgEgA0oDRwIBIANJA0gACbonQw4YAAm6iJr9KAAJvAwRrswCASADVQNMAgEgA1QDTQIBIANPA04ACbo2gb2IAgFqA1EDUAAJtAxQcEACAVgDUwNSAAex5r87AAex7i7vAAm8a0P6FAIBIANXA1YACb0WUg0sAgEgA1kDWAAJupoiEwgACbsZNqNoAgEgA94DWwIBIAObA1wCASADeANdAgEgA3EDXgIBIANmA18CASADYwNgAgEgA2IDYQAJu3UoScgACbtyxfUoAgEgA2UDZAAJu7/lGDgACbugeIXoAgEgA24DZwIBIANrA2gCASADagNpAAm5vLA10AAJubPahdACASADbQNsAAm5tK+QUAAJuEioGPACAUgDcANvAAm5CURc8AAJuCyslfACAUgDcwNyAAm8IYMwTAIBSAN3A3QCASADdgN1AAm3DbyIoAAJti1cfKAACbgyOlvwAgEgA4oDeQIBIAOBA3oCASADgAN7AgEgA30DfAAJu0UfkFgCASADfwN+AAm4pvk5sAAJua8OZHAACbx9uwGcAgEgA4kDggIBIAOEA4MACbsA7XSYAgEgA4gDhQIDeuADhwOGAAevcW2GAAeu6EI2AAm45HCLkAAJvaykwLQCASADkgOLAgEgA48DjAIBIAOOA40ACboDFLRYAAm7Xm0nKAICcwORA5AACbWpL3NAAAm06RoTQAIBIAOYA5MCAWYDlQOUAAm27M4MYAIDemADlwOWAAetVAtkAAetOX2sAgEgA5oDmQAJuhdHT+gACbvwKR/4AgEgA78DnAIBIAOwA50CASADowOeAgEgA6IDnwIBIAOhA6AACbsibw8IAAm7gNSLmAAJvCzRh6wCASADpwOkAgFYA6YDpQAJuS6ko1AACbgyPbaQAgEgA6sDqAIBWAOqA6kACbaIXqHgAAm36uoEYAIBIAOtA6wACblh7s4QAgN8GAOvA64AB6zwdcwAB63IpOQCASADuAOxAgEgA7UDsgIBIAO0A7MACbu93KP4AAm6rZ3RuAIBIAO3A7YACboUe+DoAAm64Yj8eAIBIAO8A7kCAWIDuwO6AAm2IswOYAAJt8HnBeACASADvgO9AAm6P16YGAAJuxr3VrgCASADzQPAAgEgA8oDwQIBIAPFA8ICASADxAPDAAm7AMQjSAAJupW59XgCASADxwPGAAm7JfVvuAIBIAPJA8gACbnd5AowAAm4NL38MAIBIAPMA8sACbzZEnzcAAm8LpKszAIBIAPTA84CASAD0gPPAgEgA9ED0AAJu5OONegACbpCsrVoAAm86S7sLAIBIAPbA9QCASAD2APVAgEgA9cD1gAJuB9a7XAACbmhMm+QAgEgA9oD2QAJuGQx0VAACbilQ7IQAgFYA90D3AAJuUl3VtAACbi51oXQAgEgBCID3wIBIAQBA+ACASAD9APhAgEgA+8D4gIBIAPqA+MCASAD5QPkAAm7cw2FuAIBIAPpA+YCA4yEA+gD5wAHqwGy2AAHq06PyAAJuN8hC/ACASAD7APrAAm7lGOn6AIBIAPuA+0ACbgAKxFQAAm4zWmjMAIBIAPxA/AACbwBpQ+MAgJzA/MD8gAJtQsK+EAACbWzzL7AAgEgBAAD9QIBIAP5A/YCAVgD+AP3AAm5f8UUEAAJuMjNF1ACASAD/wP6AgEgA/wD+wAJuCkIBdACASAD/gP9AAm3zUDaoAAJtgmcViAACbvmJec4AAm/RJNtzgIBIAQTBAICASAEDAQDAgEgBAcEBAIBIAQGBAUACbrt5VtYAAm71oVVOAIBIAQJBAgACboXk6TYAgEgBAsECgAJuCN+7BAACbmgxryQAgEgBBAEDQIBSAQPBA4ACbhCPFzwAAm4+sYhsAIBSAQSBBEACbl0oshwAAm5CiVZ0AIBIAQVBBQACb5aaLuWAgEgBBsEFgIBIAQaBBcCASAEGQQYAAm4uzRMcAAJuazMZLAACbvG0tcIAgEgBB8EHAIBWAQeBB0ACbatQhogAAm2WAJkYAIBIAQhBCAACbkuoEaQAAm4KbuNMAIBIARGBCMCASAEMwQkAgEgBCwEJQIBIAQpBCYCASAEKAQnAAm6W0ISmAAJuyAeaigCA4zcBCsEKgAHrwEmygAHr5v6mgIBIAQyBC0CASAEMQQuAgEgBDAELwAJuAj74PAACbhIihxwAAm6moP76AAJvVbnSfwCASAEPwQ0AgEgBDoENQIBWAQ5BDYCASAEOAQ3AAm3ccAy4AAJt6GqWKAACbkimfCwAgFYBDwEOwAJuJoMTlACA43EBD4EPQAHqg+deAAHqhIN2AIBIARDBEACAVgEQgRBAAm5K5PCUAAJubJGwnACAW4ERQREAAm2xmL/oAAJtgWXqaACASAEWARHAgEgBFEESAIBIAROBEkCAVgESwRKAAm5xuARkAIBIARNBEwACbaoSyNgAAm26DRiYAIBIARQBE8ACbux8k+YAAm6r73LWAIBIARTBFIACbztraocAgEgBFcEVAIBIARWBFUACbgQr6LQAAm57gFt0AAJu/J0w/gCASAEYARZAgEgBF8EWgIBIARcBFsACbrf7u3oAgEgBF4EXQAJuY2NjVAACbhyS2YwAAm8K5V3jAIBIARoBGECASAEZQRiAgFuBGQEYwAJtIrImcAACbXfBC9AAgFIBGcEZgAJt51w6KAACbbTV9xgAgLkBGoEaQAIs9x5BAAIs3xtmQIBIAWDBGwCASAE+gRtAgEgBLUEbgIBIASSBG8CASAEgQRwAgEgBH4EcQIBIAR5BHICASAEdARzAAm7CGQNaAIBIAR2BHUACbj8boKQAgJwBHgEdwAHsXoP2QAHsaGjOwIBWAR7BHoACblOn37wAgJwBH0EfAAHsTSLcwAHsIobzwIBSASABH8ACbu9MMAIAAm61sK6eAIBIASJBIICAVgEhgSDAgFIBIUEhAAJt8tiuGAACbcgBDjgAgEgBIgEhwAJuDA3M3AACbkFE+8QAgEgBIsEigAJvL1TIuQCASAEkQSMAgEgBJAEjQIBSASPBI4ACbVtFaHAAAm09cQAwAAJuLJw9vAACbrEmJvIAgEgBKYEkwIBIASVBJQACb5n1btSAgEgBJsElgIBSASYBJcACbnfIk3QAgFmBJoEmQAIs4Zx1gAIs3Za5wIBIASfBJwCAUgEngSdAAm3W+o2oAAJt/VihSACASAEowSgAgEgBKIEoQAJti96V2AACbYGH2xgAgFIBKUEpAAJtWnS6kAACbXWJxDAAgEgBLAEpwIBIAStBKgCASAEqgSpAAm67flcqAIBIASsBKsACbhDeBEwAAm4l4f+UAIBIASvBK4ACbswlNpoAAm7SQp7GAIBIASyBLEACb3ClbOsAgFIBLQEswAJuOyautAACbnvnEKwAgEgBNcEtgIBIATIBLcCASAEwQS4AgEgBL4EuQIBIAS9BLoCA3ngBLwEuwAHsIfLXwAHsaesqwAJu6Od7TgCASAEwAS/AAm6L8d0qAAJukFIkYgCASAExwTCAgFYBMQEwwAJuToCSBACASAExgTFAAm289AyIAAJtwfTp2AACb1XEAdMAgEgBNQEyQIBIATNBMoCASAEzATLAAm6KjRAaAAJumCRdwgCASAEzwTOAAm7BIqfqAIBIATTBNACASAE0gTRAAm2jGjJIAAJt8A1xGAACbi6NCUwAgFIBNYE1QAJukZ1HqgACbthqat4AgEgBOkE2AIBIATiBNkCASAE3QTaAgEgBNwE2wAJu3tMFIgACbq66pf4AgEgBN8E3gAJuwjYYDgCASAE4QTgAAm4uqui8AAJuD9RmnACASAE6ATjAgEgBOcE5AIBagTmBOUACbUAvGrAAAm0zafdwAAJuzVUC9gACbwt0q1MAgEgBPEE6gIBIATwBOsCASAE7wTsAgEgBO4E7QAJuSpdCtAACbkoQHwQAAm6WO4+KAAJvdPnptQCASAE8wTyAAm8Xw14XAIBIAT3BPQCASAE9gT1AAm4qk7CEAAJuH63FBACAUgE+QT4AAm32QmOIAAJt/tnc+ACASAFPgT7AgEgBR0E/AIBIAUOBP0CASAFCQT+AgEgBQYE/wIBWAUDBQACASAFAgUBAAm2ztJs4AAJtq4iA2ACAVgFBQUEAAm0eov9wAAJtUu7YsACASAFCAUHAAm7wqFYSAAJujBmcRgCASAFDQUKAgEgBQwFCwAJusacokgACbqnN0XoAAm8WS6ODAIBIAUaBQ8CASAFFwUQAgEgBRIFEQAJuqhbkEgCAUgFFgUTAgEgBRUFFAAJtGi5GcAACbST9EJAAAm3R56mIAIBIAUZBRgACbswhgHYAAm6TvUn+AIBWAUcBRsACboEE6cYAAm60aFB6AIBIAUvBR4CASAFKgUfAgEgBSMFIAIBagUiBSEACbZJ+u5gAAm3kLEtoAIBIAUnBSQCASAFJgUlAAm4acieUAAJucna2PACAWIFKQUoAAm1tfYKwAAJtIoZrkACASAFLAUrAAm8bF/R5AIBIAUuBS0ACbqxc184AAm7ONdO2AIBIAU5BTACASAFNAUxAgEgBTMFMgAJutkluWgACbvRI/GoAgEgBTYFNQAJu19tG3gCA3ogBTgFNwAHsLkD7wAHsBDMyQIBIAU7BToACb3He2eUAgFuBT0FPAAJtkS6qaAACbch3b4gAgEgBWIFPwIBIAVRBUACASAFSgVBAgEgBUcFQgIBWAVGBUMCA3jgBUUFRAAHrrGPLgAHrpIPUgAJuNTuMFACASAFSQVIAAm6xJAyWAAJu4Tv+DgCASAFUAVLAgEgBU0FTAAJujqDlqgCASAFTwVOAAm4JSBo8AAJuUq9hpAACb2AI0XcAgEgBVsFUgIBIAVWBVMCASAFVQVUAAm6OYCTqAAJujqBvKgCASAFWAVXAAm6hQ+Q+AIBIAVaBVkACbnAFfqwAAm418gd0AIBIAVfBVwCAUgFXgVdAAm4UbtYkAAJuD6Pc9ACASAFYQVgAAm6YOYneAAJunj+8YgCASAFdAVjAgEgBWcFZAICdwVmBWUACbfzechgAAm29tJ9YAIBIAVvBWgCAUgFbgVpAgEgBW0FagIBIAVsBWsACbRnU7xAAAm07P4GwAAJtn65FqAACbkp6NawAgEgBXMFcAIBIAVyBXEACblN0oMQAAm5dc2x0AAJu4u5zigCASAFfgV1AgEgBXcFdgAJvS8T6AwCASAFeQV4AAm6zybfSAIBIAV7BXoACbgQZO2wAgEgBX0FfAAJt6jRXyAACbZBDgCgAgEgBYAFfwAJvffckhwCAWIFggWBAAm2p3hqIAAJtmPJuGACASAGCwWEAgEgBcoFhQIBIAWnBYYCASAFmAWHAgEgBZEFiAIBIAWOBYkCASAFjQWKAgEgBYwFiwAJuHC+cXAACbloaz7QAAm6oCAnCAIBIAWQBY8ACbt2D1lYAAm6G7Jv+AIBIAWXBZICAVgFlAWTAAm5nwOu8AIBSAWWBZUACbVp0CtAAAm0JT5uwAAJvLtv2lwCASAFogWZAgEgBZsFmgAJvNSZoKQCAUgFnQWcAAm5m/+DEAIBIAWfBZ4ACbb/LKpgAgEgBaEFoAAJtc0qnEAACbShxIzAAgEgBaQFowAJvOTM//wCASAFpgWlAAm6cWnRGAAJu79vj1gCASAFuQWoAgEgBawFqQIBWAWrBaoACbupa6I4AAm7jMIwyAIBIAW0Ba0CASAFsQWuAgEgBbAFrwAJuWmR6TAACbgG4HDQAgFmBbMFsgAJtLAfN8AACbQ2LzTAAgFIBbYFtQAJuO+6STACAW4FuAW3AAiymmx2AAizvMTRAgEgBb8FugIBIAW8BbsACb03LvNEAgEgBb4FvQAJuvwTkwgACbqam8+oAgEgBcMFwAIBWAXCBcEACbnOmqYQAAm4SfEdEAIBIAXFBcQACbr8wnP4AgEgBckFxgIBZgXIBccACLNW3EkACLJM6psACbmV6jvQAgEgBeoFywIBIAXbBcwCASAF1gXNAgEgBdUFzgIBIAXUBc8CASAF0QXQAAm4wq6e8AIBSAXTBdIACbTTjpnAAAm1r6/PQAAJusgDXdgACb2S5cmcAgEgBdoF1wIBIAXZBdgACbsG4jZYAAm7100VGAAJvOj0OtwCASAF5QXcAgEgBeQF3QIBIAXjBd4CASAF4gXfAgFiBeEF4AAIsyqwJgAIssXnNQAJuCxGopAACboej8/IAAm9ZdZE5AIBIAXpBeYCAWIF6AXnAAm2ua3LoAAJt+Bq2eAACbx44fEMAgEgBfwF6wIBIAXzBewCASAF7gXtAAm8HVbvxAIBIAXyBe8CAWYF8QXwAAm0rj1EQAAJtEJB18AACbt18qQIAgEgBfUF9AAJvBg68LwCASAF+wX2AgEgBfoF9wICcgX5BfgAB7FU9nkAB7Ctx1cACbiMtLtwAAm7uGZ1aAIBIAYCBf0CASAF/wX+AAm9DibOVAIBagYBBgAACbZTugIgAAm36nUNoAIBIAYIBgMCASAGBwYEAgJ1BgYGBQAIs6qZBgAIssc7qwAJu/tPkogCA3jgBgoGCQAIsklucwAIsteNfgIBIAZJBgwCASAGKAYNAgEgBhkGDgIBIAYUBg8CASAGEwYQAgLEBhIGEQAIs0FnwAAIswGNOAAJvd+NIdQCAVgGGAYVAgEgBhcGFgAJufr9/HAACbhmBBnwAAm6b0wwWAIBIAYlBhoCASAGIAYbAgEgBh8GHAIBIAYeBh0ACblogMbQAAm4De1pkAAJu+l+2WgCASAGJAYhAgFIBiMGIgAJtg71r6AACbZTM+0gAAm6WkuoeAIBIAYnBiYACbyBH1J8AAm8AAIntAIBIAY6BikCASAGNQYqAgFIBjAGKwIBIAYtBiwACbjE0zWQAgEgBi8GLgAJtolq2WAACbaOuKegAgEgBjQGMQIBIAYzBjIACbc8LxsgAAm2BWsYoAAJuWMm27ACAVgGNwY2AAm75KsEuAIBWAY5BjgACbdKCtUgAAm2dbZHIAIBIAZCBjsCASAGPwY8AgFYBj4GPQAJuVWvfLAACbiMU/lwAgFIBkEGQAAJuEvCm3AACbhBPACwAgEgBkYGQwIBIAZFBkQACbs9YiJIAAm6tLNpqAIBYgZIBkcACba31HFgAAm3Na1D4AIBIAZpBkoCASAGXAZLAgEgBlEGTAIBZgZQBk0CAVgGTwZOAAm1m707QAAJtMWRAMAACbm7nwjwAgEgBlMGUgAJvBfE8GwCASAGWwZUAgEgBlYGVQAJuIxdkLACASAGWgZXAgFYBlkGWAAIshf1RAAIs1YMmwAJtyCPOWAACbvy8unYAgEgBmIGXQIBIAZfBl4ACby+jP78AgEgBmEGYAAJuzalYfgACbpRITyoAgEgBmgGYwIBIAZlBmQACbriqSPIAgFIBmcGZgAJt9BaDWAACbZ4y2BgAAm9IuDOBAIBIAZ7BmoCASAGdgZrAgEgBnEGbAIBIAZuBm0ACbrK616oAgJxBnAGbwAIsqAj0wAIssBPogIBIAZzBnIACbvckP84AgEgBnUGdAAJuYsiHTAACbjgaQKwAgEgBngGdwAJvNXCGMQCAUgGegZ5AAm4ipr+0AAJuPRGrHACASAGhwZ8AgEgBoAGfQICcQZ/Bn4ACbUxxjzAAAm0aTyPQAIBIAaEBoECASAGgwaCAAm5POm0UAAJuTUTtbACAnAGhgaFAAiyE8A5AAizHVoeAgJxBokGiAAJtwo+/yACAVgGiwaKAAiy7XPsAAiy0IY1AgFYB6AGjQIBIAcXBo4CASAG0AaPAgEgBq0GkAIBIAaeBpECASAGkwaSAAm/k8/gYgIBIAaXBpQCAUgGlgaVAAm41EAPsAAJuDH8AZACASAGnQaYAgEgBpwGmQIBWAabBpoACbXcrY3AAAm09FgIQAAJuUr15BAACbvOJ35oAgEgBqgGnwIBIAanBqACASAGpgahAgFIBqUGogIBagakBqMAB7DFuysAB7DabOcACber0xpgAAm65b/0qAAJvHUikOwCASAGqgapAAm8BS/BfAIBagasBqsACbdEpWugAAm2NDBjIAIBIAa9Bq4CASAGtgavAgEgBrEGsAAJvUyoGsQCASAGtQayAgEgBrQGswAJuBs47RAACbkNcLQwAAm7UAWvmAIBIAa8BrcCASAGuwa4AgFuBroGuQAJtca5YsAACbQvezjAAAm66qZweAAJvCszOmwCASAGxQa+AgEgBsQGvwIBIAbBBsAACbuVk3foAgJ3BsMGwgAIs8QFjgAIsnG+qAAJvKuRgHwCASAGxwbGAAm99sqLRAIBIAbNBsgCASAGygbJAAm4Zk/j8AIBSAbMBssACbWd6B5AAAm1fsoNQAIDeWAGzwbOAAex96ppAAexZ9qFAgEgBvQG0QIBIAbhBtICASAG3AbTAgEgBtkG1AIBIAbWBtUACbta59VIAgFiBtgG1wAJtLbxsMAACbWhF6xAAgFIBtsG2gAJuc6e1PAACbkv5c0wAgEgBt4G3QAJvMOAkSQCASAG4AbfAAm6xgv3uAAJu1M1T9gCASAG7wbiAgEgBuoG4wIBIAbnBuQCASAG5gblAAm500GS8AAJuKbO2FACASAG6QboAAm4IdYb0AAJuOw8CvACAVgG7AbrAAm4Tf/9UAICdwbuBu0AB7HpWN0AB7G7V10CASAG8QbwAAm9LGsDpAIBWAbzBvIACblstnkwAAm4zJ31MAIBIAcGBvUCASAHAQb2AgEgBvwG9wIBIAb5BvgACbofetcIAgJ0BvsG+gAIs69ysAAIsm3IxQIBIAcABv0CAUgG/wb+AAm2QKxbYAAJt+KAjWAACbpgZjJYAgEgBwUHAgIBSAcEBwMACbiYVg6QAAm4WAWqEAAJveM93fwCASAHFAcHAgEgBw0HCAIBIAcKBwkACbukLNy4AgEgBwwHCwAJuKTgjdAACbnbyXQwAgEgBxMHDgIBIAcSBw8CASAHEQcQAAm3MHyzIAAJtz0isWAACbk7mLwQAAm6GpFsOAICdgcWBxUACbdMosogAAm3I25X4AIBIAdfBxgCASAHPAcZAgEgBy0HGgIBIAcgBxsCAVgHHQccAAm6tZ6/aAIBIAcfBx4ACblw7ddwAAm471f5EAIBIAcmByECAnYHJQciAgEgByQHIwAIsokIOAAIs/cuMQAJtFuPIkACASAHKgcnAgEgBykHKAAJuXzJqVAACbijtwEwAgFuBywHKwAJtTrUaMAACbWHu8XAAgEgBzEHLgIBIAcwBy8ACbyzI4O0AAm92rNDTAIBIAc5BzICASAHNgczAgEgBzUHNAAJuSSkDdAACbmVEQqwAgEgBzgHNwAJuIL6vbAACbiEFNVwAgFmBzsHOgAJtwecEaAACbbV22QgAgEgB04HPQIBIAdFBz4CASAHQgc/AgEgB0EHQAAJuthTuVgACbp0q2qYAgN7IAdEB0MACLMYj0cACLMnT4wCASAHRwdGAAm9JYSWBAIBIAdJB0gACbtoS/vYAgEgB0sHSgAJuSVI2rACASAHTQdMAAm3njl44AAJtmyCQiACASAHUgdPAgFYB1EHUAAJurp9eSgACburQTNoAgEgB1QHUwAJvNy+fQQCASAHXAdVAgEgB1sHVgIBIAdaB1cCAnMHWQdYAAevawxaAAevT52SAAm3TA+HoAAJuBjUlzACAUgHXgddAAm273ucoAAJtn9G+qACASAHfwdgAgEgB24HYQIBIAdjB2IACb77rhMyAgEgB2kHZAIBIAdmB2UACbpZCbbIAgEgB2gHZwAJuMg7VHAACbk88ziQAgFYB20HagIBagdsB2sACLOqJCUACLPBHZoACbgQ2nRQAgEgB3gHbwIBIAd3B3ACASAHdgdxAgEgB3UHcgIBYgd0B3MACLMsW6UACLN6pBEACbgpHrAQAAm7CjvlqAAJvTr0SuwCASAHegd5AAm9gIfS3AIBIAd+B3sCAUgHfQd8AAm2ExT5IAAJtxmUuWAACbs6y/6YAgEgB5EHgAIBIAeOB4ECASAHhQeCAgEgB4QHgwAJugo7ILgACbqTlnoYAgEgB4sHhgIBIAeKB4cCAUgHiQeIAAm1UW01wAAJtfXLccAACbl6vaYwAgEgB40HjAAJuR7bj3AACbjzIW0QAgEgB5AHjwAJvP4+bvwACbyER1S0AgEgB5kHkgIBWAeWB5MCASAHlQeUAAm48qsdkAAJuCRPjZACASAHmAeXAAm5I/oJMAAJuEoOsTACASAHmweaAAm9spfVhAIBIAefB5wCASAHngedAAm4LvmDEAAJuCGGrRAACbrDTB3IAgFYB+QHoQIBIAfBB6ICASAHtAejAgEgB6cHpAIBWAemB6UACboltCs4AAm727E5GAIBIAetB6gCASAHrAepAgFIB6sHqgAJtthUrSAACbaYPycgAAm71W2UyAIBIAevB64ACbp73lt4AgEgB7MHsAIBWAeyB7EACbRsiFTAAAm0rvGXQAAJuBEl+jACASAHvAe1AgEgB7sHtgIBIAe6B7cCASAHuQe4AAm5UfX5UAAJuLdOWpAACbpSRvJIAAm886pALAIBIAe+B70ACb2rC3AMAgEgB8AHvwAJuklXYggACboQXSjIAgEgB9EHwgIBIAfGB8MCASAHxQfEAAm8j5s3JAAJvbGjuhQCASAHygfHAgFYB8kHyAAJuPeco5AACbjiQGOQAgFIB8wHywAJuc9QKHACASAHzgfNAAm3y0IfIAIBIAfQB88ACbSvVMLAAAm0bk+pwAIBIAfbB9ICASAH2AfTAgJ2B9UH1AAJtcybpUACAnMH1wfWAAesPLH8AAetjl10AgEgB9oH2QAJu+K36BgACbqgx7xYAgEgB98H3AIBIAfeB90ACbv+K0/YAAm6RsZZ2AIBIAfhB+AACbqFlMyIAgEgB+MH4gAJuI8c3DAACbjHZB3wAgFYB/YH5QIBIAftB+YCASAH7AfnAgEgB+kH6AAJur7csEgCAUgH6wfqAAm2dKxwIAAJtk4lEqAACb3FIIvkAgEgB+8H7gAJvbHTSOwCASAH8wfwAgN9aAfyB/EAB6/sZooAB6+RDPICA3ogB/UH9AAHsZnh6wAHsZ583QIBIAf8B/cCASAH+Qf4AAm9oH79pAIBIAf7B/oACbqucNAoAAm7c6awyAIBIAgEB/0CASAH/wf+AAm6NMnVWAIBIAgBCAAACbkVeuPQAgFiCAMIAgAIs+ZaQwAIszai7AIBWAgGCAUACbmffMmwAAm44j4NMAEU/wD0pBP0vPLICwgIAgEgCAsICQHq8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44WIYAQ9HhvpSCYAtMH1DAB+wCRMuIBs+ZbgyWhyEA0gED0Q4rmMcgSyx8Tyz/L//QAye1UCAoANCCAQPSWb6UyURCUMFMDud4gkzM2AZIyMOKzAgFICA8IDAIBIAgOCA0AQb5fl2omhpj5jpn+n/mPoCaKkQQCB6BzfQmMktv8ld0fFAAXvZznaiaGmvmOuF/8AATQMA=="; + + let account = Boc::decode_base64(account_base64)?; + + let state = AccountState::load_from(&mut account.as_slice()?)?; + + let data = match state { + AccountState::Active(state_init) => state_init.data.unwrap(), + _ => anyhow::bail!("ACCOUNT NOT ACTIVE"), + }; + + let init_data = InitData::try_from(&data).expect("init data failed"); + + println!("{:?}", init_data.data.values().count()); + + Ok(()) + } + + #[test] + fn test_init_data() { + let p = hex::decode("b76bf868d742e291c9f1a9a1a47dc79728456d5b74b07471dded4bc8f06d5d8a") + .unwrap(); + let public_key = VerifyingKey::from_bytes(&p.try_into().unwrap()).unwrap(); + let init_data = InitData::from_key(&public_key).with_wallet_id(WALLET_ID); + let gifts = vec![Gift { + amount: 0, + state_init: None, + body: None, + flags: 0, + bounce: false, + destination: StdAddr::default(), + }]; + //let gifts = vec![]; + let (hash, payload) = init_data.make_transfer_payload(gifts, 0).unwrap(); + println!("{hash:?} {}", Boc::encode_base64(payload.build().unwrap())); + } +} diff --git a/src/utils/ton_wallet/mod.rs b/src/utils/ton_wallet/mod.rs new file mode 100644 index 0000000..7e7a7d9 --- /dev/null +++ b/src/utils/ton_wallet/mod.rs @@ -0,0 +1,302 @@ +use std::num::NonZeroU8; +use std::str::FromStr; + +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use serde::{Deserialize, Serialize}; + +use tycho_types::abi::FromAbi; +use tycho_types::cell::{Cell, HashBytes}; +use tycho_types::models::{StateInit, StdAddr}; + +use crate::utils::serde_string; +use crate::utils::wallets; + +pub use self::multisig::MultisigType; + +pub mod ever_wallet; +pub mod highload_wallet_v2; +pub mod multisig; +pub mod wallet_v3; +pub mod wallet_v3v4; +pub mod wallet_v5r1; + +pub const DEFAULT_WORKCHAIN: i8 = 0; + +pub const WALLET_TYPES_BY_POPULARITY: [WalletType; 10] = [ + WalletType::Multisig(MultisigType::SafeMultisigWallet), + WalletType::Multisig(MultisigType::SurfWallet), + WalletType::WalletV3, + WalletType::EverWallet, + WalletType::Multisig(MultisigType::Multisig2_1), + WalletType::Multisig(MultisigType::Multisig2), + WalletType::Multisig(MultisigType::SetcodeMultisigWallet), + WalletType::Multisig(MultisigType::SafeMultisigWallet24h), + WalletType::Multisig(MultisigType::BridgeMultisigWallet), + WalletType::HighloadWalletV2, +]; + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub struct TonWalletDetails { + pub requires_separate_deploy: bool, + #[serde(with = "serde_string")] + pub min_amount: u64, + pub max_messages: usize, + pub supports_payload: bool, + pub supports_state_init: bool, + pub supports_multiple_owners: bool, + pub supports_code_update: bool, + pub expiration_time: u32, + pub required_confirmations: Option, +} + +/// Message info +#[derive(Clone)] +pub struct Gift { + pub flags: u8, + pub bounce: bool, + pub destination: StdAddr, + pub amount: u128, + pub body: Option, + pub state_init: Option, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, Serialize, Deserialize)] +pub enum WalletType { + Multisig(MultisigType), + WalletV3, + WalletV3R1, + WalletV3R2, + WalletV4R1, + WalletV4R2, + WalletV5R1, + HighloadWalletV2, + EverWallet, +} + +impl WalletType { + pub fn details(&self) -> TonWalletDetails { + match self { + Self::Multisig(multisig_type) => multisig::ton_wallet_details(*multisig_type), + Self::WalletV3 => wallet_v3::DETAILS, + Self::WalletV5R1 => wallet_v5r1::DETAILS, + Self::HighloadWalletV2 => highload_wallet_v2::DETAILS, + Self::EverWallet => ever_wallet::DETAILS, + Self::WalletV3R1 | Self::WalletV3R2 | Self::WalletV4R1 | Self::WalletV4R2 => { + wallet_v3v4::DETAILS + } + } + } + + pub fn possible_updates(&self) -> &'static [Self] { + const MULTISIG2_UPDATES: &[WalletType] = &[WalletType::Multisig(MultisigType::Multisig2_1)]; + + match self { + Self::Multisig(MultisigType::Multisig2) => MULTISIG2_UPDATES, + _ => &[], + } + } + + pub fn code_hash(&self) -> &[u8; 32] { + match self { + Self::Multisig(multisig_type) => multisig_type.code_hash(), + Self::WalletV3 => wallet_v3::CODE_HASH, + Self::WalletV3R1 => wallet_v3v4::CODE_HASH_V3_R1, + Self::WalletV3R2 => wallet_v3v4::CODE_HASH_V3_R2, + Self::WalletV4R1 => wallet_v3v4::CODE_HASH_V4_R1, + Self::WalletV4R2 => wallet_v3v4::CODE_HASH_V4_R2, + Self::WalletV5R1 => wallet_v5r1::CODE_HASH, + Self::HighloadWalletV2 => highload_wallet_v2::CODE_HASH, + Self::EverWallet => ever_wallet::CODE_HASH, + } + } + + pub fn code(&self) -> Cell { + match self { + Self::Multisig(multisig_type) => multisig_type.code().unwrap(), + Self::WalletV3 => wallets::code::wallet_v3(), + Self::WalletV3R1 => wallets::code::wallet_v3r1(), + Self::WalletV3R2 => wallets::code::wallet_v3r2(), + Self::WalletV4R1 => wallets::code::wallet_v4r1(), + Self::WalletV4R2 => wallets::code::wallet_v4r2(), + Self::WalletV5R1 => wallets::code::wallet_v5r1(), + Self::HighloadWalletV2 => wallets::code::highload_wallet_v2(), + Self::EverWallet => wallets::code::ever_wallet(), + } + } +} + +impl FromStr for WalletType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "WalletV3" => Self::WalletV3, + "WalletV3R1" => Self::WalletV3R1, + "WalletV3R2" => Self::WalletV3R2, + "WalletV4R1" => Self::WalletV4R1, + "WalletV4R2" => Self::WalletV4R2, + "WalletV5R1" => Self::WalletV5R1, + "HighloadWalletV2" => Self::HighloadWalletV2, + "EverWallet" => Self::EverWallet, + s => Self::Multisig(MultisigType::from_str(s)?), + }) + } +} + +impl TryInto for WalletType { + type Error = anyhow::Error; + + fn try_into(self) -> Result { + let res = match self { + WalletType::WalletV3 => 0, + WalletType::EverWallet => 1, + WalletType::Multisig(MultisigType::SafeMultisigWallet) => 2, + WalletType::Multisig(MultisigType::SafeMultisigWallet24h) => 3, + WalletType::Multisig(MultisigType::SetcodeMultisigWallet) => 4, + WalletType::Multisig(MultisigType::BridgeMultisigWallet) => 5, + WalletType::Multisig(MultisigType::SurfWallet) => 6, + WalletType::Multisig(MultisigType::Multisig2) => 7, + WalletType::Multisig(MultisigType::Multisig2_1) => 8, + WalletType::WalletV4R1 => 9, + WalletType::WalletV4R2 => 10, + WalletType::WalletV5R1 => 11, + _ => anyhow::bail!("Unimplemented wallet type"), + }; + + Ok(res) + } +} + +impl std::fmt::Display for WalletType { + fn fmt(&self, f: &'_ mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Multisig(multisig_type) => multisig_type.fmt(f), + Self::WalletV3 => f.write_str("WalletV3"), + Self::WalletV3R1 => f.write_str("WalletV3R1"), + Self::WalletV3R2 => f.write_str("WalletV3R2"), + Self::WalletV4R1 => f.write_str("WalletV4R1"), + Self::WalletV4R2 => f.write_str("WalletV4R2"), + Self::WalletV5R1 => f.write_str("WalletV5R1"), + Self::HighloadWalletV2 => f.write_str("HighloadWalletV2"), + Self::EverWallet => f.write_str("EverWallet"), + } + } +} + +pub fn compute_address( + public_key: &VerifyingKey, + wallet_type: WalletType, + workchain_id: i8, +) -> Result { + match wallet_type { + WalletType::Multisig(multisig_type) => { + multisig::compute_contract_address(public_key, multisig_type, workchain_id) + } + WalletType::WalletV3 => wallet_v3::compute_contract_address(public_key, workchain_id), + WalletType::WalletV5R1 => wallet_v5r1::compute_contract_address(public_key, workchain_id), + WalletType::EverWallet => ever_wallet::compute_contract_address(public_key, workchain_id), + WalletType::HighloadWalletV2 => { + highload_wallet_v2::compute_contract_address(public_key, workchain_id) + } + WalletType::WalletV3R1 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V3R1, + ), + WalletType::WalletV3R2 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V3R2, + ), + WalletType::WalletV4R1 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V4R1, + ), + WalletType::WalletV4R2 => wallet_v3v4::compute_contract_address( + public_key, + workchain_id, + wallet_v3v4::WalletVersion::V4R2, + ), + } +} + +#[derive(Debug, Clone, Eq, PartialEq, FromAbi)] +pub struct MultisigPendingTransaction { + pub id: u64, + pub confirmations: Vec, + pub signs_required: u8, + pub signs_received: u8, + pub creator: HashBytes, + pub index: u8, + pub dest: StdAddr, + pub value: u128, + pub send_flags: u16, + pub payload: Cell, + pub bounce: bool, +} + +#[derive(Debug, Clone, Eq, PartialEq, FromAbi)] +pub struct MultisigPendingUpdate { + pub id: u64, + pub confirmations: Vec, + pub signs_received: u8, + pub creator: HashBytes, + pub index: u8, + pub new_code_hash: Option, + pub new_custodians: Option>, + pub new_req_confirms: Option, + pub new_lifetime: Option, +} + +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub enum MessageFlags { + #[default] + Normal, + AllBalance, + AllBalanceDeleteNetworkAccount, +} + +impl TryFrom for MessageFlags { + type Error = MessageFlagsError; + + fn try_from(value: u8) -> Result { + match value { + 3 => Ok(MessageFlags::Normal), + 128 => Ok(MessageFlags::AllBalance), + 160 => Ok(MessageFlags::AllBalanceDeleteNetworkAccount), + _ => Err(MessageFlagsError::UnknownMessageFlags), + } + } +} + +impl From for u8 { + fn from(value: MessageFlags) -> u8 { + match value { + MessageFlags::Normal => 3, + MessageFlags::AllBalance => 128, + MessageFlags::AllBalanceDeleteNetworkAccount => 128 + 32, + } + } +} + +#[derive(thiserror::Error, Debug, Copy, Clone)] +pub enum MessageFlagsError { + #[error("Unknown message flags combination")] + UnknownMessageFlags, +} + +#[derive(Debug, Clone, Eq, PartialEq)] +pub struct PendingTransaction { + /// External message hash + pub message_hash: HashBytes, + /// Incoming message source + pub src: Option, + /// Last known lt at the time the message was sent + pub latest_lt: u64, + /// Message broadcast timestamp (adjusted) + pub created_at: u32, + /// Expiration timestamp (adjusted) + pub expire_at: u32, +} diff --git a/src/utils/ton_wallet/multisig.rs b/src/utils/ton_wallet/multisig.rs new file mode 100644 index 0000000..0c3217d --- /dev/null +++ b/src/utils/ton_wallet/multisig.rs @@ -0,0 +1,855 @@ +use std::borrow::Cow; +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use nekoton_core::contracts::blockchain_context::BlockchainContext; +use nekoton_core::contracts::function_ext::ExecutionOutput; +use nekoton_core::contracts::function_ext::FunctionExt; +use tycho_types::{ + abi::{AbiValue, FromAbi, Function, IntoAbi, NamedAbiValue, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, CellDataBuilder, HashBytes, Load}, + dict::RawDict, + models::{Account, StateInit, StdAddr}, +}; + +use crate::utils::{ + ton_wallet::{MessageFlags, MultisigPendingTransaction, MultisigPendingUpdate}, + IntoAbiPlain, +}; + +use super::{Gift, TonWalletDetails}; + +#[derive(Copy, Clone, Debug)] +pub struct DeployParams<'a> { + pub owners: &'a [VerifyingKey], + pub req_confirms: u8, + pub expiration_time: Option, +} + +impl<'a> DeployParams<'a> { + pub fn single_custodian(pubkey: &'a VerifyingKey) -> Self { + Self { + owners: std::slice::from_ref(pubkey), + req_confirms: 1, + expiration_time: None, + } + } +} + +pub fn prepare_deploy( + public_key: &VerifyingKey, + multisig_type: MultisigType, + workchain: i8, + expire_at: u32, + params: DeployParams<'_>, +) -> Result { + let state_init = prepare_state_init(public_key, multisig_type)?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + + let dst = StdAddr::new(workchain, *hash); + + let owners = params + .owners + .iter() + .map(|public_key| HashBytes(public_key.to_bytes())) + .collect::>(); + + let is_new_multisig = multisig_type.is_multisig2(); + let function = if is_new_multisig { + crate::utils::wallets::multisig2::constructor() + } else if params.expiration_time.is_none() { + crate::utils::wallets::multisig::constructor() + } else { + return Err(MultisigError::CustomExpirationTimeNotSupported.into()); + }; + + let mut abi_values = vec![ + owners.as_abi().named("owners"), + params.req_confirms.as_abi().named("reqConfirms"), + ]; + if is_new_multisig { + abi_values.push( + params + .expiration_time + .unwrap_or(DEFAULT_LIFETIME) + .as_abi() + .named("lifetime"), + ); + } + + let mut unsigned_message = function + .encode_external(&abi_values) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&dst)?; + + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) +} + +pub fn prepare_confirm_transaction( + multisig_type: MultisigType, + public_key: &VerifyingKey, + address: StdAddr, + transaction_id: u64, + expire_at: u32, +) -> Result { + let function = if multisig_type.is_multisig2() { + crate::utils::wallets::multisig2::confirm_transaction() + } else { + crate::utils::wallets::multisig::confirm_transaction() + }; + + make_ext_message( + public_key, + address, + expire_at, + function, + vec![transaction_id.as_abi().named("transactionId")], + ) +} + +pub fn prepare_transfer( + multisig_type: MultisigType, + public_key: &VerifyingKey, + has_multiple_owners: bool, + address: StdAddr, + gift: Gift, + expire_at: u32, +) -> Result { + let is_new_multisig = multisig_type.is_multisig2(); + + let (function, input) = if has_multiple_owners || is_new_multisig && gift.state_init.is_some() { + let all_balance = match MessageFlags::try_from(gift.flags) { + Ok(MessageFlags::Normal) => false, + Ok(MessageFlags::AllBalance) => true, + _ => return Err(MultisigError::UnsupportedFlagsSet.into()), + }; + + let function = if is_new_multisig { + crate::utils::wallets::multisig2::submit_transaction() + } else { + crate::utils::wallets::multisig::submit_transaction() + }; + + let mut named_abi_values = vec![ + AbiValue::address(gift.destination).named("destination"), + AbiValue::uint(128, gift.amount).named("amount"), + AbiValue::Bool(gift.bounce).named("bounce"), + AbiValue::uint(8, all_balance).named("flags"), + AbiValue::Cell(gift.body.unwrap_or_default()).named("body"), + ]; + + if is_new_multisig { + named_abi_values.push( + gift.state_init + .map(|state_init| CellBuilder::build_from(&state_init)) + .transpose()? + .as_abi() + .named("stateInit"), + ); + } + (function, named_abi_values) + } else { + let function = if is_new_multisig { + crate::utils::wallets::multisig2::send_transaction() + } else { + crate::utils::wallets::multisig::send_transaction() + }; + let named_abi_values = vec![ + AbiValue::address(gift.destination).named("destination"), + AbiValue::uint(128, gift.amount).named("amount"), + AbiValue::Bool(gift.bounce).named("bounce"), + AbiValue::uint(8, gift.flags).named("flags"), + AbiValue::Cell(gift.body.unwrap_or_default()).named("body"), + ]; + (function, named_abi_values) + }; + + make_ext_message(public_key, address, expire_at, function, input) +} + +pub fn prepare_code_update( + multisig_type: MultisigType, + public_key: &VerifyingKey, + address: StdAddr, + new_code_hash: &[u8; 32], + expire_at: u32, +) -> Result { + use crate::utils::wallets::multisig2; + + if !multisig_type.is_multisig2() { + return Err(MultisigError::UnsupportedUpdate.into()); + } + + make_ext_message( + public_key, + address, + expire_at, + multisig2::submit_update(), + multisig2::SubmitUpdateParams { + code_hash: Some(HashBytes::from(*new_code_hash)), + owners: None, + req_confirms: None, + lifetime: None, + } + .into_abi_plain(), + ) +} + +pub fn prepare_confirm_update( + multisig_type: MultisigType, + public_key: &VerifyingKey, + address: StdAddr, + update_id: u64, + expire_at: u32, +) -> Result { + use crate::utils::wallets::multisig2; + + if !multisig_type.is_multisig2() { + return Err(MultisigError::UnsupportedUpdate.into()); + } + + make_ext_message( + public_key, + address, + expire_at, + multisig2::confirm_update(), + multisig2::ConfirmUpdateParams { update_id }.into_abi_plain(), + ) +} + +pub fn prepare_execute_update( + multisig_type: MultisigType, + public_key: &VerifyingKey, + update_id: u64, + code: Option, + address: StdAddr, + expire_at: u32, +) -> Result { + use crate::utils::wallets::multisig2; + + if !multisig_type.is_multisig2() { + return Err(MultisigError::UnsupportedUpdate.into()); + } + + make_ext_message( + public_key, + address, + expire_at, + multisig2::execute_update(), + multisig2::ExecuteUpdateParams { update_id, code }.into_abi_plain(), + ) +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize)] +pub enum MultisigType { + SafeMultisigWallet, + SafeMultisigWallet24h, + SetcodeMultisigWallet, + SetcodeMultisigWallet24h, + BridgeMultisigWallet, + SurfWallet, + Multisig2, + Multisig2_1, +} + +impl MultisigType { + #[inline(always)] + pub fn as_str(&self) -> &'static str { + match self { + Self::SafeMultisigWallet => "SafeMultisigWallet", + Self::SafeMultisigWallet24h => "SafeMultisigWallet24h", + Self::SetcodeMultisigWallet => "SetcodeMultisigWallet", + Self::SetcodeMultisigWallet24h => "SetcodeMultisigWallet24h", + Self::BridgeMultisigWallet => "BridgeMultisigWallet", + Self::SurfWallet => "SurfWallet", + Self::Multisig2 => "Multisig2", + Self::Multisig2_1 => "Multisig2_1", + } + } +} + +impl std::str::FromStr for MultisigType { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + Ok(match s { + "SafeMultisigWallet" => Self::SafeMultisigWallet, + "SafeMultisigWallet24h" => Self::SafeMultisigWallet24h, + "SetcodeMultisigWallet" => Self::SetcodeMultisigWallet, + "SetcodeMultisigWallet24h" => Self::SetcodeMultisigWallet24h, + "BridgeMultisigWallet" => Self::BridgeMultisigWallet, + "SurfWallet" => Self::SurfWallet, + "Multisig2" => Self::Multisig2, + "Multisig2_1" => Self::Multisig2_1, + _ => return Err(anyhow::anyhow!("Invalid multisig type")), + }) + } +} + +impl std::fmt::Display for MultisigType { + fn fmt(&self, f: &'_ mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) + } +} + +impl MultisigType { + pub fn is_multisig2(self) -> bool { + matches!(self, Self::Multisig2 | Self::Multisig2_1) + } + + pub fn is_updatable(&self) -> bool { + matches!( + self, + Self::SetcodeMultisigWallet + | Self::SetcodeMultisigWallet24h + | Self::SurfWallet + | Self::Multisig2 + | Self::Multisig2_1 + ) + } + + pub fn state_init(&self) -> Result { + use crate::utils::wallets; + + let state_init = match self { + MultisigType::SafeMultisigWallet => wallets::code::safe_multisig_wallet(), + MultisigType::SafeMultisigWallet24h => wallets::code::safe_multisig_wallet_24h(), + MultisigType::SetcodeMultisigWallet => wallets::code::setcode_multisig_wallet(), + MultisigType::SetcodeMultisigWallet24h => wallets::code::setcode_multisig_wallet_24h(), + MultisigType::BridgeMultisigWallet => wallets::code::bridge_multisig_wallet(), + MultisigType::SurfWallet => wallets::code::surf_wallet(), + MultisigType::Multisig2 => wallets::code::multisig2(), + MultisigType::Multisig2_1 => wallets::code::multisig2_1(), + }; + StateInit::load_from(&mut state_init.as_slice()?).map_err(Into::into) + } + + pub fn code_hash(&self) -> &[u8; 32] { + match self { + Self::SafeMultisigWallet => SAFE_MULTISIG_WALLET_HASH, + Self::SafeMultisigWallet24h => SAFE_MULTISIG_WALLET_24H_HASH, + Self::SetcodeMultisigWallet => SETCODE_MULTISIG_WALLET_HASH, + Self::SetcodeMultisigWallet24h => SETCODE_MULTISIG_WALLET_24H_HASH, + Self::BridgeMultisigWallet => BRIDGE_MULTISIG_WALLET_HASH, + Self::SurfWallet => SURF_WALLET_HASH, + Self::Multisig2 => MULTISIG2_HASH, + Self::Multisig2_1 => MULTISIG2_1_HASH, + } + } + + pub fn code(&self) -> Result { + self.state_init()? + .code + .ok_or(UnpackerError::InvalidAbi.into()) + } +} + +static SAFE_MULTISIG_WALLET_HASH: &[u8; 32] = &[ + 0x80, 0xd6, 0xc4, 0x7c, 0x4a, 0x25, 0x54, 0x3c, 0x9b, 0x39, 0x7b, 0x71, 0x71, 0x6f, 0x3f, 0xae, + 0x1e, 0x2c, 0x5d, 0x24, 0x71, 0x74, 0xc5, 0x2e, 0x2c, 0x19, 0xbd, 0x89, 0x64, 0x42, 0xb1, 0x05, +]; +static SAFE_MULTISIG_WALLET_24H_HASH: &[u8; 32] = &[ + 0x7d, 0x09, 0x96, 0x94, 0x34, 0x06, 0xf7, 0xd6, 0x2a, 0x4f, 0xf2, 0x91, 0xb1, 0x22, 0x8b, 0xf0, + 0x6e, 0xbd, 0x3e, 0x04, 0x8b, 0x58, 0x43, 0x6c, 0x5b, 0x70, 0xfb, 0x77, 0xff, 0x8b, 0x4b, 0xf2, +]; +static SETCODE_MULTISIG_WALLET_HASH: &[u8; 32] = &[ + 0xe2, 0xb6, 0x0b, 0x6b, 0x60, 0x2c, 0x10, 0xce, 0xd7, 0xea, 0x8e, 0xde, 0x4b, 0xdf, 0x96, 0x34, + 0x2c, 0x97, 0x57, 0x0a, 0x37, 0x98, 0x06, 0x6f, 0x3f, 0xb5, 0x0a, 0x4b, 0x2b, 0x27, 0xa2, 0x08, +]; +static SETCODE_MULTISIG_WALLET_24H_HASH: &[u8; 32] = &[ + 0xa4, 0x91, 0x80, 0x4c, 0xa5, 0x5d, 0xd5, 0xb2, 0x8c, 0xff, 0xdf, 0xf4, 0x8c, 0xb3, 0x41, 0x42, + 0x93, 0x09, 0x99, 0x62, 0x1a, 0x54, 0xac, 0xee, 0x6b, 0xe8, 0x3c, 0x34, 0x20, 0x51, 0xd8, 0x84, +]; +static BRIDGE_MULTISIG_WALLET_HASH: &[u8; 32] = &[ + 0xf3, 0xa0, 0x7a, 0xe8, 0x4f, 0xc3, 0x43, 0x25, 0x9d, 0x7f, 0xa4, 0x84, 0x7b, 0x86, 0x33, 0x5b, + 0x3f, 0xdc, 0xfc, 0x8b, 0x31, 0xf1, 0xba, 0x4b, 0x7a, 0x94, 0x99, 0xd5, 0x53, 0x0f, 0x0b, 0x18, +]; +static SURF_WALLET_HASH: &[u8; 32] = &[ + 0x20, 0x7d, 0xc5, 0x60, 0xc5, 0x95, 0x6d, 0xe1, 0xa2, 0xc1, 0x47, 0x93, 0x56, 0xf8, 0xf3, 0xee, + 0x70, 0xa5, 0x97, 0x67, 0xdb, 0x2b, 0xf4, 0x78, 0x8b, 0x1d, 0x61, 0xad, 0x42, 0xcd, 0xad, 0x82, +]; +static MULTISIG2_HASH: &[u8; 32] = &[ + 0x29, 0xb2, 0x47, 0x76, 0xb3, 0xdf, 0x6a, 0x05, 0xc5, 0xa1, 0xb8, 0xd8, 0xfd, 0x75, 0xcb, 0x72, + 0xa1, 0xd3, 0x3c, 0x0a, 0x44, 0x38, 0x53, 0x32, 0xa8, 0xbf, 0xc2, 0x72, 0x7f, 0xb6, 0x65, 0x90, +]; +static MULTISIG2_1_HASH: &[u8; 32] = &[ + 0xd6, 0x6d, 0x19, 0x87, 0x66, 0xab, 0xdb, 0xe1, 0x25, 0x3f, 0x34, 0x15, 0x82, 0x6c, 0x94, 0x6c, + 0x37, 0x1f, 0x51, 0x12, 0x55, 0x24, 0x08, 0x62, 0x5a, 0xeb, 0x0b, 0x31, 0xe0, 0xef, 0x2d, 0xf3, +]; + +pub fn guess_multisig_type(code_hash: &HashBytes) -> Option { + match code_hash.as_slice() { + s if s == SAFE_MULTISIG_WALLET_HASH => Some(MultisigType::SafeMultisigWallet), + s if s == SAFE_MULTISIG_WALLET_24H_HASH => Some(MultisigType::SafeMultisigWallet24h), + s if s == SETCODE_MULTISIG_WALLET_HASH => Some(MultisigType::SetcodeMultisigWallet), + s if s == BRIDGE_MULTISIG_WALLET_HASH => Some(MultisigType::BridgeMultisigWallet), + s if s == SETCODE_MULTISIG_WALLET_24H_HASH => Some(MultisigType::SetcodeMultisigWallet24h), + s if s == SURF_WALLET_HASH => Some(MultisigType::SurfWallet), + s if s == MULTISIG2_HASH => Some(MultisigType::Multisig2), + s if s == MULTISIG2_1_HASH => Some(MultisigType::Multisig2_1), + _ => None, + } +} + +pub fn compute_contract_address( + public_key: &VerifyingKey, + multisig_type: MultisigType, + workchain_id: i8, +) -> Result { + let state_init = prepare_state_init(public_key, multisig_type)?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) +} + +pub fn ton_wallet_details(multisig_type: MultisigType) -> TonWalletDetails { + TonWalletDetails { + requires_separate_deploy: true, + min_amount: if multisig_type.is_multisig2() { + 0 + } else { + 1000000 // 0.001 EVER + }, + max_messages: 1, + supports_payload: true, + supports_state_init: multisig_type.is_multisig2(), + supports_multiple_owners: true, + supports_code_update: multisig_type.is_updatable(), + expiration_time: match multisig_type { + MultisigType::SafeMultisigWallet + | MultisigType::SetcodeMultisigWallet + | MultisigType::Multisig2 + | MultisigType::Multisig2_1 => 3600, + MultisigType::SurfWallet => 3601, + MultisigType::SafeMultisigWallet24h + | MultisigType::SetcodeMultisigWallet24h + | MultisigType::BridgeMultisigWallet => 86400, + }, + required_confirmations: None, + } +} + +pub fn prepare_state_init( + public_key: &VerifyingKey, + multisig_type: MultisigType, +) -> Result { + let mut state_init = multisig_type.state_init()?; + + let state_init_data = state_init.data.unwrap_or_default(); + + let state_init_data_slice = state_init_data.as_slice()?; + + let cell = state_init_data_slice.get_reference_cloned(0).ok(); + + let mut result = RawDict::<64>::from(cell); + + let mut key_builder = CellDataBuilder::new(); + key_builder.store_u64(0)?; + let cell_builder = CellBuilder::from_raw_data(public_key.as_bytes(), 256)?; + let data_slice = cell_builder.as_data_slice(); + result.set(key_builder.as_data_slice(), data_slice)?; + + // Encode init data as mapping + let cell = CellBuilder::build_from(result)?; + state_init.data = Some(cell); + + Ok(state_init) +} + +fn run_local( + function: &Function, + mut account_stuff: Account, + input: &[NamedAbiValue], + responsible: bool, + context: &mut BlockchainContext, +) -> Result> { + let ExecutionOutput { values, exit_code } = + function.run_local(&mut account_stuff, input, responsible, context)?; + + if exit_code != 0 { + return Err(MultisigError::NonZeroResultCode(exit_code).into()); + } + + Ok(values) +} + +#[derive(Copy, Clone)] +pub struct MultisigParamsPrefix { + pub max_queued_transactions: u8, + pub max_custodian_count: u8, + pub expiration_time: u64, + pub min_value: u128, + pub required_confirms: u8, +} + +impl TryFrom> for MultisigParamsPrefix { + fn try_from(params: Vec) -> std::result::Result { + let mut params_iter = params.into_iter(); + + let Some(AbiValue::Uint(8, max_queued_transactions)) = params_iter.next().map(|v| v.value) + else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(8, max_custodian_count)) = params_iter.next().map(|v| v.value) + else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(64, expiration_time)) = params_iter.next().map(|v| v.value) else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(128, min_value)) = params_iter.next().map(|v| v.value) else { + return Err(MultisigError::InvalidParams.into()); + }; + + let Some(AbiValue::Uint(8, required_confirms)) = params_iter.next().map(|v| v.value) else { + return Err(MultisigError::InvalidParams.into()); + }; + + Ok(MultisigParamsPrefix { + max_queued_transactions: u8::try_from(max_queued_transactions)?, + max_custodian_count: u8::try_from(max_custodian_count)?, + expiration_time: u64::try_from(expiration_time)?, + min_value: u128::try_from(min_value)?, + required_confirms: u8::try_from(required_confirms)?, + }) + } + + type Error = anyhow::Error; +} + +pub fn get_params( + multisig_type: MultisigType, + account: Cow<'_, Account>, + context: &mut BlockchainContext, +) -> Result { + let function = match multisig_type { + MultisigType::Multisig2 | MultisigType::Multisig2_1 => { + crate::utils::wallets::multisig2::get_parameters() + } + MultisigType::SafeMultisigWallet + | MultisigType::SafeMultisigWallet24h + | MultisigType::BridgeMultisigWallet => { + crate::utils::wallets::multisig::safe_multisig::get_parameters() + } + MultisigType::SetcodeMultisigWallet + | MultisigType::SetcodeMultisigWallet24h + | MultisigType::SurfWallet => { + crate::utils::wallets::multisig::set_code_multisig::get_parameters() + } + }; + + let output = run_local(function, account.into_owned(), &[], false, context)?; + MultisigParamsPrefix::try_from(output) +} + +pub fn get_custodians( + multisig_type: MultisigType, + account: Cow<'_, Account>, + context: &mut BlockchainContext, +) -> Result> { + let function = if multisig_type.is_multisig2() { + crate::utils::wallets::multisig2::get_custodians() + } else { + crate::utils::wallets::multisig::get_custodians() + }; + + let output = run_local(function, account.into_owned(), &[], false, context)?; + parse_multisig_contract_custodians(output) +} + +fn parse_multisig_contract_custodians(tokens: Vec) -> Result> { + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + let mut custodians = array + .into_iter() + .map(crate::utils::wallets::multisig::MultisigCustodian::from_abi) + .collect::, _>>()?; + + custodians.sort_by(|a, b| a.index.cmp(&b.index)); + + Ok(custodians.into_iter().map(|item| item.pubkey).collect()) +} + +pub fn find_pending_transaction( + multisig_type: MultisigType, + account: Cow<'_, Account>, + pending_transaction_id: u64, + context: &mut BlockchainContext, +) -> Result { + #[derive(Copy, Clone)] + pub struct MultisigTransactionId { + pub id: u64, + } + + impl TryFrom for MultisigTransactionId { + fn try_from(params: AbiValue) -> std::result::Result { + let AbiValue::Tuple(params) = params else { + return Err(anyhow::anyhow!("Invalid params")); + }; + + let mut params_iter = params.into_iter(); + + let Some(AbiValue::Uint(64, index)) = params_iter.next().map(|v| v.value) else { + return Err(anyhow::anyhow!("Invalid params")); + }; + + Ok(MultisigTransactionId { + id: u64::try_from(index)?, + }) + } + + type Error = anyhow::Error; + } + + let function = if multisig_type.is_multisig2() { + crate::utils::wallets::multisig2::get_transactions() + } else { + crate::utils::wallets::multisig::get_transactions() + }; + + let tokens = run_local(function, account.into_owned(), &[], false, context)?; + + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + for item in array { + let m = MultisigTransactionId::try_from(item)?; + if pending_transaction_id == m.id { + return Ok(true); + } + } + Ok(false) +} + +pub fn find_pending_update( + multisig_type: MultisigType, + account: Cow<'_, Account>, + update_id: u64, + context: &mut BlockchainContext, +) -> Result> { + use crate::utils::wallets::multisig2; + + let function = match multisig_type { + MultisigType::Multisig2 => multisig2::v2_0::get_update_requests(), + MultisigType::Multisig2_1 => multisig2::v2_1::get_update_requests(), + _ => return Ok(None), + }; + + let tokens = run_local(function, account.into_owned(), &[], false, context)?; + + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + for item in array { + let update = multisig2::UpdateTransaction::from_abi(item)?; + if update_id == update.id { + return Ok(Some(UpdatedParams { + new_code_hash: update.new_code_hash, + new_custodians: update.new_custodians, + new_req_confirms: update.new_req_confirms, + new_lifetime: update.new_lifetime, + })); + } + } + + Ok(None) +} + +#[derive(Debug, Clone)] +pub struct UpdatedParams { + pub new_code_hash: Option, + pub new_custodians: Option>, + pub new_req_confirms: Option, + pub new_lifetime: Option, +} + +pub fn get_pending_transactions( + multisig_type: MultisigType, + account: Cow<'_, Account>, + custodians: &[HashBytes], + context: &mut BlockchainContext, +) -> Result> { + let function = if multisig_type.is_multisig2() { + crate::utils::wallets::multisig2::get_transactions() + } else { + crate::utils::wallets::multisig::get_transactions() + }; + run_local(function, account.into_owned(), &[], false, context).and_then(|tokens| { + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + let transactions = array + .into_iter() + .map(|item| { + Ok(extend_pending_transaction( + crate::utils::wallets::multisig::MultisigTransaction::from_abi(item)?, + custodians, + )) + }) + .collect::>>()?; + + Ok(transactions) + }) +} + +pub fn get_pending_updates( + multisig_type: MultisigType, + account: Cow<'_, Account>, + custodians: &[HashBytes], + context: &mut BlockchainContext, +) -> Result> { + use crate::utils::wallets::multisig2; + + let function = match multisig_type { + MultisigType::Multisig2 => multisig2::v2_0::get_update_requests(), + MultisigType::Multisig2_1 => multisig2::v2_1::get_update_requests(), + _ => return Ok(Vec::new()), + }; + + run_local(function, account.into_owned(), &[], false, context).and_then(|tokens| { + let array = match tokens.into_iter().next().map(|v| v.value) { + Some(AbiValue::Array(_, tokens)) => tokens, + _ => return Err(UnpackerError::InvalidAbi.into()), + }; + + let updates = array + .into_iter() + .map(|item| { + Ok(extend_pending_update( + crate::utils::wallets::multisig2::UpdateTransaction::from_abi(item)?, + custodians, + )) + }) + .collect::>>()?; + + Ok(updates) + }) +} + +fn extend_pending_transaction( + tx: crate::utils::wallets::multisig::MultisigTransaction, + custodians: &[HashBytes], +) -> MultisigPendingTransaction { + let confirmations = custodians + .iter() + .enumerate() + .filter(|(i, _)| (0b1 << i) & tx.confirmation_mask != 0) + .map(|(_, item)| *item) + .collect::>(); + + MultisigPendingTransaction { + id: tx.id, + confirmations, + signs_required: tx.signs_required, + signs_received: tx.signs_received, + creator: tx.creator, + index: tx.index, + dest: tx.dest, + value: tx.value, + send_flags: tx.send_flags, + payload: tx.payload, + bounce: tx.bounce, + } +} + +fn extend_pending_update( + tx: crate::utils::wallets::multisig2::UpdateTransaction, + custodians: &[HashBytes], +) -> MultisigPendingUpdate { + let confirmations = custodians + .iter() + .enumerate() + .filter(|(i, _)| (0b1 << i) & tx.confirmations_mask != 0) + .map(|(_, item)| *item) + .collect::>(); + + MultisigPendingUpdate { + id: tx.id, + confirmations, + signs_received: tx.signs, + creator: tx.creator, + index: tx.index, + new_code_hash: tx.new_code_hash, + new_custodians: tx.new_custodians, + new_req_confirms: tx.new_req_confirms, + new_lifetime: tx.new_lifetime, + } +} + +fn make_ext_message( + public_key: &VerifyingKey, + address: StdAddr, + expire_at: u32, + function: &'static Function, + input: Vec, +) -> Result { + let unsigned_message = function + .encode_external(&input) + .with_pubkey(public_key) + .with_expire_at(expire_at) + .build_message(&address)?; + + Ok(unsigned_message) +} + +const DEFAULT_LIFETIME: u32 = 3600; + +#[derive(thiserror::Error, Debug)] +enum MultisigError { + #[error("Non-zero execution result code: {}", .0)] + NonZeroResultCode(i32), + #[error("Unsupported message flags set")] + UnsupportedFlagsSet, + #[error("Custom lifetime is not supported for this contract type")] + CustomExpirationTimeNotSupported, + #[error("Update is not supported or not implemented for this contract type")] + UnsupportedUpdate, + #[error("Invalid params")] + InvalidParams, +} + +pub type UnpackerResult = Result; + +#[derive(thiserror::Error, Debug, Clone, Copy)] +pub enum UnpackerError { + #[error("Invalid ABI")] + InvalidAbi, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_address() { + let key = hex::decode("5ace46d93d8f3932499df9f2bc7ef787385e16965e7797258948febd186de7f6") + .unwrap(); + + let key = key.try_into().unwrap(); + + let key = VerifyingKey::from_bytes(&key).unwrap(); + + assert_eq!( + compute_contract_address(&key, MultisigType::SetcodeMultisigWallet24h, 0) + .unwrap() + .to_string(), + "0:3de70f9212154344a3158768b3fed731fc865ca15948b0d6d0d34daf4c6a7a0a" + ); + } +} diff --git a/src/utils/ton_wallet/wallet_v3.rs b/src/utils/ton_wallet/wallet_v3.rs new file mode 100644 index 0000000..eb168ad --- /dev/null +++ b/src/utils/ton_wallet/wallet_v3.rs @@ -0,0 +1,343 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use tycho_types::{ + cell::{Cell, CellBuilder, HashBytes}, + models::{ + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OwnedMessage, + OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, StateInit, StdAddr, + }, +}; +use tycho_util::time::now_sec; + +use crate::utils::{ + ton_wallet::{Gift, PendingTransaction, TonWalletDetails}, + wallets::code::wallet_v3, +}; + +pub fn prepare_deploy( + public_key: &VerifyingKey, + workchain: i8, + expire_at: u32, +) -> Result { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + let dst = compute_contract_address(public_key, workchain)?; + + let (hash, payload) = init_data.make_transfer_payload(None, expire_at)?; + let message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std(dst), + ..Default::default() + }), + body: Default::default(), + init: Some(init_data.make_state_init()?), + layout: None, + }; + Ok(UnsignedWalletV3Message { + init_data, + gifts: vec![], + payload, + hash, + expire_at, + message, + }) +} + +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { + let init_data = InitData::from_key(public_key).with_wallet_id(WALLET_ID); + init_data.make_state_init() +} + +/// Adjusts seqno if there are some recent pending transactions that have not expired +pub fn estimate_seqno_offset( + current_state: &Account, + pending_transactions: &[PendingTransaction], +) -> u32 { + const SEQNO_ADJUST_INTERVAL: u32 = 30; // seconds + + #[inline] + fn same_lt(lt_from_pending: u64, lt_from_state: u64) -> bool { + // NOTE: `pending.latest_lt` can be exact transaction lt, or + // `storage.last_trans_lt` which is a bit greater + const ALLOWED_LT_DIFF: u64 = 1 + MAX_MESSAGES as u64; + + (lt_from_pending..=lt_from_pending + ALLOWED_LT_DIFF).contains(<_from_state) + } + + if pending_transactions.is_empty() { + return 0; + } + + let now = now_sec(); + let latest_lt = current_state.last_trans_lt; + + let mut seqno_offset = 0; + for pending in pending_transactions.iter().rev() { + // Adjust only for sufficiently new pending transactions. + if now > pending.created_at + SEQNO_ADJUST_INTERVAL { + break; + } + + // Adjust only if account state hasn't changed + if !same_lt(pending.latest_lt, latest_lt) { + break; + } + + if now < pending.expire_at { + seqno_offset += 1; + } + } + + seqno_offset +} + +pub fn prepare_transfer( + public_key: &VerifyingKey, + current_state: &Account, + seqno_offset: u32, + gifts: Vec, + expire_at: u32, +) -> Result { + if gifts.len() > MAX_MESSAGES { + return Err(WalletV3Error::TooManyGifts.into()); + } + + let (mut init_data, with_state_init) = match ¤t_state.state { + AccountState::Active(state_init) => match &state_init.data { + Some(data) => (InitData::try_from(data)?, false), + None => return Err(WalletV3Error::InvalidInitData.into()), + }, + AccountState::Frozen { .. } => return Err(WalletV3Error::AccountIsFrozen.into()), + AccountState::Uninit => ( + InitData::from_key(public_key).with_wallet_id(WALLET_ID), + true, + ), + }; + + init_data.seqno += seqno_offset; + + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at)?; + let mut message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std( + current_state + .address + .as_std() + .ok_or(WalletV3Error::InvalidAddress)? + .clone(), + ), + ..Default::default() + }), + body: Default::default(), + init: None, + layout: None, + }; + + if with_state_init { + let state_init = init_data.make_state_init()?; + message.init = Some(state_init); + } + + Ok(UnsignedWalletV3Message { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }) +} + +pub struct UnsignedWalletV3Message { + init_data: InitData, + gifts: Vec, + payload: CellBuilder, + hash: HashBytes, + expire_at: u32, + message: OwnedMessage, +} + +impl UnsignedWalletV3Message { + pub fn expire_at(&self) -> u32 { + self.expire_at + } + + pub fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + pub fn gifts(&self) -> &[Gift] { + &self.gifts + } + + pub fn message(&self) -> &OwnedMessage { + &self.message + } + + pub fn init_data(&self) -> &InitData { + &self.init_data + } + + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.prepend_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; + + let mut message = self.message.clone(); + message.body = payload.build()?.into(); + + Ok(message) + } +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x84, 0xda, 0xfa, 0x44, 0x9f, 0x98, 0xa6, 0x98, 0x77, 0x89, 0xba, 0x23, 0x23, 0x58, 0x07, 0x2b, + 0xc0, 0xf7, 0x6d, 0xc4, 0x52, 0x40, 0x02, 0xa5, 0xd0, 0x91, 0x8b, 0x9a, 0x75, 0xd2, 0xd5, 0x99, +]; + +pub fn is_wallet_v3(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { + let state_init = InitData::from_key(public_key) + .with_wallet_id(WALLET_ID) + .make_state_init()?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 4; + +/// `WalletV3` init data +#[derive(Clone, Copy)] +pub struct InitData { + pub seqno: u32, + pub wallet_id: u32, + pub public_key: HashBytes, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + &self.public_key.0 + } + + pub fn from_key(key: &VerifyingKey) -> Self { + Self { + seqno: 0, + wallet_id: 0, + public_key: HashBytes(key.to_bytes()), + } + } + + pub fn with_wallet_id(mut self, id: u32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let state_init = self.make_state_init()?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) + } + + pub fn make_state_init(&self) -> Result { + Ok(StateInit { + code: Some(wallet_v3()), + data: Some(self.serialize()?), + ..Default::default() + }) + } + + pub fn serialize(&self) -> Result { + let mut builder = CellBuilder::new(); + builder.store_u32(self.seqno)?; + builder.store_u32(self.wallet_id)?; + builder.store_u256(&self.public_key)?; + let data = builder.build()?; + Ok(data) + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + ) -> Result<(HashBytes, CellBuilder)> { + // insert prefix + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_u32(self.seqno)?; + + // create internal message + for gift in gifts { + let internal_message = OwnedRelaxedMessage { + info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { + ihr_disabled: true, + bounce: gift.bounce, + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), + ..Default::default() + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).into(), + layout: None, + }; + // append it to the body + builder.store_u8(gift.flags)?; + builder.store_reference(CellBuilder::build_from(internal_message)?)?; + } + + let payload = builder.clone().build()?; + let hash = payload.repr_hash(); + + Ok((*hash, builder)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut slice = data.as_slice()?; + let seqno = slice.load_u32()?; + let wallet_id = slice.load_u32()?; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + + Ok(Self { + seqno, + wallet_id, + public_key, + }) + } +} + +const WALLET_ID: u32 = 0x4BA92D8A; + +#[derive(thiserror::Error, Debug)] +enum WalletV3Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, + #[error("Account address is not valid")] + InvalidAddress, +} diff --git a/src/utils/ton_wallet/wallet_v3v4.rs b/src/utils/ton_wallet/wallet_v3v4.rs new file mode 100644 index 0000000..f880c1e --- /dev/null +++ b/src/utils/ton_wallet/wallet_v3v4.rs @@ -0,0 +1,374 @@ +use std::convert::TryFrom; + +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use tycho_types::{ + abi::{AbiVersion, UnsignedBody, UnsignedExternalMessage}, + cell::{Cell, CellBuilder, HashBytes}, + models::{ + Account, AccountState, CurrencyCollection, IntAddr, IntMsgInfo, Message, MsgInfo, + StateInit, StdAddr, + }, +}; + +use crate::utils::{ + ton_wallet::{Gift, TonWalletDetails}, + wallets::{self}, +}; + +pub fn prepare_deploy( + public_key: &VerifyingKey, + workchain: i8, + expire_at: u32, + version: WalletVersion, +) -> Result { + let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); + let dst = compute_contract_address(public_key, workchain, version)?; + + let (hash, payload) = init_data.make_transfer_payload(None, expire_at, version)?; + let unsigned_body = UnsignedBody { + payload, + hash, + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst(dst); + let state_init = init_data.make_state_init(version)?; + unsigned_message.set_state_init(Some(state_init)); + Ok(unsigned_message) +} + +pub fn prepare_state_init(public_key: &VerifyingKey, version: WalletVersion) -> Result { + let init_data = InitData::from_key(public_key).with_subwallet_id(WALLET_ID); + init_data.make_state_init(version) +} + +pub fn prepare_transfer( + public_key: &VerifyingKey, + current_state: &Account, + seqno_offset: u32, + gifts: Vec, + expire_at: u32, + version: WalletVersion, +) -> Result { + if gifts.len() > MAX_MESSAGES { + return Err(WalletV4Error::TooManyGifts.into()); + } + + let (mut init_data, with_state_init) = match ¤t_state.state { + AccountState::Active(state_init) => match &state_init.data { + Some(data) => (InitData::try_from(data)?, false), + None => return Err(WalletV4Error::InvalidInitData.into()), + }, + AccountState::Frozen { .. } => return Err(WalletV4Error::AccountIsFrozen.into()), + AccountState::Uninit => ( + InitData::from_key(public_key).with_subwallet_id(WALLET_ID), + true, + ), + }; + + init_data.seqno += seqno_offset; + + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at, version)?; + + let unsigned_body = UnsignedBody { + payload, + hash, + abi_version: AbiVersion::V2_3, + expire_at, + }; + let mut unsigned_message = unsigned_body.with_dst( + current_state + .address + .as_std() + .ok_or(WalletV4Error::InvalidAddress)? + .clone(), + ); + if with_state_init { + let state_init = init_data.make_state_init(version)?; + unsigned_message.set_state_init(Some(state_init)); + } + + Ok(unsigned_message) +} + +#[allow(unused)] +struct UnsignedWallet { + init_data: InitData, + gifts: Vec, + payload: Cell, + hash: HashBytes, + expire_at: u32, + message: UnsignedExternalMessage, + version: WalletVersion, +} + +pub static CODE_HASH_V3_R1: &[u8; 32] = &[ + 0xB6, 0x10, 0x41, 0xA5, 0x8A, 0x79, 0x80, 0xB9, 0x46, 0xE8, 0xFB, 0x9E, 0x19, 0x8E, 0x3C, 0x90, + 0x4D, 0x24, 0x79, 0x9F, 0xFA, 0x36, 0x57, 0x4E, 0xA4, 0x25, 0x1C, 0x41, 0xA5, 0x66, 0xF5, 0x81, +]; + +pub static CODE_HASH_V3_R2: &[u8; 32] = &[ + 0x84, 0xDA, 0xFA, 0x44, 0x9F, 0x98, 0xA6, 0x98, 0x77, 0x89, 0xBA, 0x23, 0x23, 0x58, 0x07, 0x2B, + 0xC0, 0xF7, 0x6D, 0xC4, 0x52, 0x40, 0x02, 0xA5, 0xD0, 0x91, 0x8B, 0x9A, 0x75, 0xD2, 0xD5, 0x99, +]; + +pub static CODE_HASH_V4_R1: &[u8; 32] = &[ + 0x64, 0xDD, 0x54, 0x80, 0x55, 0x22, 0xC5, 0xBE, 0x8A, 0x9D, 0xB5, 0x9C, 0xEA, 0x01, 0x05, 0xCC, + 0xF0, 0xD0, 0x87, 0x86, 0xCA, 0x79, 0xBE, 0xB8, 0xCB, 0x79, 0xE8, 0x80, 0xA8, 0xD7, 0x32, 0x2D, +]; + +pub static CODE_HASH_V4_R2: &[u8; 32] = &[ + 0xFE, 0xB5, 0xFF, 0x68, 0x20, 0xE2, 0xFF, 0x0D, 0x94, 0x83, 0xE7, 0xE0, 0xD6, 0x2C, 0x81, 0x7D, + 0x84, 0x67, 0x89, 0xFB, 0x4A, 0xE5, 0x80, 0xC8, 0x78, 0x86, 0x6D, 0x95, 0x9D, 0xAB, 0xD5, 0xC0, +]; + +pub fn is_wallet_v3r1(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH_V3_R1 +} + +pub fn is_wallet_v3r2(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH_V3_R2 +} + +pub fn is_wallet_v4r1(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH_V4_R1 +} + +pub fn is_wallet_v4r2(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH_V4_R2 +} + +pub fn compute_contract_address( + public_key: &VerifyingKey, + workchain_id: i8, + version: WalletVersion, +) -> Result { + InitData::from_key(public_key) + .with_subwallet_id(WALLET_ID) + .compute_addr(workchain_id, version) +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 4; + +/// `Default Wallet` init data +#[derive(Clone, Copy)] +pub struct InitData { + pub seqno: u32, + pub wallet_id: i32, + pub public_key: HashBytes, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + &self.public_key.0 + } + + pub fn from_key(key: &VerifyingKey) -> Self { + Self { + seqno: 0, + wallet_id: 0, + public_key: HashBytes::from_slice(key.as_bytes()), + } + } + + pub fn with_subwallet_id(mut self, id: i32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8, version: WalletVersion) -> Result { + let state_init = self.make_state_init(version)?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) + } + + pub fn make_state_init(&self, version: WalletVersion) -> Result { + let code = match version { + WalletVersion::V3R1 => wallets::code::wallet_v3r1(), + WalletVersion::V3R2 => wallets::code::wallet_v3r2(), + WalletVersion::V4R1 => wallets::code::wallet_v4r1(), + WalletVersion::V4R2 => wallets::code::wallet_v4r2(), + }; + + Ok(StateInit { + code: Some(code), + data: Some(self.serialize(version)?), + ..Default::default() + }) + } + + pub fn serialize(&self, version: WalletVersion) -> Result { + let mut builder = CellBuilder::new(); + builder.store_u32(self.seqno)?; + builder.store_u32(self.wallet_id as _)?; + builder.store_u256(&self.public_key)?; + + if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { + // empty plugin dict + builder.store_bit_zero()?; + } + + let data = builder.build()?; + Ok(data) + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + version: WalletVersion, + ) -> Result<(HashBytes, Cell)> { + // insert prefix + let mut builder = CellBuilder::new(); + builder.store_u32(self.wallet_id as _)?; + builder.store_u32(expire_at)?; + builder.store_u32(self.seqno)?; + + // Opcode + if matches!(version, WalletVersion::V4R1 | WalletVersion::V4R2) { + builder.store_u8(0)?; + } + + // create internal message + for gift in gifts { + let body = gift.body.unwrap_or(Default::default()); + let internal_message = Message { + info: MsgInfo::Int(IntMsgInfo { + ihr_disabled: true, + bounce: gift.bounce, + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), + ..Default::default() + }), + init: gift.state_init, + body: body.as_slice()?, + layout: None, + }; + // append it to the body + builder.store_u8(gift.flags)?; + builder.store_reference(CellBuilder::build_from(internal_message)?)?; + } + + let payload = builder.build()?; + let hash = payload.repr_hash(); + + Ok((*hash, payload)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut slice = data.as_slice()?; + + let seqno = slice.load_u32()?; + let wallet_id = slice.load_u32()? as i32; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 32)?; + let public_key = HashBytes::from_slice(&buffer); + + Ok(Self { + seqno, + wallet_id, + public_key, + }) + } +} + +const WALLET_ID: i32 = 0x29A9A317; + +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum WalletVersion { + V3R1, + V3R2, + V4R1, + V4R2, +} + +#[derive(thiserror::Error, Debug)] +enum WalletV4Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, + #[error("Account address is not valid")] + InvalidAddress, +} + +#[cfg(test)] +mod tests { + + use std::str::FromStr; + + use tycho_types::{ + boc::Boc, + cell::{HashBytes, Load}, + models::StateInit, + }; + + use crate::utils::{ + ton_wallet::wallet_v3v4::{ + is_wallet_v4r1, is_wallet_v4r2, InitData, WalletVersion, WALLET_ID, + }, + wallets, + }; + + #[test] + fn code_hash_v4r1() -> anyhow::Result<()> { + let code_cell = wallets::code::wallet_v4r1(); + + let is_wallet_v4r1 = is_wallet_v4r1(&code_cell.repr_hash()); + assert!(is_wallet_v4r1); + + Ok(()) + } + + #[test] + fn code_hash_v4r2() -> anyhow::Result<()> { + let code_cell = wallets::code::wallet_v4r2(); + + let is_wallet_v4r2 = is_wallet_v4r2(&code_cell.repr_hash()); + assert!(is_wallet_v4r2); + + Ok(()) + } + + #[test] + fn state_init_v4r2() -> anyhow::Result<()> { + let state_init_base64 = "te6ccgECFgEAAwQAAgE0AQIBFP8A9KQT9LzyyAsDAFEAAAAAKamjF2dW1vNw/It5bDWN3jVo5dxzZVk+Q11lVLs3LamPSWAVQAIBIAQFAgFIBgcE+PKDCNcYINMf0x/THwL4I7vyZO1E0NMf0x/T//QE0VFDuvKhUVG68qIF+QFUEGT5EPKj+AAkpMjLH1JAyx9SMMv/UhD0AMntVPgPAdMHIcAAn2xRkyDXSpbTB9QC+wDoMOAhwAHjACHAAuMAAcADkTDjDQOkyMsfEssfy/8SExQVAubQAdDTAyFxsJJfBOAi10nBIJJfBOAC0x8hghBwbHVnvSKCEGRzdHK9sJJfBeAD+kAwIPpEAcjKB8v/ydDtRNCBAUDXIfQEMFyBAQj0Cm+hMbOSXwfgBdM/yCWCEHBsdWe6kjgw4w0DghBkc3RyupJfBuMNCAkCASAKCwB4AfoA9AQw+CdvIjBQCqEhvvLgUIIQcGx1Z4MesXCAGFAEywUmzxZY+gIZ9ADLaRfLH1Jgyz8gyYBA+wAGAIpQBIEBCPRZMO1E0IEBQNcgyAHPFvQAye1UAXKwjiOCEGRzdHKDHrFwgBhQBcsFUAPPFiP6AhPLassfyz/JgED7AJJfA+ICASAMDQBZvSQrb2omhAgKBrkPoCGEcNQICEekk30pkQzmkD6f+YN4EoAbeBAUiYcVnzGEAgFYDg8AEbjJftRNDXCx+AA9sp37UTQgQFA1yH0BDACyMoHy//J0AGBAQj0Cm+hMYAIBIBARABmtznaiaEAga5Drhf/AABmvHfaiaEAQa5DrhY/AAG7SB/oA1NQi+QAFyMoHFcv/ydB3dIAYyMsFywIizxZQBfoCFMtrEszMyXP7AMhAFIEBCPRR8qcCAHCBAQjXGPoA0z/IVCBHgQEI9FHyp4IQbm90ZXB0gBjIywXLAlAGzxZQBPoCFMtqEssfyz/Jc/sAAgBsgQEI1xj6ANM/MFIkgQEI9Fnyp4IQZHN0cnB0gBjIywXLAlAFzxZQA/oCE8tqyx8Syz/Jc/sAAAr0AMntVA=="; + let state_init = Boc::decode_base64(state_init_base64)?; + + let state_init = StateInit::load_from(&mut state_init.as_slice()?)?; + + let init_data_clone = InitData { + seqno: 0, + wallet_id: WALLET_ID, + public_key: HashBytes::from_str( + "6756d6f370fc8b796c358dde3568e5dc7365593e435d6554bb372da98f496015", + )?, + }; + + let state_init_clone = init_data_clone.make_state_init(WalletVersion::V4R2)?; + + assert_eq!(state_init, state_init_clone); + + Ok(()) + } +} diff --git a/src/utils/ton_wallet/wallet_v5r1.rs b/src/utils/ton_wallet/wallet_v5r1.rs new file mode 100644 index 0000000..9470017 --- /dev/null +++ b/src/utils/ton_wallet/wallet_v5r1.rs @@ -0,0 +1,484 @@ +use std::{collections::LinkedList, convert::TryFrom}; + +use anyhow::Result; +use ed25519_dalek::VerifyingKey; +use tycho_types::{ + cell::{Cell, CellBuilder, HashBytes, Lazy, Load, Store}, + models::{ + Account, AccountState, CurrencyCollection, ExtInMsgInfo, IntAddr, OutAction, OwnedMessage, + OwnedRelaxedMessage, RelaxedIntMsgInfo, RelaxedMsgInfo, SendMsgFlags, StateInit, StdAddr, + }, +}; + +use crate::utils::{ + ton_wallet::{Gift, TonWalletDetails}, + wallets::{self}, +}; + +const SIGNED_EXTERNAL_PREFIX: u32 = 0x7369676E; +const SIGNED_INTERNAL_PREFIX: u32 = 0x73696E74; + +pub fn prepare_deploy( + public_key: &VerifyingKey, + workchain: i8, + expire_at: u32, +) -> Result { + let init_data = make_init_data(public_key); + let dst = compute_contract_address(public_key, workchain)?; + let message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std(dst), + ..Default::default() + }), + body: Default::default(), + init: Some(init_data.make_state_init()?), + layout: None, + }; + let (hash, payload) = init_data.make_transfer_payload(None, expire_at, false)?; + Ok(UnsignedWalletV5 { + init_data, + gifts: vec![], + payload, + hash, + expire_at, + message, + }) +} + +pub fn prepare_state_init(public_key: &VerifyingKey) -> Result { + let init_data = make_init_data(public_key); + init_data.make_state_init() +} + +pub fn make_init_data(public_key: &VerifyingKey) -> InitData { + InitData::from_key(public_key) + .with_wallet_id(WALLET_ID) + .with_is_signature_allowed(true) +} + +pub fn get_init_data( + current_state: &Account, + public_key: &VerifyingKey, +) -> Result<(InitData, bool)> { + match current_state.state { + AccountState::Active(ref state_init) => match &state_init.data { + Some(data) => Ok((InitData::try_from(data)?, false)), + None => Err(WalletV5Error::InvalidInitData.into()), + }, + AccountState::Frozen { .. } => Err(WalletV5Error::AccountIsFrozen.into()), + AccountState::Uninit => Ok((make_init_data(public_key), true)), + } +} + +pub fn get_init_data_from_state_init(init: &StateInit) -> Result { + match &init.data { + Some(data) => Ok(InitData::try_from(data)?), + None => Err(WalletV5Error::InvalidInitData.into()), + } +} + +pub fn prepare_transfer( + public_key: &VerifyingKey, + current_state: &Account, + seqno_offset: u32, + gifts: Vec, + expire_at: u32, +) -> Result { + if gifts.len() > MAX_MESSAGES { + return Err(WalletV5Error::TooManyGifts.into()); + } + let (mut init_data, with_state_init) = get_init_data(current_state, public_key)?; + + init_data.seqno += seqno_offset; + + let (hash, payload) = init_data.make_transfer_payload(gifts.clone(), expire_at, false)?; + + let mut message = OwnedMessage { + info: tycho_types::models::MsgInfo::ExtIn(ExtInMsgInfo { + dst: IntAddr::Std( + current_state + .address + .as_std() + .ok_or(WalletV5Error::InvalidAddress)? + .clone(), + ), + ..Default::default() + }), + body: Default::default(), + init: None, + layout: None, + }; + + if with_state_init { + let state_init = init_data.make_state_init()?; + message.init = Some(state_init); + } + + Ok(UnsignedWalletV5 { + init_data, + gifts, + payload, + hash, + expire_at, + message, + }) +} + +pub struct UnsignedWalletV5 { + init_data: InitData, + gifts: Vec, + payload: CellBuilder, + hash: HashBytes, + expire_at: u32, + message: OwnedMessage, +} + +impl UnsignedWalletV5 { + pub fn expire_at(&self) -> u32 { + self.expire_at + } + + pub fn hash(&self) -> &[u8] { + self.hash.as_slice() + } + + pub fn gifts(&self) -> &[Gift] { + &self.gifts + } + + pub fn message(&self) -> &OwnedMessage { + &self.message + } + + pub fn init_data(&self) -> &InitData { + &self.init_data + } + + pub fn sign(&self, signature: &[u8; ed25519_dalek::SIGNATURE_LENGTH]) -> Result { + let mut payload = self.payload.clone(); + payload.store_raw(signature, (ed25519_dalek::SIGNATURE_LENGTH * 8) as u16)?; + + let mut message = self.message.clone(); + message.body = payload.build()?.into(); + + Ok(message) + } +} + +pub static CODE_HASH: &[u8; 32] = &[ + 0x20, 0x83, 0x4b, 0x7b, 0x72, 0xb1, 0x12, 0x14, 0x7e, 0x1b, 0x2f, 0xb4, 0x57, 0xb8, 0x4e, 0x74, + 0xd1, 0xa3, 0x0f, 0x04, 0xf7, 0x37, 0xd4, 0xf6, 0x2a, 0x66, 0x8e, 0x95, 0x52, 0xd2, 0xb7, 0x2f, +]; + +pub fn is_wallet_v5r1(code_hash: &HashBytes) -> bool { + code_hash.as_slice() == CODE_HASH +} + +pub fn compute_contract_address(public_key: &VerifyingKey, workchain_id: i8) -> Result { + make_init_data(public_key).compute_addr(workchain_id) +} + +pub static DETAILS: TonWalletDetails = TonWalletDetails { + requires_separate_deploy: false, + min_amount: 1, // 0.000000001 TON + max_messages: MAX_MESSAGES, + supports_payload: true, + supports_state_init: true, + supports_multiple_owners: false, + supports_code_update: false, + expiration_time: 0, + required_confirmations: None, +}; + +const MAX_MESSAGES: usize = 250; + +/// `WalletV5` init data +#[derive(Clone)] +pub struct InitData { + pub is_signature_allowed: bool, + pub seqno: u32, + pub wallet_id: u32, + pub public_key: HashBytes, + pub extensions: Option, +} + +impl InitData { + pub fn public_key(&self) -> &[u8; 32] { + &self.public_key.0 + } + + pub fn from_key(key: &VerifyingKey) -> Self { + Self { + is_signature_allowed: false, + seqno: 0, + wallet_id: 0, + public_key: HashBytes::from_slice(key.as_bytes()), + extensions: Default::default(), + } + } + + pub fn with_is_signature_allowed(mut self, is_allowed: bool) -> Self { + self.is_signature_allowed = is_allowed; + self + } + + pub fn with_wallet_id(mut self, id: u32) -> Self { + self.wallet_id = id; + self + } + + pub fn compute_addr(&self, workchain_id: i8) -> Result { + let state_init = self.make_state_init()?; + let cell_builder = CellBuilder::build_from(&state_init)?; + let hash = cell_builder.repr_hash(); + Ok(StdAddr::new(workchain_id, *hash)) + } + + pub fn make_state_init(&self) -> Result { + Ok(StateInit { + code: Some(wallets::code::wallet_v5r1()), + data: Some(self.serialize()?), + ..Default::default() + }) + } + + pub fn serialize(&self) -> Result { + let mut builder = CellBuilder::new(); + builder.store_bit(self.is_signature_allowed)?; + builder.store_u32(self.seqno)?; + builder.store_u32(self.wallet_id)?; + builder.store_u256(&self.public_key)?; + + if let Some(extensions) = &self.extensions { + builder.store_bit_one()?; + builder.store_reference(extensions.clone())?; + } else { + builder.store_bit_zero()?; + } + + let data = builder.build()?; + Ok(data) + } + + pub fn make_transfer_payload( + &self, + gifts: impl IntoIterator, + expire_at: u32, + is_internal_flow: bool, + ) -> Result<(HashBytes, CellBuilder)> { + // Check if signatures are allowed + if !self.is_signature_allowed { + return if self.extensions.is_none() { + Err(WalletV5Error::WalletLocked.into()) + } else { + Err(WalletV5Error::SignaturesDisabled.into()) + }; + } + + let mut builder = CellBuilder::new(); + + // insert prefix + if is_internal_flow { + builder.store_u32(SIGNED_INTERNAL_PREFIX)?; + } else { + builder.store_u32(SIGNED_EXTERNAL_PREFIX)?; + }; + + builder.store_u32(self.wallet_id)?; + builder.store_u32(expire_at)?; + builder.store_u32(self.seqno)?; + + let mut actions_builder = OutActions(Default::default()); + + for gift in gifts { + let internal_message = Lazy::new(&OwnedRelaxedMessage { + info: RelaxedMsgInfo::Int(RelaxedIntMsgInfo { + ihr_disabled: true, + bounce: gift.bounce, + dst: IntAddr::Std(gift.destination), + value: CurrencyCollection::new(gift.amount), + ..Default::default() + }), + init: gift.state_init, + body: gift.body.unwrap_or(Default::default()).into(), + layout: None, + })?; + + let action = OutAction::SendMsg { + mode: SendMsgFlags::from_bits_retain(gift.flags), + out_msg: internal_message, + }; + + actions_builder.0.push_back(action); + } + + builder.store_bit_one()?; + builder.store_reference(CellBuilder::build_from(&actions_builder)?)?; + + // has_other_actions + builder.store_bit_zero()?; + + let payload = builder.clone().build()?; + let hash = payload.repr_hash(); + + Ok((*hash, builder)) + } +} + +impl TryFrom<&Cell> for InitData { + type Error = anyhow::Error; + + fn try_from(data: &Cell) -> Result { + let mut slice = data.as_slice()?; + let is_signature_allowed = slice.load_bit()?; + let seqno = slice.load_u32()?; + let wallet_id = slice.load_u32()?; + let mut buffer = [0u8; 32]; + slice.load_raw(&mut buffer, 256)?; + let public_key = HashBytes::from_slice(&buffer); + let extensions = Option::::load_from(&mut slice)?; + + Ok(Self { + is_signature_allowed, + seqno, + wallet_id, + public_key, + extensions, + }) + } +} + +pub struct OutActions(pub LinkedList); + +impl Store for OutActions { + fn store_into( + &self, + builder: &mut CellBuilder, + context: &dyn tycho_types::prelude::CellContext, + ) -> std::result::Result<(), tycho_types::error::Error> { + let mut new_builder = CellBuilder::new(); + + for action in self.0.iter() { + let mut next_builder = CellBuilder::new(); + + next_builder.store_reference(new_builder.build()?)?; + action.store_into(&mut next_builder, context)?; + + new_builder = next_builder; + } + + builder.store_builder(&new_builder)?; + Ok(()) + } +} + +const WALLET_ID: u32 = 0x7FFFFF11; + +#[derive(thiserror::Error, Debug)] +enum WalletV5Error { + #[error("Invalid init data")] + InvalidInitData, + #[error("Account is frozen")] + AccountIsFrozen, + #[error("Too many outgoing messages")] + TooManyGifts, + #[error("Signatures are disabled")] + SignaturesDisabled, + #[error("Wallet locked")] + WalletLocked, + #[error("Account address is not valid")] + InvalidAddress, +} + +#[cfg(test)] +mod tests { + use ed25519_dalek::VerifyingKey; + use tycho_types::{ + boc::Boc, + cell::Load, + models::{Account, AccountState}, + }; + + use crate::utils::{ + ton_wallet::wallet_v5r1::{compute_contract_address, is_wallet_v5r1, InitData, WALLET_ID}, + wallets, + }; + + #[test] + fn state_init() -> anyhow::Result<()> { + let account_base64 = "te6ccgECFgEAAucAAm6ADZRqTnEksRaYvpXRMbgzB92SzFv/19WbfQQgdDo7lYwEWQnKBnPzD1AAAXPmjwdAEj9i9OgmAgEAUYAAAAG///+IyIPTKTihvw1MFdzCAl7NQWIaeY9xhjENsss4FdrN+FAgART/APSkE/S88sgLAwIBIAYEAQLyBQEeINcLH4IQc2lnbrry4Ip/EQIBSBAHAgEgCQgAGb5fD2omhAgKDrkPoCwCASANCgIBSAwLABGyYvtRNDXCgCAAF7Ml+1E0HHXIdcLH4AIBbg8OABmvHfaiaEAQ65DrhY/AABmtznaiaEAg65Drhf/AAtzQINdJwSCRW49jINcLHyCCEGV4dG69IYIQc2ludL2wkl8D4IIQZXh0brqOtIAg1yEB0HTXIfpAMPpE+Cj6RDBYvZFb4O1E0IEBQdch9AWDB/QOb6ExkTDhgEDXIXB/2zzgMSDXSYECgLmRMOBw4hIRAeaO8O2i7fshgwjXIgKDCNcjIIAg1yHTH9Mf0x/tRNDSANMfINMf0//XCgAK+QFAzPkQmiiUXwrbMeHywIffArNQB7Dy0IRRJbry4IVQNrry4Ib4I7vy0IgikvgA3gGkf8jKAMsfAc8Wye1UIJL4D95w2zzYEgP27aLt+wL0BCFukmwhjkwCIdc5MHCUIccAs44tAdcoIHYeQ2wg10nACPLgkyDXSsAC8uCTINcdBscSwgBSMLDy0InXTNc5MAGk6GwShAe78uCT10rAAPLgk+1V4tIAAcAAkVvg69csCBQgkXCWAdcsCBwS4lIQseMPINdKFRQTABCTW9sx4ddM0AByMNcsCCSOLSHy4JLSAO1E0NIAURO68tCPVFAwkTGcAYEBQNch1woA8uCO4sjKAFjPFsntVJPywI3iAJYB+kAB+kT4KPpEMFi68uCR7UTQgQFB1xj0BQSdf8jKAEAEgwf0U/Lgi44UA4MH9Fvy4Iwi1woAIW4Bs7Dy0JDiyFADzxYS9ADJ7VQ="; + let account = Boc::decode_base64(account_base64)?; + + let state = Account::load_from(&mut account.as_slice()?)?; + + if let AccountState::Active(state_init) = state.state { + let init_data = InitData::try_from(&state_init.data.unwrap())?; + assert_eq!(init_data.is_signature_allowed, true); + assert_eq!( + init_data.public_key.to_string(), + "9107a65271437e1a982bb98404bd9a82c434f31ee30c621b6596702bb59bf0a0" + ); + assert_eq!(init_data.wallet_id, WALLET_ID); + assert_eq!(init_data.extensions, None); + + let public_key = + VerifyingKey::from_bytes(init_data.public_key.as_slice().try_into().unwrap())?; + let address = compute_contract_address(&public_key, 0)?; + assert_eq!( + address.to_string(), + "0:6ca35273892588b4c5f4ae898dc1983eec9662dffebeacdbe82103a1d1dcac60" + ); + } + + Ok(()) + } + + #[test] + fn code_hash() -> anyhow::Result<()> { + let code_cell = wallets::code::wallet_v5r1(); + + let is_wallet_v5r1 = is_wallet_v5r1(&code_cell.repr_hash()); + assert!(is_wallet_v5r1); + + Ok(()) + } + + // #[test] + // fn check_signature_test() -> anyhow::Result<()> { + // let public_key_bytes = + // hex::decode("6c2f9514c1c0f2ec54cffe1ac2ba0e85268e76442c14205581ebc808fe7ee52c")?; + // //let payload = base64::decode("te6ccgECCQEAAWMAASFzaW50f///EWjJNSIAAAABoAECCg7DyG0DBQIB80IAEiSxvuIkjLwTZ/69OCTi5io4ZpgjPKnD56XnecGH1Q0gcJ32yAAAAAAAAAAAAAAAAABz4iFDAAAAAAAAAAAAAAAAO5rKAIAfPq6ksCQX/kNfsY8xS5PTRd4WSjwjs5C/fod9ktFK+MAAAAAAAAAAAAAAAAD39JAwAwFDgBI2HlLkTtTC7ntWgsSS4jmXMUkhy2OTDHvAO1YAIIdyCAQBCAAAAAAIAgoOw8htAwgGAdNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIC+vCAAAAAAAAAAAAAAAAAAARqnX7AAAAAAAAAAAAAAAAAIA9mKAH6YK7ZtGhTyJBnq9b54dnz07z830q8r/r5MBXJdSioIQBwFDgBhcpJ/VWhGKPK44GyznIrRqKDcoivK5/ZanRrMrFKCjiAgAAA==")?; + // let payload = base64::decode("te6ccgECCQEAAaMAAaFzaW50f///EWjJNSIAAAABr9SYdbfeTOkhxaWVTsB40YIzxnswT6p7oxjydvTUZ0afi8fq5F2NvuyGho+YxBUC2NPkhtL3+tuMa5CfUwJMg2ABAgoOw8htAwUCAfNCABIksb7iJIy8E2f+vTgk4uYqOGaYIzypw+el53nBh9UNIHCd9sgAAAAAAAAAAAAAAAAAc+IhQwAAAAAAAAAAAAAAADuaygCAHz6upLAkF/5DX7GPMUuT00XeFko8I7OQv36HfZLRSvjAAAAAAAAAAAAAAAAA9/SQMAMBQ4ASNh5S5E7Uwu57VoLEkuI5lzFJIctjkwx7wDtWACCHcggEAQgAAAAACAIKDsPIbQMIBgHTQgASJLG+4iSMvBNn/r04JOLmKjhmmCM8qcPnped5wYfVDSAvrwgAAAAAAAAAAAAAAAAAAEap1+wAAAAAAAAAAAAAAAACAPZigB+mCu2bRoU8iQZ6vW+eHZ89O8/N9KvK/6+TAVyXUoqCEAcBQ4AYXKSf1VoRijyuOBss5yK0aig3KIryuf2Wp0azKxSgo4gIAAA=")?; + // let in_msg_body = ton_types::deserialize_tree_of_cells(&mut payload.as_slice())?; + // let in_msg_body_slice = SliceData::load_cell(in_msg_body)?; + // + // let public_key = VerifyingKey::from_bytes(public_key_bytes.as_slice())?; + // + // let result = check_signature(in_msg_body_slice, public_key, Some(2000))?; + // assert!(result); + // Ok(()) + // } + // + // fn check_signature( + // mut in_msg_body: SliceData, + // public_key: VerifyingKey, + // signature_id: Option, + // ) -> anyhow::Result { + // let signature_binding = in_msg_body + // .get_slice(in_msg_body.remaining_bits() - 512, 512)? + // .remaining_data(); + // let sig = signature_binding.data(); + // + // let payload = in_msg_body + // .shrink_data(in_msg_body.remaining_bits() - 512..) + // .into_cell(); + // + // let hash = payload.repr_hash(); + // + // let data = extend_with_signature_id(hash.as_ref(), signature_id); + // + // Ok(public_key + // .verify(&*data, &Signature::from_bytes(sig)?) + // .is_ok()) + // } +} diff --git a/src/utils/tx_context.rs b/src/utils/tx_context.rs index f186efa..50ecc70 100644 --- a/src/utils/tx_context.rs +++ b/src/utils/tx_context.rs @@ -1,7 +1,13 @@ -use nekoton::transport::models::ExistingContract; +use anyhow::Result; +use nekoton_core::contracts::blockchain_context::BlockchainContext; use tokio::sync::oneshot; -use ton_types::UInt256; -use tycho_types::models::BlockId; +use tycho_types::{ + abi::{Function, NamedAbiValue}, + cell::{CellSlice, HashBytes}, + models::{BlockId, MsgInfo, OrdinaryTxInfo, OwnedMessage, Transaction}, +}; + +use crate::models::ExistingContract; pub trait ReadFromTransaction: Sized { fn read_from_transaction(ctx: &TxContext<'_>, state: HandleTransactionStatusTx) @@ -19,22 +25,33 @@ pub struct StateContext<'a> { #[derive(Copy, Clone)] pub struct TxContext<'a> { pub block_info_gen_utime: u32, - pub block_hash: &'a UInt256, - pub account: &'a UInt256, - pub transaction_hash: &'a UInt256, - pub transaction_info: &'a ton_block::TransactionDescrOrdinary, - pub transaction: &'a ton_block::Transaction, - pub in_msg: &'a ton_block::Message, - pub token_transaction: &'a Option, + pub block_hash: &'a HashBytes, + pub account: &'a HashBytes, + pub transaction_hash: &'a HashBytes, + pub transaction_info: &'a OrdinaryTxInfo, + pub transaction: &'a Transaction, + pub in_msg: &'a OwnedMessage, + pub token_transaction: &'a Option, pub token_state: &'a Option, + pub blockchain_context: &'a Option, +} + +#[derive(Clone)] +pub struct BlockchainContextWrapper { + pub blockchain_context: BlockchainContext, +} + +impl std::fmt::Debug for BlockchainContextWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("BlockchainContextWrapper") + .field("blockchain_context", &"context") + .finish() + } } impl TxContext<'_> { - pub fn in_msg_internal(&self) -> Option<&ton_block::Message> { - if matches!( - self.in_msg.header(), - ton_block::CommonMsgInfo::IntMsgInfo(_) - ) { + pub fn in_msg_internal(&self) -> Option<&OwnedMessage> { + if matches!(self.in_msg.info, MsgInfo::Int(_)) { Some(self.in_msg) } else { None @@ -42,81 +59,53 @@ impl TxContext<'_> { } #[allow(dead_code)] - pub fn in_msg_external(&self) -> Option<&ton_block::Message> { - if matches!( - self.in_msg.header(), - ton_block::CommonMsgInfo::ExtInMsgInfo(_) - ) { + pub fn in_msg_external(&self) -> Option<&OwnedMessage> { + if matches!(self.in_msg.info, MsgInfo::ExtIn(_)) { Some(self.in_msg) } else { None } } #[allow(dead_code)] - pub fn find_function_output( - &self, - function: &ton_abi::Function, - ) -> Option> { - let mut result = None; - self.transaction - .out_msgs - .iterate(|ton_block::InRefValue(message)| { - // Skip all messages except external outgoing - if !matches!(message.header(), ton_block::CommonMsgInfo::ExtOutMsgInfo(_)) { - return Ok(true); - } + pub fn find_function_output(&self, function: &Function) -> Result>> { + for message in self.transaction.iter_out_msgs() { + let message = message?; + // Skip all messages except external outgoing + if !matches!(message.info, MsgInfo::ExtOut(_)) { + continue; + } - // Handle body if it exists - let body = match message.body() { - Some(body) => body, - None => return Ok(true), - }; + let function_id = message.body.get_u32(0)?; + if function_id != function.output_id { + continue; + } - let function_id = nekoton_abi::read_function_id(&body)?; - if function_id != function.output_id { - return Ok(true); + match function.decode_output(message.body) { + Ok(tokens) => { + return Ok(Some(tokens)); } - - Ok(match function.decode_output(body, false) { - Ok(tokens) => { - result = Some(tokens); - false - } - Err(_) => true, - }) - }) - .ok(); - result + Err(_) => continue, + } + } + Ok(None) } #[allow(dead_code)] pub fn iterate_events(&self, mut f: F) where - F: FnMut(u32, ton_types::SliceData), + F: FnMut(u32, CellSlice<'_>), { - self.transaction - .out_msgs - .iterate(|ton_block::InRefValue(message)| { - // Skip all messages except external outgoing - if !matches!(message.header(), ton_block::CommonMsgInfo::ExtOutMsgInfo(_)) { - return Ok(true); - } - - // Handle body if it exists - let body = match message.body() { - Some(body) => body, - None => return Ok(true), - }; + for message in self.transaction.iter_out_msgs() { + let Ok(message) = message else { continue }; + // Skip all messages except external outgoing + if !matches!(message.info, MsgInfo::ExtOut(_)) { + continue; + } - // Parse function id - if let Ok(function_id) = nekoton_abi::read_function_id(&body) { - f(function_id, body) - } - - // Process all messages - Ok(true) - }) - .ok(); + if let Ok(function_id) = message.body.get_u32(0) { + f(function_id, message.body) + } + } } } diff --git a/src/utils/wallets/code/BridgeMultisigWallet.tvc b/src/utils/wallets/code/BridgeMultisigWallet.tvc new file mode 100644 index 0000000..7d8917f Binary files /dev/null and b/src/utils/wallets/code/BridgeMultisigWallet.tvc differ diff --git a/src/utils/wallets/code/Multisig2.tvc b/src/utils/wallets/code/Multisig2.tvc new file mode 100644 index 0000000..e37b2ec Binary files /dev/null and b/src/utils/wallets/code/Multisig2.tvc differ diff --git a/src/utils/wallets/code/Multisig2_1.tvc b/src/utils/wallets/code/Multisig2_1.tvc new file mode 100644 index 0000000..2419b75 Binary files /dev/null and b/src/utils/wallets/code/Multisig2_1.tvc differ diff --git a/src/utils/wallets/code/SafeMultisigWallet.tvc b/src/utils/wallets/code/SafeMultisigWallet.tvc new file mode 100644 index 0000000..7584907 Binary files /dev/null and b/src/utils/wallets/code/SafeMultisigWallet.tvc differ diff --git a/src/utils/wallets/code/SafeMultisigWallet24h.tvc b/src/utils/wallets/code/SafeMultisigWallet24h.tvc new file mode 100644 index 0000000..03a4246 Binary files /dev/null and b/src/utils/wallets/code/SafeMultisigWallet24h.tvc differ diff --git a/src/utils/wallets/code/SetcodeMultisigWallet.tvc b/src/utils/wallets/code/SetcodeMultisigWallet.tvc new file mode 100644 index 0000000..4e2eef2 Binary files /dev/null and b/src/utils/wallets/code/SetcodeMultisigWallet.tvc differ diff --git a/src/utils/wallets/code/SetcodeMultisigWallet24h.tvc b/src/utils/wallets/code/SetcodeMultisigWallet24h.tvc new file mode 100644 index 0000000..b70936d Binary files /dev/null and b/src/utils/wallets/code/SetcodeMultisigWallet24h.tvc differ diff --git a/src/utils/wallets/code/Surf.tvc b/src/utils/wallets/code/Surf.tvc new file mode 100644 index 0000000..db20071 Binary files /dev/null and b/src/utils/wallets/code/Surf.tvc differ diff --git a/src/utils/wallets/code/ever_wallet_code.boc b/src/utils/wallets/code/ever_wallet_code.boc new file mode 100644 index 0000000..5a5b0cc Binary files /dev/null and b/src/utils/wallets/code/ever_wallet_code.boc differ diff --git a/src/utils/wallets/code/highload_wallet_v2_code.boc b/src/utils/wallets/code/highload_wallet_v2_code.boc new file mode 100644 index 0000000..7e9ced8 Binary files /dev/null and b/src/utils/wallets/code/highload_wallet_v2_code.boc differ diff --git a/src/utils/wallets/code/mod.rs b/src/utils/wallets/code/mod.rs new file mode 100644 index 0000000..3379d90 --- /dev/null +++ b/src/utils/wallets/code/mod.rs @@ -0,0 +1,34 @@ +use tycho_types::{boc::Boc, cell::Cell}; + +macro_rules! declare_tvc { + ($($contract:ident => $source:literal ($const_bytes:ident)),*$(,)?) => {$( + const $const_bytes: &[u8] = include_bytes!($source); + + pub fn $contract() -> Cell { + load($const_bytes) + } + )*}; +} + +declare_tvc! { + safe_multisig_wallet => "./SafeMultisigWallet.tvc" (SAFE_MULTISIG_WALLET_CODE), + safe_multisig_wallet_24h => "./SafeMultisigWallet24h.tvc" (SAFE_MULTISIG_WALLET24H_CODE), + setcode_multisig_wallet => "./SetcodeMultisigWallet.tvc" (SETCODE_MULTISIG_WALLET_CODE), + setcode_multisig_wallet_24h => "./SetcodeMultisigWallet24h.tvc" (SETCODE_MULTISIG_WALLET24H_CODE), + bridge_multisig_wallet => "./BridgeMultisigWallet.tvc" (BRIDGE_MULTISIG_WALLET_CODE), + multisig2 => "./Multisig2.tvc" (MULTISIG2_CODE), + multisig2_1 => "./Multisig2_1.tvc" (MULTISIG2_1_CODE), + surf_wallet => "./Surf.tvc" (SURF_WALLET_CODE), + wallet_v3 => "./wallet_v3_code.boc" (WALLET_V3_CODE), + wallet_v3r1 => "./wallet_v3r1_code.boc" (WALLET_V3R1_CODE), + wallet_v3r2 => "./wallet_v3r2_code.boc" (WALLET_V3R2_CODE), + wallet_v4r1 => "./wallet_v4r1_code.boc" (WALLET_V4R1_CODE), + wallet_v4r2 => "./wallet_v4r2_code.boc" (WALLET_V4R2_CODE), + wallet_v5r1 => "./wallet_v5r1_code.boc" (WALLET_V5R1_CODE), + highload_wallet_v2 => "./highload_wallet_v2_code.boc" (HIGHLOAD_WALLET_V2_CODE), + ever_wallet => "./ever_wallet_code.boc" (EVER_WALLET_CODE), +} + +fn load(data: &[u8]) -> Cell { + Boc::decode(data).expect("Trust me") +} diff --git a/src/utils/wallets/code/wallet_v3_code.boc b/src/utils/wallets/code/wallet_v3_code.boc new file mode 100644 index 0000000..2600b43 Binary files /dev/null and b/src/utils/wallets/code/wallet_v3_code.boc differ diff --git a/src/utils/wallets/code/wallet_v3r1_code.boc b/src/utils/wallets/code/wallet_v3r1_code.boc new file mode 100644 index 0000000..e5cea09 Binary files /dev/null and b/src/utils/wallets/code/wallet_v3r1_code.boc differ diff --git a/src/utils/wallets/code/wallet_v3r2_code.boc b/src/utils/wallets/code/wallet_v3r2_code.boc new file mode 100644 index 0000000..2600b43 Binary files /dev/null and b/src/utils/wallets/code/wallet_v3r2_code.boc differ diff --git a/src/utils/wallets/code/wallet_v4r1_code.boc b/src/utils/wallets/code/wallet_v4r1_code.boc new file mode 100644 index 0000000..542d420 Binary files /dev/null and b/src/utils/wallets/code/wallet_v4r1_code.boc differ diff --git a/src/utils/wallets/code/wallet_v4r2_code.boc b/src/utils/wallets/code/wallet_v4r2_code.boc new file mode 100644 index 0000000..f97fbc0 Binary files /dev/null and b/src/utils/wallets/code/wallet_v4r2_code.boc differ diff --git a/src/utils/wallets/code/wallet_v5r1_code.boc b/src/utils/wallets/code/wallet_v5r1_code.boc new file mode 100644 index 0000000..56c9a34 Binary files /dev/null and b/src/utils/wallets/code/wallet_v5r1_code.boc differ diff --git a/src/utils/wallets/ever_wallet.rs b/src/utils/wallets/ever_wallet.rs new file mode 100644 index 0000000..4d10297 --- /dev/null +++ b/src/utils/wallets/ever_wallet.rs @@ -0,0 +1,53 @@ +use tycho_types::abi::{AbiType, Function}; + +use crate::utils::declare_function; + +pub fn send_transaction() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "sendTransaction", + inputs: vec![ + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Uint(8).named("flags"), + AbiType::Cell.named("payload"), + ], + outputs: vec![], + } +} + +macro_rules! declare_send_transaction_raw { + ($($name:ident => [$($inputs:tt)*]),*,) => { + $(pub fn $name() -> &'static Function { + declare_function! { + abi: v2_3, + function_id: 0x169e3e11, + header: [pubkey, time, expire], + name: "sendTransactionRaw", + inputs: declare_send_transaction_raw!(@inputs [$($inputs)*] []), + outputs: Vec::new(), + } + })* + }; + + (@inputs [] [$($inputs:tt)*]) => { + vec![$($inputs)*] + }; + (@inputs [$(,)? _ $($rest:tt)*] [$($inputs:tt)*]) => { + declare_send_transaction_raw!(@inputs [$($rest)*] [ + $($inputs)* + tycho_types::abi::AbiType::Uint(8).named("flags"), + tycho_types::abi::AbiType::Cell.named("message"), + ]) + }; +} + +declare_send_transaction_raw! { + send_transaction_raw_0 => [], + send_transaction_raw_1 => [_], + send_transaction_raw_2 => [_, _], + send_transaction_raw_3 => [_, _, _], + send_transaction_raw_4 => [_, _, _, _], +} diff --git a/src/utils/wallets/mod.rs b/src/utils/wallets/mod.rs new file mode 100644 index 0000000..6f56b39 --- /dev/null +++ b/src/utils/wallets/mod.rs @@ -0,0 +1,4 @@ +pub mod code; +pub mod ever_wallet; +pub mod multisig; +pub mod multisig2; diff --git a/src/utils/wallets/multisig.rs b/src/utils/wallets/multisig.rs new file mode 100644 index 0000000..b60beec --- /dev/null +++ b/src/utils/wallets/multisig.rs @@ -0,0 +1,192 @@ +use std::sync::Arc; + +use tycho_types::{ + abi::{AbiType, FromAbi, Function, IntoAbi, NamedAbiType, WithAbiType}, + cell::{Cell, HashBytes}, + models::StdAddr, +}; + +use crate::utils::declare_function; + +pub fn constructor() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "constructor", + inputs: vec![ + AbiType::Array(Arc::new(AbiType::Uint(256))).named("owners"), + AbiType::Uint(8).named("reqConfirms"), + ], + outputs: vec![] as Vec, + } +} + +pub fn send_transaction() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "sendTransaction", + inputs: vec![ + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Uint(8).named("flags"), + AbiType::Cell.named("payload"), + ], + outputs: vec![] as Vec, + } +} + +pub fn submit_transaction() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "submitTransaction", + inputs: vec![ + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Bool.named("allBalance"), + AbiType::Cell.named("payload"), + ], + outputs: vec![ + AbiType::Uint(64).named("transId") + ], + } +} + +pub fn confirm_transaction() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "confirmTransaction", + inputs: vec![ + AbiType::Uint(64).named("transactionId"), + ], + outputs: vec![] as Vec, + } +} + +#[derive(IntoAbi, FromAbi, WithAbiType, Eq, PartialEq, Debug, Clone)] +pub struct MultisigTransaction { + pub id: u64, + #[abi(name = "confirmationsMask")] + pub confirmation_mask: u32, + #[abi(name = "signsRequired")] + pub signs_required: u8, + #[abi(name = "signsReceived")] + pub signs_received: u8, + pub creator: HashBytes, + pub index: u8, + pub dest: StdAddr, + pub value: u128, + #[abi(name = "sendFlags")] + pub send_flags: u16, + pub payload: Cell, + pub bounce: bool, +} + +pub fn get_transactions() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getTransactions", + inputs: vec![] as Vec, + outputs: vec![ + AbiType::Array(Arc::new(MultisigTransaction::abi_type())).named("transactions") + ] + } +} + +#[derive(IntoAbi, FromAbi, WithAbiType, Eq, PartialEq, Debug, Clone)] +pub struct MultisigCustodian { + pub index: u8, + pub pubkey: HashBytes, +} + +pub fn get_custodians() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getCustodians", + inputs: vec![] as Vec, + outputs: vec![ + AbiType::Array(Arc::new(MultisigCustodian::abi_type())).named("custodians") + + ] + } +} + +#[allow(unused)] +pub mod safe_multisig { + use crate::utils::WithAbiTypePlain; + + use super::*; + + #[derive(Debug, Clone, Copy, WithAbiType)] + pub struct SafeMultisigParams { + pub max_queued_transactions: u8, + pub max_custodian_count: u8, + pub expiration_time: u64, + pub min_value: u128, + pub required_txn_confirms: u8, + } + + impl WithAbiTypePlain for SafeMultisigParams {} + + pub fn get_parameters() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getParameters", + inputs: vec![] as Vec, + outputs: SafeMultisigParams::abi_type_plain(), + } + } +} + +#[allow(unused)] +pub mod set_code_multisig { + use crate::utils::WithAbiTypePlain; + + use super::*; + + #[derive(Debug, Clone, Copy, WithAbiType)] + pub struct SetCodeMultisigParams { + pub max_queued_transactions: u8, + pub max_custodian_count: u8, + pub expiration_time: u64, + pub min_value: u128, + pub required_txn_confirms: u8, + pub required_upd_confirms: u8, + } + + impl WithAbiTypePlain for SetCodeMultisigParams {} + + pub fn get_parameters() -> &'static Function { + declare_function! { + abi: v2_0, + header: [pubkey, time, expire], + name: "getParameters", + inputs: vec![] as Vec, + outputs: SetCodeMultisigParams::abi_type_plain(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn correct_function_ids() { + assert_eq!(constructor().input_id, 0x6c1e693c); + assert_eq!(send_transaction().input_id, 0x4cee646c); + assert_eq!(submit_transaction().input_id, 0x131d82cd); + assert_eq!(confirm_transaction().input_id, 0x1aa740ed); + assert_eq!(safe_multisig::get_parameters().input_id, 0x6d28dde8); + assert_eq!(set_code_multisig::get_parameters().input_id, 0x66b8710c); + assert_eq!(get_transactions().input_id, 0x73122f72); + assert_eq!(get_custodians().input_id, 0x5b00d859); + } +} diff --git a/src/utils/wallets/multisig2.rs b/src/utils/wallets/multisig2.rs new file mode 100644 index 0000000..e8eb054 --- /dev/null +++ b/src/utils/wallets/multisig2.rs @@ -0,0 +1,286 @@ +use std::sync::Arc; + +use tycho_types::{ + abi::{AbiType, FromAbi, Function, IntoAbi, NamedAbiType, WithAbiType}, + cell::{Cell, HashBytes}, + models::StdAddr, +}; + +use crate::utils::{declare_function, FromAbiPlain, IntoAbiPlain, WithAbiTypePlain}; + +pub fn constructor() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "constructor", + inputs: vec![ + AbiType::Array(Arc::new(AbiType::Uint(256))).named("owners"), + AbiType::Uint(8).named("reqConfirms"), + AbiType::Uint(32).named("lifetime"), + ], + outputs: vec![] as Vec, + } +} + +pub fn send_transaction() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "sendTransaction", + inputs: vec![ + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Uint(8).named("flags"), + AbiType::Cell.named("payload"), + ], + outputs: vec![] as Vec, + } +} + +pub fn submit_transaction() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "submitTransaction", + inputs: vec![ + AbiType::Address.named("dest"), + AbiType::Uint(128).named("value"), + AbiType::Bool.named("bounce"), + AbiType::Bool.named("allBalance"), + AbiType::Cell.named("payload"), + AbiType::Optional(Arc::new(AbiType::Cell)).named("stateInit"), + ], + outputs: vec![ + AbiType::Uint(64).named("transId") + ], + } +} + +pub fn confirm_transaction() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "confirmTransaction", + inputs: vec![ + AbiType::Uint(64).named("transactionId"), + ], + outputs: vec![] as Vec, + } +} + +#[allow(unused)] +#[derive(Debug, WithAbiType)] +pub struct MultisigTransaction { + pub id: u64, + pub confirmation_mask: u32, + pub signs_required: u8, + pub signs_received: u8, + pub creator: HashBytes, + pub index: u8, + pub dest: StdAddr, + pub value: u128, + pub send_flags: u16, + pub payload: Cell, + pub bounce: bool, + pub state_init: Option, +} + +pub fn get_transactions() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getTransactions", + inputs: vec![] as Vec, + outputs: vec![ + AbiType::Array(Arc::new(MultisigTransaction::abi_type())).named("transactions") + ] + } +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy, WithAbiType)] +pub struct MultisigCustodian { + pub index: u8, + pub pubkey: HashBytes, +} + +pub fn get_custodians() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getCustodians", + inputs: vec![] as Vec, + outputs: vec![ + AbiType::Array(Arc::new(MultisigCustodian::abi_type())).named("custodians") + ] + } +} + +#[derive(Debug, Clone, WithAbiType, FromAbi, IntoAbi)] +pub struct SubmitUpdateParams { + pub code_hash: Option, + pub owners: Option>, + pub req_confirms: Option, + pub lifetime: Option, +} +impl IntoAbiPlain for SubmitUpdateParams {} +impl FromAbiPlain for SubmitUpdateParams {} +impl WithAbiTypePlain for SubmitUpdateParams {} + +#[derive(Debug, Copy, Clone, WithAbiType, FromAbi, IntoAbi)] +pub struct SubmitUpdateOutput { + pub update_id: u64, +} +impl IntoAbiPlain for SubmitUpdateOutput {} +impl FromAbiPlain for SubmitUpdateOutput {} +impl WithAbiTypePlain for SubmitUpdateOutput {} + +pub fn submit_update() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "submitUpdate", + inputs: SubmitUpdateParams::abi_type_plain(), + outputs: SubmitUpdateOutput::abi_type_plain(), + } +} + +#[derive(Debug, Copy, Clone, WithAbiType, FromAbi, IntoAbi)] +pub struct ConfirmUpdateParams { + pub update_id: u64, +} +impl IntoAbiPlain for ConfirmUpdateParams {} +impl FromAbiPlain for ConfirmUpdateParams {} +impl WithAbiTypePlain for ConfirmUpdateParams {} + +pub fn confirm_update() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "confirmUpdate", + inputs: ConfirmUpdateParams::abi_type_plain(), + outputs: vec![] as Vec, + } +} + +#[derive(Debug, Clone, WithAbiType, FromAbi, IntoAbi)] +pub struct ExecuteUpdateParams { + pub update_id: u64, + pub code: Option, +} +impl IntoAbiPlain for ExecuteUpdateParams {} +impl FromAbiPlain for ExecuteUpdateParams {} +impl WithAbiTypePlain for ExecuteUpdateParams {} + +pub fn execute_update() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "executeUpdate", + inputs: ExecuteUpdateParams::abi_type_plain(), + outputs: vec![] as Vec, + } +} + +#[allow(unused)] +#[derive(Debug, Clone, Copy)] +pub struct SetCodeMultisigParams { + pub max_queued_transactions: u8, + pub max_custodian_count: u8, + pub expiration_time: u64, + pub min_value: u128, + pub required_txn_confirms: u8, + pub required_upd_confirms: u8, +} + +impl SetCodeMultisigParams { + fn abi_type() -> Vec { + vec![ + AbiType::Uint(8).named("maxQueuedTransactions"), + AbiType::Uint(8).named("maxCustodianCount"), + AbiType::Uint(64).named("expirationTime"), + AbiType::Uint(128).named("minValue"), + AbiType::Uint(8).named("requiredTxnConfirms"), + AbiType::Uint(8).named("requiredUpdConfirms"), + ] + } +} +pub fn get_parameters() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getParameters", + inputs: vec![] as Vec, + outputs: SetCodeMultisigParams::abi_type(), + } +} + +#[derive(IntoAbi, FromAbi, WithAbiType, Eq, PartialEq, Debug, Clone)] +pub struct UpdateTransaction { + pub id: u64, + pub index: u8, + pub signs: u8, + #[abi(name = "confirmationsMask")] + pub confirmations_mask: u32, + pub creator: HashBytes, + #[abi(name = "newCodeHash")] + pub new_code_hash: Option, + #[abi(name = "newCustodians")] + pub new_custodians: Option>, + #[abi(name = "newReqConfirms")] + pub new_req_confirms: Option, + #[abi(name = "newLifetime")] + pub new_lifetime: Option, +} + +pub mod v2_0 { + use tycho_types::abi::NamedAbiType; + + use super::*; + + pub fn get_update_requests() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getUpdateRequests", + inputs: vec![] as Vec, + outputs: { + let param_types = match UpdateTransaction::abi_type() { + AbiType::Tuple(params) => { + let mut vec = params.iter().cloned().collect::>(); + if let Some(last) = vec.last_mut() { + if let NamedAbiType { ty: AbiType::Optional(param), name } = last { + if let AbiType::Uint(_) = param.as_ref() { + *last = AbiType::Optional(Arc::new(AbiType::Uint(64))).named(&*name.clone()); + } + } + } + AbiType::Tuple(Arc::<[NamedAbiType]>::from(vec)) + } + other => other, + }; + + vec![ + AbiType::Array(Arc::new(param_types)).named("updates") + ] + }, + } + } +} + +pub mod v2_1 { + use super::*; + + pub fn get_update_requests() -> &'static Function { + declare_function! { + abi: v2_3, + header: [pubkey, time, expire], + name: "getUpdateRequests", + inputs: vec![] as Vec, + outputs: vec![ + AbiType::Array(Arc::new(UpdateTransaction::abi_type())).named("updates") + ], + } + } +}