diff --git a/Cargo.lock b/Cargo.lock index e669576e..950c93e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler2" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" - [[package]] name = "ahash" version = "0.7.6" @@ -34,30 +19,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" -dependencies = [ - "libc", -] - [[package]] name = "anyhow" version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" -[[package]] -name = "arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" -dependencies = [ - "derive_arbitrary", -] - [[package]] name = "arrayvec" version = "0.7.4" @@ -70,21 +37,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "base16ct" version = "0.1.1" @@ -97,12 +49,6 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" -[[package]] -name = "base32" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ce669cd6c8588f79e15cf450314f9638f967fc5770ff1c7c1deb0925ea7cfa" - [[package]] name = "base64" version = "0.11.0" @@ -121,12 +67,6 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "base64ct" version = "1.6.0" @@ -214,12 +154,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - [[package]] name = "byte-slice-cast" version = "1.2.2" @@ -241,31 +175,12 @@ dependencies = [ "serde", ] -[[package]] -name = "bytes-lit" -version = "0.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0adabf37211a5276e46335feabcbb1530c95eb3fdf85f324c7db942770aa025d" -dependencies = [ - "num-bigint", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" -[[package]] -name = "centralized-connection" -version = "0.0.0" -dependencies = [ - "soroban-sdk", -] - [[package]] name = "cfg-if" version = "1.0.0" @@ -279,10 +194,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ "android-tzdata", - "iana-time-zone", "num-traits", - "serde", - "winapi", ] [[package]] @@ -321,7 +233,7 @@ dependencies = [ "displaydoc", "dyn-clone", "hex", - "hex-literal 0.3.4", + "hex-literal", "ibc-proto", "ics23", "pbjson", @@ -347,12 +259,6 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - [[package]] name = "cosmwasm" version = "0.7.2" @@ -453,17 +359,6 @@ dependencies = [ "libc", ] -[[package]] -name = "crate-git-revision" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c521bf1f43d31ed2f73441775ed31935d77901cb3451e44b38a1c1612fcbaf98" -dependencies = [ - "serde", - "serde_derive", - "serde_json", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -504,16 +399,6 @@ dependencies = [ "typenum", ] -[[package]] -name = "ctor" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" -dependencies = [ - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "curve25519-dalek" version = "3.2.0" @@ -527,33 +412,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "curve25519-dalek" -version = "4.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" -dependencies = [ - "cfg-if", - "cpufeatures", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", - "rustc_version", - "subtle", - "zeroize", -] - -[[package]] -name = "curve25519-dalek-derive" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "curve25519-dalek-ng" version = "4.1.1" @@ -705,7 +563,7 @@ dependencies = [ "cw-storage-plus 0.15.1", "cw-utils 0.15.1", "derivative", - "itertools 0.10.5", + "itertools", "prost 0.9.0", "schemars 0.8.12", "serde", @@ -723,7 +581,7 @@ dependencies = [ "cw-storage-plus 1.0.1", "cw-utils 1.0.1", "derivative", - "itertools 0.10.5", + "itertools", "k256 0.11.6", "prost 0.9.0", "schemars 0.8.12", @@ -951,41 +809,6 @@ dependencies = [ "serde", ] -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2 1.0.70", - "quote 1.0.33", - "strsim", - "syn 2.0.42", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "debug_print" version = "1.0.0" @@ -1023,17 +846,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "derive_arbitrary" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "derive_more" version = "0.99.17" @@ -1083,12 +895,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - [[package]] name = "dyn-clone" version = "1.0.11" @@ -1130,16 +936,6 @@ dependencies = [ "signature 1.6.4", ] -[[package]] -name = "ed25519" -version = "2.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" -dependencies = [ - "pkcs8 0.10.2", - "signature 2.2.0", -] - [[package]] name = "ed25519-consensus" version = "2.1.0" @@ -1153,28 +949,13 @@ dependencies = [ "zeroize", ] -[[package]] -name = "ed25519-dalek" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" -dependencies = [ - "curve25519-dalek 4.1.3", - "ed25519 2.2.3", - "rand_core 0.6.4", - "serde", - "sha2 0.10.8", - "subtle", - "zeroize", -] - [[package]] name = "ed25519-zebra" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c24f403d068ad0b359e577a77f92392118be3f3c927538f2bb544a5ecd828c6" dependencies = [ - "curve25519-dalek 3.2.0", + "curve25519-dalek", "hashbrown 0.12.3", "hex", "rand_core 0.6.4", @@ -1264,18 +1045,6 @@ dependencies = [ "libc", ] -[[package]] -name = "escape-bytes" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bfcf67fea2815c2fc3b90873fae90957be12ff417335dfadc7f52927feb03b2" - -[[package]] -name = "ethnum" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" - [[package]] name = "fastrand" version = "1.9.0" @@ -1305,12 +1074,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "fiat-crypto" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" - [[package]] name = "fixed-hash" version = "0.8.0" @@ -1335,12 +1098,6 @@ dependencies = [ "paste", ] -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - [[package]] name = "forward_ref" version = "1.0.0" @@ -1426,18 +1183,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", - "js-sys", "libc", "wasi", - "wasm-bindgen", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "group" version = "0.12.1" @@ -1492,9 +1241,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hex-buffer-serde" @@ -1512,12 +1258,6 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - [[package]] name = "hmac" version = "0.12.1" @@ -1527,29 +1267,6 @@ dependencies = [ "digest 0.10.7", ] -[[package]] -name = "iana-time-zone" -version = "0.1.61" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - [[package]] name = "ibc" version = "0.32.0" @@ -1616,12 +1333,6 @@ dependencies = [ "sha3", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "impl-serde" version = "0.4.0" @@ -1650,7 +1361,6 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", - "serde", ] [[package]] @@ -1661,15 +1371,8 @@ checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" dependencies = [ "equivalent", "hashbrown 0.14.3", - "serde", ] -[[package]] -name = "indexmap-nostd" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" - [[package]] name = "instant" version = "0.1.12" @@ -1699,30 +1402,12 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - [[package]] name = "itoa" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - [[package]] name = "k256" version = "0.11.6" @@ -1770,12 +1455,6 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - [[package]] name = "linux-raw-sys" version = "0.3.8" @@ -1794,41 +1473,12 @@ version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" -[[package]] -name = "miniz_oxide" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" -dependencies = [ - "adler2", -] - -[[package]] -name = "mock-dapp-multi" -version = "0.0.0" -dependencies = [ - "soroban-rlp", - "soroban-sdk", - "soroban-xcall-lib", - "xcall", -] - [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - [[package]] name = "num-derive" version = "0.3.3" @@ -1840,26 +1490,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "num-derive" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.19" @@ -1869,15 +1499,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.20.2" @@ -1890,18 +1511,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "p256" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" -dependencies = [ - "ecdsa 0.16.9", - "elliptic-curve 0.13.8", - "primeorder", - "sha2 0.10.8", -] - [[package]] name = "parity-scale-codec" version = "3.6.9" @@ -1949,7 +1558,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bdbb7b706f2afc610f3853550cdbbf6372fd324824a087806bd4480ea4996e24" dependencies = [ "heck", - "itertools 0.10.5", + "itertools", "prost 0.11.9", "prost-types", ] @@ -2011,15 +1620,6 @@ dependencies = [ "spki 0.7.3", ] -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - [[package]] name = "prettyplease" version = "0.1.25" @@ -2030,25 +1630,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "prettyplease" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" -dependencies = [ - "proc-macro2 1.0.70", - "syn 2.0.42", -] - -[[package]] -name = "primeorder" -version = "0.13.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" -dependencies = [ - "elliptic-curve 0.13.8", -] - [[package]] name = "primitive-types" version = "0.12.2" @@ -2135,12 +1716,12 @@ checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck", - "itertools 0.10.5", + "itertools", "lazy_static", "log", "multimap", "petgraph", - "prettyplease 0.1.25", + "prettyplease", "prost 0.11.9", "prost-types", "regex", @@ -2156,7 +1737,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", @@ -2169,7 +1750,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", - "itertools 0.10.5", + "itertools", "proc-macro2 1.0.70", "quote 1.0.33", "syn 1.0.109", @@ -2202,27 +1783,6 @@ dependencies = [ "proc-macro2 1.0.70", ] -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -2303,27 +1863,12 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hex" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" -[[package]] -name = "rustc_version" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" -dependencies = [ - "semver", -] - [[package]] name = "rustix" version = "0.37.19" @@ -2593,36 +2138,6 @@ dependencies = [ "syn 2.0.42", ] -[[package]] -name = "serde_with" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" -dependencies = [ - "base64 0.22.1", - "chrono", - "hex", - "indexmap 1.9.3", - "indexmap 2.1.0", - "serde", - "serde_derive", - "serde_json", - "serde_with_macros", - "time", -] - -[[package]] -name = "serde_with_macros" -version = "3.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" -dependencies = [ - "darling", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "sha2" version = "0.9.9" @@ -2677,12 +2192,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - [[package]] name = "snafu" version = "0.5.0" @@ -2934,39 +2443,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "stellar-strkey" -version = "0.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12d2bf45e114117ea91d820a846fd1afbe3ba7d717988fee094ce8227a3bf8bd" -dependencies = [ - "base32", - "crate-git-revision", - "thiserror", -] - -[[package]] -name = "stellar-xdr" -version = "21.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" -dependencies = [ - "arbitrary", - "base64 0.13.1", - "crate-git-revision", - "escape-bytes", - "hex", - "serde", - "serde_with", - "stellar-strkey", -] - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - [[package]] name = "strum" version = "0.25.0" @@ -3061,7 +2537,7 @@ checksum = "cda53c85447577769cdfc94c10a56f34afef2c00e4108badb57fce6b1a0c75eb" dependencies = [ "bytes", "digest 0.10.7", - "ed25519 1.5.3", + "ed25519", "ed25519-consensus", "flex-error", "futures", @@ -3103,7 +2579,7 @@ checksum = "c943f78c929cdf14553842f705f2c30324bc35b9179caaa5c9b80620f60652e6" dependencies = [ "bytes", "flex-error", - "num-derive 0.3.3", + "num-derive", "num-traits", "prost 0.11.9", "prost-types", @@ -3155,8 +2631,6 @@ version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ - "itoa", - "serde", "time-core", "time-macros", ] @@ -3277,98 +2751,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote 1.0.33", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wasmi_arena" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073" - -[[package]] -name = "wasmi_core" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf1a7db34bff95b85c261002720c00c3a6168256dcb93041d3fa2054d19856a" -dependencies = [ - "downcast-rs", - "libm", - "num-traits", - "paste", -] - -[[package]] -name = "wasmparser" -version = "0.116.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" -dependencies = [ - "indexmap 2.1.0", - "semver", -] - -[[package]] -name = "wasmparser-nostd" -version = "0.100.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5a015fe95f3504a94bb1462c717aae75253e39b9dd6c3fb1062c934535c64aa" -dependencies = [ - "indexmap-nostd", -] - [[package]] name = "which" version = "4.4.0" @@ -3380,37 +2762,6 @@ dependencies = [ "once_cell", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[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-sys" version = "0.45.0" @@ -3459,22 +2810,6 @@ dependencies = [ "windows_x86_64_msvc 0.48.0", ] -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -3487,12 +2822,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - [[package]] name = "windows_aarch64_msvc" version = "0.42.2" @@ -3505,12 +2834,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - [[package]] name = "windows_i686_gnu" version = "0.42.2" @@ -3523,18 +2846,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - [[package]] name = "windows_i686_msvc" version = "0.42.2" @@ -3547,12 +2858,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - [[package]] name = "windows_x86_64_gnu" version = "0.42.2" @@ -3565,12 +2870,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" @@ -3583,12 +2882,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - [[package]] name = "windows_x86_64_msvc" version = "0.42.2" @@ -3601,12 +2894,6 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - [[package]] name = "winnow" version = "0.5.30" @@ -3616,36 +2903,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "xcall" -version = "0.1.0" -dependencies = [ - "soroban-rlp", - "soroban-sdk", - "soroban-xcall-lib", -] - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2 1.0.70", - "quote 1.0.33", - "syn 2.0.42", -] - [[package]] name = "zeroize" version = "1.8.1" diff --git a/Cargo.toml b/Cargo.toml index 8ee82d52..19f8128c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,6 @@ [workspace] members = [ - "contracts/cosmwasm-vm/*", - "contracts/soroban/contracts/*", - "contracts/soroban/libs/*" + "contracts/cosmwasm-vm/*" ] [workspace.package] @@ -38,8 +36,6 @@ cw-common={ git="https://github.com/icon-project/IBC-Integration.git", branch = cw-mock-dapp = {path="contracts/cosmwasm-vm/cw-mock-dapp"} cw-mock-dapp-multi = { path="contracts/cosmwasm-vm/cw-mock-dapp-multi"} -soroban-sdk = "21.6.0" - [profile.release] opt-level = 'z' debug = false diff --git a/contracts/javascore/aggregator/build.gradle b/contracts/javascore/aggregator/build.gradle new file mode 100644 index 00000000..426026ca --- /dev/null +++ b/contracts/javascore/aggregator/build.gradle @@ -0,0 +1,41 @@ +version = '0.1.0' + +dependencies { + testImplementation 'foundation.icon:javaee-unittest:0.11.1' + testImplementation project(':test-lib') +} + +test { + useJUnitPlatform() + finalizedBy jacocoTestReport +} + +optimizedJar { + mainClassName = 'relay.aggregator.RelayAggregator' + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from { + configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } + } +} + +deployJar { + endpoints { + lisbon { + uri = 'https://lisbon.net.solidwallet.io/api/v3' + nid = 0x2 + } + local { + uri = 'http://localhost:9082/api/v3' + nid = 0x3 + } + mainnet { + uri = 'https://ctz.solidwallet.io/api/v3' + nid = 0x1 + } + } + keystore = rootProject.hasProperty('keystoreName') ? "$keystoreName" : '' + password = rootProject.hasProperty('keystorePass') ? "$keystorePass" : '' + parameters { + arg('_admin', "hxb6b5791be0b5ef67063b3c10b840fb81514db2fd") + } +} \ No newline at end of file diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java new file mode 100644 index 00000000..0568de37 --- /dev/null +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/Packet.java @@ -0,0 +1,176 @@ +package relay.aggregator; + +import java.math.BigInteger; + +import score.Context; +import score.ObjectReader; +import score.ObjectWriter; + +public class Packet { + + /** + * The ID of the source network (chain) from where the packet originated. + */ + private final String srcNetwork; + + /** + * The contract address on the source network (chain). + */ + private final String srcContractAddress; + + /** + * The sequence number of the packet in the source network (chain). + */ + private final BigInteger srcSn; + + /** + * The source height of the packet in the source network (chain). + */ + private final BigInteger srcHeight; + + /** + * The ID of the destination network (chain) where the packet is being sent. + */ + private final String dstNetwork; + + /** + * The contract address on the destination network (chain). + */ + private final String dstContractAddress; + + /** + * The payload data associated with this packet. + */ + private final byte[] data; + + /** + * Constructs a new {@code Packet} object with the specified {@code PacketID}, + * destination network, and data. + * All parameters must be non-null. + * + * @param id the unique identifier for the packet. + * @param dstNetwork the ID of the destination network (chain). + * @param data the payload data for this packet. + * @throws IllegalArgumentException if {@code srcNetwork}, + * {@code srcContractAddress}, {@code srcSn}, + * {@code srcHeight}, + * {@code dstNetwork}, + * {@code dstContractAddress}, or {@code data} + * is + * {@code null}. + */ + public Packet(String srcNetwork, String srcContractAddress, BigInteger srcSn, BigInteger srcHeight, + String dstNetwork, String dstContractAddress, + byte[] data) { + Boolean isIllegalArg = srcNetwork == null || srcContractAddress == null || srcSn == null + || srcHeight == null || dstNetwork == null || dstContractAddress == null || data == null; + Context.require(!isIllegalArg, + "srcNetwork, contractAddress, srcSn, srcHeight, dstNetwork, and data cannot be null"); + if (isIllegalArg) { + } + this.srcNetwork = srcNetwork; + this.srcContractAddress = srcContractAddress; + this.srcSn = srcSn; + this.srcHeight = srcHeight; + this.dstNetwork = dstNetwork; + this.dstContractAddress = dstContractAddress; + this.data = data; + } + + public String getId() { + return createId(this.srcNetwork, this.srcContractAddress, this.srcSn); + } + + public static String createId(String srcNetwork, String contractAddress, BigInteger srcSn) { + return srcNetwork + "/" + contractAddress + "/" + srcSn.toString(); + } + + /** + * Returns the source network (chain) from where the packet originated. + * + * @return the source network ID. + */ + public String getSrcNetwork() { + return srcNetwork; + } + + /** + * Returns the contract address on the source network (chain). + * + * @return the source contract address. + */ + public String getSrcContractAddress() { + return srcContractAddress; + } + + /** + * Returns the sequence number of the packet in the source network (chain). + * + * @return the sequence number. + */ + public BigInteger getSrcSn() { + return srcSn; + } + + /** + * Returns the height of the packet in the source network (chain). + * + * @return the source height. + */ + public BigInteger getSrcHeight() { + return srcHeight; + } + + /** + * Returns the destination network (chain) where the packet is being sent. + * + * @return the destination network ID. + */ + public String getDstNetwork() { + return dstNetwork; + } + + /** + * Returns the contract address on the destination network (chain). + * + * @return the destination contract address. + */ + public String getDstContractAddress() { + return dstContractAddress; + } + + /** + * Returns a copy of the data associated with this packet. + * + * @return a byte array containing the packet data. + */ + public byte[] getData() { + return data; + } + + public static void writeObject(ObjectWriter w, Packet p) { + w.beginList(7); + w.write(p.srcNetwork); + w.write(p.srcContractAddress); + w.write(p.srcSn); + w.write(p.srcHeight); + w.write(p.dstNetwork); + w.write(p.dstContractAddress); + w.writeNullable(p.data); + w.end(); + } + + public static Packet readObject(ObjectReader r) { + r.beginList(); + Packet p = new Packet( + r.readString(), + r.readString(), + r.readBigInteger(), + r.readBigInteger(), + r.readString(), + r.readString(), + r.readNullable(byte[].class)); + r.end(); + return p; + } +} diff --git a/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java new file mode 100644 index 00000000..ec475a99 --- /dev/null +++ b/contracts/javascore/aggregator/src/main/java/relay/aggregator/RelayAggregator.java @@ -0,0 +1,317 @@ +/* + * Copyright 2022 ICON Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package relay.aggregator; + +import java.math.BigInteger; + +import score.Context; +import score.Address; +import score.ArrayDB; +import score.VarDB; +import score.DictDB; +import score.BranchDB; +import score.ByteArrayObjectWriter; +import score.annotation.EventLog; +import score.annotation.External; +import score.ObjectReader; +import scorex.util.ArrayList; +import scorex.util.HashMap; + +public class RelayAggregator { + private final Integer DEFAULT_SIGNATURE_THRESHOLD = 1; + + private final VarDB signatureThreshold = Context.newVarDB("signatureThreshold", Integer.class); + + private final VarDB
admin = Context.newVarDB("admin", Address.class); + + private final ArrayDB
relayers = Context.newArrayDB("relayers", Address.class); + private final DictDB relayersLookup = Context.newDictDB("relayersLookup", Boolean.class); + + private final DictDB packets = Context.newDictDB("packets", Packet.class); + + private final BranchDB> signatures = Context.newBranchDB("signatures", + byte[].class); + + public RelayAggregator(Address _admin) { + if (admin.get() == null) { + admin.set(_admin); + signatureThreshold.set(DEFAULT_SIGNATURE_THRESHOLD); + addRelayer(_admin); + } + } + + @External + public void setAdmin(Address _admin) { + adminOnly(); + + Context.require(admin.get() != _admin, "admin already set"); + + // add new admin as relayer + addRelayer(_admin); + + // remove old admin from relayer list + removeRelayer(admin.get()); + + admin.set(_admin); + } + + @External(readonly = true) + public Address getAdmin() { + return admin.get(); + } + + @External + public void setSignatureThreshold(int threshold) { + adminOnly(); + Context.require(threshold > 0 && threshold <= relayers.size(), + "threshold value should be at least 1 and not greater than relayers size"); + signatureThreshold.set(threshold); + } + + @External(readonly = true) + public int getSignatureThreshold() { + return signatureThreshold.get(); + } + + @External(readonly = true) + public Address[] getRelayers() { + Address[] rlrs = new Address[relayers.size()]; + for (int i = 0; i < relayers.size(); i++) { + rlrs[i] = relayers.get(i); + } + return rlrs; + } + + @External + public void setRelayers(Address[] newRelayers, int threshold) { + adminOnly(); + + if (newRelayers.length > 0) { + HashMap newRelayersMap = new HashMap(); + for (Address newRelayer : newRelayers) { + newRelayersMap.put(newRelayer, true); + addRelayer(newRelayer); + } + + Address adminAdrr = admin.get(); + for (int i = 0; i < relayers.size(); i++) { + Address oldRelayer = relayers.get(i); + if (!oldRelayer.equals(adminAdrr) && !newRelayersMap.containsKey(oldRelayer)) { + removeRelayer(oldRelayer); + } + } + } + + Context.require(threshold > 0 && threshold <= relayers.size(), + "threshold value should be at least 1 and not greater than relayers size"); + + signatureThreshold.set(threshold); + } + + @External(readonly = true) + public boolean packetSubmitted( + Address relayer, + String srcNetwork, + String srcContractAddress, + BigInteger srcSn) { + String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn); + byte[] existingSign = signatures.at(pktID).get(relayer); + return existingSign != null; + } + + @External + public void submitPacket( + String srcNetwork, + String srcContractAddress, + BigInteger srcSn, + BigInteger srcHeight, + String dstNetwork, + String dstContractAddress, + byte[] data, + byte[] signature) { + + relayersOnly(); + + Packet pkt = new Packet(srcNetwork, srcContractAddress, srcSn, srcHeight, dstNetwork, dstContractAddress, data); + String pktID = pkt.getId(); + + if (packets.get(pktID) == null) { + packets.set(pktID, pkt); + if (signatureThreshold.get() > 1) { + PacketRegistered( + pkt.getSrcNetwork(), + pkt.getSrcContractAddress(), + pkt.getSrcSn(), + pkt.getSrcHeight(), + pkt.getDstNetwork(), + pkt.getDstContractAddress(), + pkt.getData()); + } + + } + + byte[] existingSign = signatures.at(pktID).get(Context.getCaller()); + Context.require(existingSign == null, "Signature already exists"); + + setSignature(pktID, Context.getCaller(), signature); + + if (signatureThresholdReached(pktID)) { + byte[][] sigs = getSignatures(srcNetwork, srcContractAddress, srcSn); + byte[] encodedSigs = serializeSignatures(sigs); + PacketAcknowledged( + pkt.getSrcNetwork(), + pkt.getSrcContractAddress(), + pkt.getSrcSn(), + pkt.getSrcHeight(), + pkt.getDstNetwork(), + pkt.getDstContractAddress(), + pkt.getData(), + encodedSigs); + removePacket(pktID); + } + } + + private byte[][] getSignatures(String srcNetwork, String srcContractAddress, BigInteger srcSn) { + String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn); + DictDB signDict = signatures.at(pktID); + ArrayList signatureList = new ArrayList(); + + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + byte[] sign = signDict.get(relayer); + if (sign != null) { + signatureList.add(sign); + } + } + + byte[][] sigs = new byte[signatureList.size()][]; + for (int i = 0; i < signatureList.size(); i++) { + sigs[i] = signatureList.get(i); + } + return sigs; + } + + protected void setSignature(String pktID, Address addr, byte[] sign) { + signatures.at(pktID).set(addr, sign); + } + + protected static byte[] serializeSignatures(byte[][] sigs) { + ByteArrayObjectWriter w = Context.newByteArrayObjectWriter("RLPn"); + w.beginList(sigs.length); + + for (byte[] sig : sigs) { + w.write(sig); + } + + w.end(); + return w.toByteArray(); + } + + protected static byte[][] deserializeSignatures(byte[] encodedSigs) { + ObjectReader r = Context.newByteArrayObjectReader("RLPn", encodedSigs); + + ArrayList sigList = new ArrayList<>(); + + r.beginList(); + while (r.hasNext()) { + sigList.add(r.readByteArray()); + } + r.end(); + + byte[][] sigs = new byte[sigList.size()][]; + for (int i = 0; i < sigList.size(); i++) { + sigs[i] = sigList.get(i); + } + + return sigs; + } + + private void adminOnly() { + Context.require(Context.getCaller().equals(admin.get()), "Unauthorized: caller is not the leader relayer"); + } + + private void relayersOnly() { + Address caller = Context.getCaller(); + Boolean isRelayer = relayersLookup.get(caller); + Context.require(isRelayer != null && isRelayer, "Unauthorized: caller is not a registered relayer"); + } + + private void addRelayer(Address newRelayer) { + if (relayersLookup.get(newRelayer) == null) { + relayers.add(newRelayer); + relayersLookup.set(newRelayer, true); + } + } + + private void removeRelayer(Address oldRelayer) { + if (relayersLookup.get(oldRelayer)) { + relayersLookup.set(oldRelayer, null); + Address top = relayers.pop(); + for (int i = 0; i < relayers.size(); i++) { + if (oldRelayer.equals(relayers.get(i))) { + relayers.set(i, top); + break; + } + } + } + } + + private Boolean signatureThresholdReached(String pktID) { + int noOfSignatures = 0; + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + byte[] relayerSign = signatures.at(pktID).get(relayer); + if (relayerSign != null) { + noOfSignatures++; + } + } + return noOfSignatures >= signatureThreshold.get(); + } + + private void removePacket(String pktID) { + packets.set(pktID, null); + DictDB signDict = signatures.at(pktID); + + for (int i = 0; i < relayers.size(); i++) { + Address relayer = relayers.get(i); + signDict.set(relayer, null); + } + } + + @EventLog(indexed = 2) + public void PacketRegistered( + String srcNetwork, + String srcContractAddress, + BigInteger srcSn, + BigInteger srcHeight, + String dstNetwork, + String dstContractAddress, + byte[] data) { + } + + @EventLog(indexed = 2) + public void PacketAcknowledged( + String srcNetwork, + String srcContractAddress, + BigInteger srcSn, + BigInteger srcHeight, + String dstNetwork, + String dstContractAddress, + byte[] data, + byte[] signatures) { + } +} \ No newline at end of file diff --git a/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java new file mode 100644 index 00000000..0da3391f --- /dev/null +++ b/contracts/javascore/aggregator/src/test/java/relay/aggregator/RelayAggregatorTest.java @@ -0,0 +1,367 @@ +package relay.aggregator; + +import java.math.BigInteger; +import java.util.Arrays; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.function.Executable; + +import score.Address; +import score.Context; +import score.UserRevertedException; +import scorex.util.HashSet; + +import com.iconloop.score.test.Account; +import com.iconloop.score.test.Score; +import com.iconloop.score.test.ServiceManager; +import com.iconloop.score.test.TestBase; + +import foundation.icon.icx.KeyWallet; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +class RelayAggregatorTest extends TestBase { + private final ServiceManager sm = getServiceManager(); + + private KeyWallet admin; + private Account adminAc; + + private KeyWallet relayerOne; + private Account relayerOneAc; + + private KeyWallet relayerTwo; + private Account relayerTwoAc; + + private KeyWallet relayerThree; + private Account relayerThreeAc; + + private KeyWallet relayerFour; + private Account relayerFourAc; + + private Score aggregator; + private RelayAggregator aggregatorSpy; + + @BeforeEach + void setup() throws Exception { + admin = KeyWallet.create(); + adminAc = sm.getAccount(Address.fromString(admin.getAddress().toString())); + + relayerOne = KeyWallet.create(); + relayerOneAc = sm.getAccount(Address.fromString(relayerOne.getAddress().toString())); + + relayerTwo = KeyWallet.create(); + relayerTwoAc = sm.getAccount(Address.fromString(relayerTwo.getAddress().toString())); + + relayerThree = KeyWallet.create(); + relayerThreeAc = sm.getAccount(Address.fromString(relayerThree.getAddress().toString())); + + relayerFour = KeyWallet.create(); + relayerFourAc = sm.getAccount(Address.fromString(relayerFour.getAddress().toString())); + + aggregator = sm.deploy(adminAc, RelayAggregator.class, adminAc.getAddress()); + + Address[] relayers = new Address[] { adminAc.getAddress(), relayerOneAc.getAddress(), relayerTwoAc.getAddress(), + relayerThreeAc.getAddress() }; + + aggregator.invoke(adminAc, "setRelayers", (Object) relayers, 2); + + aggregatorSpy = (RelayAggregator) spy(aggregator.getInstance()); + aggregator.setInstance(aggregatorSpy); + } + + @Test + public void testSetAdmin() { + Address oldAdmin = (Address) aggregator.call("getAdmin"); + + Account newAdminAc = sm.createAccount(); + aggregator.invoke(adminAc, "setAdmin", newAdminAc.getAddress()); + + Address newAdmin = (Address) aggregator.call("getAdmin"); + assertEquals(newAdminAc.getAddress(), newAdmin); + + Address[] relayers = (Address[]) aggregator.call("getRelayers"); + + boolean containsNewAdmin = Arrays.asList(relayers).contains(newAdmin); + boolean containsOldAdmin = Arrays.asList(relayers).contains(oldAdmin); + + assertTrue(containsNewAdmin); + assertFalse(containsOldAdmin); + } + + @Test + public void testSetAdmin_unauthorized() { + Account normalAc = sm.createAccount(); + Account newAdminAc = sm.createAccount(); + + Executable action = () -> aggregator.invoke(normalAc, "setAdmin", newAdminAc.getAddress()); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", e.getMessage()); + } + + @Test + public void testSetSignatureThreshold() { + int threshold = 3; + aggregator.invoke(adminAc, "setSignatureThreshold", threshold); + + Integer result = (Integer) aggregator.call("getSignatureThreshold"); + assertEquals(threshold, result); + } + + @Test + public void testSetSignatureThreshold_unauthorised() { + int threshold = 3; + + Executable action = () -> aggregator.invoke(relayerOneAc, + "setSignatureThreshold", threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", + e.getMessage()); + } + + @Test + public void testSetRelayers() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + Integer threshold = 3; + aggregator.invoke(adminAc, "setRelayers", (Object) newRelayers, threshold); + + Address[] updatedRelayers = (Address[]) aggregator.call("getRelayers"); + + Address[] expectedRelayers = new Address[] { adminAc.getAddress(), relayerThreeAc.getAddress(), + relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + HashSet
updatedRelayersSet = new HashSet
(); + for (Address rlr : updatedRelayers) { + updatedRelayersSet.add(rlr); + } + + HashSet
expectedRelayersSet = new HashSet
(); + for (Address rlr : expectedRelayers) { + expectedRelayersSet.add(rlr); + } + + assertEquals(expectedRelayersSet, updatedRelayersSet); + + Integer result = (Integer) aggregator.call("getSignatureThreshold"); + assertEquals(threshold, result); + } + + @Test + public void testSetRelayers_unauthorized() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + Integer threshold = 3; + Executable action = () -> aggregator.invoke(relayerOneAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): Unauthorized: caller is not the leader relayer", + e.getMessage()); + + } + + @Test + public void testSetRelayers_invalidThreshold() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + Integer threshold = 5; + Executable action = () -> aggregator.invoke(adminAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", + e.getMessage()); + + } + + @Test + public void testSetRelayers_invalidThresholdZero() { + Account relayerFiveAc = sm.createAccount(); + Address[] newRelayers = new Address[] { relayerThreeAc.getAddress(), relayerFourAc.getAddress(), + relayerFiveAc.getAddress() }; + + Integer threshold = 0; + Executable action = () -> aggregator.invoke(adminAc, "setRelayers", + (Object) newRelayers, threshold); + UserRevertedException e = assertThrows(UserRevertedException.class, action); + + assertEquals("Reverted(0): threshold value should be at least 1 and not greater than relayers size", + e.getMessage()); + + } + + @Test + public void testPacketSubmitted_true() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + aggregator.invoke(adminAc, "setSignatureThreshold", 2); + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash); + + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, + sign); + + boolean submitted = (boolean) aggregator.call("packetSubmitted", + relayerOneAc.getAddress(), srcNetwork, + srcContractAddress, srcSn); + assertEquals(submitted, true); + } + + @Test + public void testPacketSubmitted_false() throws Exception { + String srcNetwork = "0x2.icon"; + BigInteger srcSn = BigInteger.ONE; + String srcContractAddress = "hxjuiod"; + + boolean submitted = (boolean) aggregator.call("packetSubmitted", + relayerOneAc.getAddress(), srcNetwork, + srcContractAddress, srcSn); + assertEquals(submitted, false); + } + + @Test + public void testSubmitPacket() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + aggregator.invoke(adminAc, "setSignatureThreshold", 2); + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash); + + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, + sign); + + String pktID = Packet.createId(srcNetwork, srcContractAddress, srcSn); + verify(aggregatorSpy).PacketRegistered(srcNetwork, srcContractAddress, srcSn, + srcHeight, dstNetwork, + dstContractAddress, data); + verify(aggregatorSpy).setSignature(pktID, relayerOneAc.getAddress(), sign); + } + + @Test + public void testSubmitPacket_thresholdReached() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + aggregator.invoke(adminAc, "setSignatureThreshold", 2); + + byte[] dataHash = Context.hash("sha-256", data); + + byte[] signAdmin = admin.sign(dataHash); + aggregator.invoke(adminAc, "submitPacket", srcNetwork, srcContractAddress, + srcSn, srcHeight, dstNetwork, + dstContractAddress, data, + signAdmin); + + byte[] signOne = relayerOne.sign(dataHash); + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, + data, + signOne); + + byte[][] sigs = new byte[2][]; + sigs[0] = signAdmin; + sigs[1] = signOne; + + byte[] encodedSigs = RelayAggregator.serializeSignatures(sigs); + byte[][] decodedSigs = RelayAggregator.deserializeSignatures(encodedSigs); + + assertArrayEquals(signAdmin, decodedSigs[0]); + assertArrayEquals(signOne, decodedSigs[1]); + + verify(aggregatorSpy).PacketAcknowledged(srcNetwork, srcContractAddress, + srcSn, srcHeight, dstNetwork, + dstContractAddress, data, + encodedSigs); + } + + @Test + public void testSubmitPacket_unauthorized() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerFour.sign(dataHash); + + Executable action = () -> aggregator.invoke(relayerFourAc, "submitPacket", + srcNetwork, srcContractAddress, + srcSn, + srcHeight, dstNetwork, dstContractAddress, data, sign); + + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Unauthorized: caller is not a registered relayer", + e.getMessage()); + } + + @Test + public void testSubmitPacket_duplicate() throws Exception { + String srcNetwork = "0x2.icon"; + String dstNetwork = "sui"; + BigInteger srcSn = BigInteger.ONE; + BigInteger srcHeight = BigInteger.ONE; + String srcContractAddress = "hxjuiod"; + String dstContractAddress = "hxjuiod"; + byte[] data = new byte[] { 0x01, 0x02 }; + + aggregator.invoke(adminAc, "setSignatureThreshold", 2); + + byte[] dataHash = Context.hash("sha-256", data); + byte[] sign = relayerOne.sign(dataHash); + + aggregator.invoke(relayerOneAc, "submitPacket", srcNetwork, + srcContractAddress, srcSn, srcHeight, dstNetwork, + dstContractAddress, data, sign); + + Executable action = () -> aggregator.invoke(relayerOneAc, "submitPacket", + srcNetwork, srcContractAddress, srcSn, + srcHeight, dstNetwork, dstContractAddress, + data, sign); + ; + UserRevertedException e = assertThrows(UserRevertedException.class, action); + assertEquals("Reverted(0): Signature already exists", e.getMessage()); + } +} diff --git a/contracts/javascore/keystore.json b/contracts/javascore/keystore.json new file mode 100644 index 00000000..4632fa28 --- /dev/null +++ b/contracts/javascore/keystore.json @@ -0,0 +1 @@ +{"address":"hx5f7afdb96154bbe9dbeb2dde3a58afcb1efbfaff","id":"7a8237b8-a61f-4501-9896-b96a39e9c72b","version":3,"coinType":"icx","crypto":{"cipher":"aes-128-ctr","cipherparams":{"iv":"c2e5a31a638992a36e3afe96d38ccfda"},"ciphertext":"68d61f1325e1198ca7fd0ee961997e31541ead36ff24b29fc04e6f39cb05eeac","kdf":"scrypt","kdfparams":{"dklen":32,"n":65536,"r":8,"p":1,"salt":"53663f4c4320aff5"},"mac":"388b1466f2a75736fee1f576e2307da2fcaa0cf3d00b968d3c45a98eb9c06f84"}} \ No newline at end of file diff --git a/contracts/javascore/settings.gradle b/contracts/javascore/settings.gradle index 6e6fdcdf..243072ad 100644 --- a/contracts/javascore/settings.gradle +++ b/contracts/javascore/settings.gradle @@ -3,7 +3,8 @@ include( 'test-lib', 'xcall', 'xcall-lib', - 'centralized-connection' + 'centralized-connection', + 'aggregator' ) include(':dapp-simple') diff --git a/contracts/solana/libs/xcall-lib/src/xcall_type.rs b/contracts/solana/libs/xcall-lib/src/xcall_type.rs index 3ec5cf07..aaececfa 100644 --- a/contracts/solana/libs/xcall-lib/src/xcall_type.rs +++ b/contracts/solana/libs/xcall-lib/src/xcall_type.rs @@ -25,12 +25,14 @@ pub struct HandleMessageArgs { pub from_nid: String, pub message: Vec, pub sequence_no: u128, + pub conn_sn: u128, } #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] pub struct HandleRequestArgs { pub from_nid: String, pub msg_payload: Vec, + pub conn_sn: u128, } #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] @@ -38,6 +40,7 @@ pub struct HandleResultArgs { pub from_nid: String, pub msg_payload: Vec, pub sequence_no: u128, + pub conn_sn: u128, } #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] @@ -48,4 +51,7 @@ pub struct HandleErrorArgs { #[derive(Debug, Clone, AnchorSerialize, AnchorDeserialize)] pub struct HandleForcedRollback { pub req_id: u128, + pub from_nid: String, + pub conn_sn: u128, + pub connection: Pubkey, } diff --git a/contracts/solana/programs/centralized-connection/src/helper.rs b/contracts/solana/programs/centralized-connection/src/helper.rs index 927c17a5..96ead7ab 100644 --- a/contracts/solana/programs/centralized-connection/src/helper.rs +++ b/contracts/solana/programs/centralized-connection/src/helper.rs @@ -46,12 +46,14 @@ pub fn call_xcall_handle_message<'info>( from_nid: String, message: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { let mut data = vec![]; let args = xcall_type::HandleMessageArgs { from_nid, message, sequence_no, + conn_sn, }; args.serialize(&mut data)?; diff --git a/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs b/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs index 7f149623..9f21112f 100644 --- a/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs +++ b/contracts/solana/programs/centralized-connection/src/instructions/query_accounts.rs @@ -2,7 +2,7 @@ use anchor_lang::{ prelude::*, solana_program::{ instruction::Instruction, - program::{get_return_data, invoke}, + program::{get_return_data, invoke, invoke_signed}, system_program, }, }; @@ -35,8 +35,8 @@ pub fn query_send_message_accounts<'info>( }) } -pub fn query_recv_message_accounts( - ctx: Context, +pub fn query_recv_message_accounts<'info>( + ctx: Context<'_, '_, '_, 'info, QueryAccountsCtx<'info>>, src_network: String, conn_sn: u128, msg: Vec, @@ -65,8 +65,8 @@ pub fn query_recv_message_accounts( AccountMetadata::new(authority, false), ]; - let mut xcall_account_metas = vec![]; - let mut xcall_account_infos = vec![]; + let mut xcall_account_metas = vec![AccountMeta::new_readonly(config.key(), true)]; + let mut xcall_account_infos = vec![config.to_account_info()]; for (_, account) in ctx.remaining_accounts.iter().enumerate() { if account.is_writable { @@ -78,7 +78,7 @@ pub fn query_recv_message_accounts( xcall_account_infos.push(account.to_account_info()) } - let ix_data = get_handle_message_ix_data(src_network, msg, sequence_no)?; + let ix_data = get_handle_message_ix_data(src_network, msg, sequence_no, conn_sn)?; let ix = Instruction { program_id: config.xcall, @@ -86,7 +86,11 @@ pub fn query_recv_message_accounts( data: ix_data, }; - invoke(&ix, &xcall_account_infos)?; + invoke_signed( + &ix, + &xcall_account_infos, + &[&[Config::SEED_PREFIX.as_bytes(), &[config.bump]]], + )?; let (_, data) = get_return_data().unwrap(); let mut data_slice: &[u8] = &data; @@ -171,12 +175,14 @@ pub fn get_handle_message_ix_data( from_nid: String, message: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result> { let mut ix_args_data = vec![]; let ix_args = xcall_type::HandleMessageArgs { from_nid, message, sequence_no, + conn_sn, }; ix_args.serialize(&mut ix_args_data)?; diff --git a/contracts/solana/programs/centralized-connection/src/lib.rs b/contracts/solana/programs/centralized-connection/src/lib.rs index 7bb0ac67..a7755eef 100644 --- a/contracts/solana/programs/centralized-connection/src/lib.rs +++ b/contracts/solana/programs/centralized-connection/src/lib.rs @@ -72,7 +72,7 @@ pub mod centralized_connection { msg: Vec, sequence_no: u128, ) -> Result<()> { - helper::call_xcall_handle_message(ctx, src_network, msg, sequence_no) + helper::call_xcall_handle_message(ctx, src_network, msg, sequence_no, conn_sn) } pub fn revert_message<'info>( diff --git a/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs b/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs index 3fe45ae7..7bd63c64 100644 --- a/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs +++ b/contracts/solana/programs/mock-dapp-multi/src/instructions/execute_forced_rollback.rs @@ -6,8 +6,11 @@ use crate::{state::*, xcall}; pub fn execute_forced_rollback<'info>( ctx: Context<'_, '_, '_, 'info, ExecuteForcedRollbackCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, ) -> Result<()> { - let ix_data = xcall::get_handle_forced_rollback_ix_data(req_id)?; + let ix_data = xcall::get_handle_forced_rollback_ix_data(req_id, from_nid, conn_sn, connection)?; xcall::call_xcall_handle_forced_rollback( &ix_data, diff --git a/contracts/solana/programs/mock-dapp-multi/src/lib.rs b/contracts/solana/programs/mock-dapp-multi/src/lib.rs index c56b9bb8..2b0f3a86 100644 --- a/contracts/solana/programs/mock-dapp-multi/src/lib.rs +++ b/contracts/solana/programs/mock-dapp-multi/src/lib.rs @@ -64,8 +64,11 @@ pub mod mock_dapp_multi { pub fn execute_forced_rollback<'info>( ctx: Context<'_, '_, '_, 'info, ExecuteForcedRollbackCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, ) -> Result<()> { - instructions::execute_forced_rollback(ctx, req_id) + instructions::execute_forced_rollback(ctx, req_id, from_nid, conn_sn, connection) } #[allow(unused_variables)] diff --git a/contracts/solana/programs/mock-dapp-multi/src/xcall.rs b/contracts/solana/programs/mock-dapp-multi/src/xcall.rs index d4cc32f7..7fd367fd 100644 --- a/contracts/solana/programs/mock-dapp-multi/src/xcall.rs +++ b/contracts/solana/programs/mock-dapp-multi/src/xcall.rs @@ -100,9 +100,19 @@ pub fn get_send_call_ix_data(msg: Vec, to: NetworkAddress) -> Result Ok(ix_data) } -pub fn get_handle_forced_rollback_ix_data(req_id: u128) -> Result> { +pub fn get_handle_forced_rollback_ix_data( + req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, +) -> Result> { let mut ix_args_data = vec![]; - let ix_args = xcall_type::HandleForcedRollback { req_id }; + let ix_args = xcall_type::HandleForcedRollback { + req_id, + from_nid, + conn_sn, + connection, + }; ix_args.serialize(&mut ix_args_data)?; let ix_data = helpers::get_instruction_data(HANDLE_FORCED_ROLLBACK_IX, ix_args_data); diff --git a/contracts/solana/programs/xcall/src/event.rs b/contracts/solana/programs/xcall/src/event.rs index c2190054..3d4bf653 100644 --- a/contracts/solana/programs/xcall/src/event.rs +++ b/contracts/solana/programs/xcall/src/event.rs @@ -16,6 +16,8 @@ pub struct CallMessage { pub sn: u128, pub reqId: u128, pub data: Vec, + pub connection: Pubkey, + pub connSn: u128, } #[event] diff --git a/contracts/solana/programs/xcall/src/instructions/execute_call.rs b/contracts/solana/programs/xcall/src/instructions/execute_call.rs index 1ea976b7..32935034 100644 --- a/contracts/solana/programs/xcall/src/instructions/execute_call.rs +++ b/contracts/solana/programs/xcall/src/instructions/execute_call.rs @@ -96,7 +96,7 @@ pub fn execute_call<'info>( } #[derive(Accounts)] -#[instruction(req_id : u128)] +#[instruction(req_id : u128, from_nid: String, conn_sn: u128, connection: Pubkey)] pub struct ExecuteCallCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable /// because it will be debited for any fees or rent required during the transaction. @@ -125,7 +125,7 @@ pub struct ExecuteCallCtx<'info> { /// calls. The account is closed after use, with any remaining funds sent to the `admin`. #[account( mut, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.to_bytes()], bump = proxy_request.bump, close = admin )] diff --git a/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs b/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs index 19b02b37..424ec86d 100644 --- a/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs +++ b/contracts/solana/programs/xcall/src/instructions/handle_forced_rollback.rs @@ -77,7 +77,7 @@ pub fn handle_forced_rollback<'info>( } #[derive(Accounts)] -#[instruction(req_id: u128)] +#[instruction(req_id: u128, from_nid: String, conn_sn: u128, connection: Pubkey)] pub struct HandleForcedRollbackCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable because /// it will be debited for any fees or rent required during the transaction. @@ -111,7 +111,7 @@ pub struct HandleForcedRollbackCtx<'info> { /// calls and is closed after use, with any remaining funds sent to the `admin`. #[account( mut, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.to_bytes()], bump = proxy_request.bump, close = admin )] diff --git a/contracts/solana/programs/xcall/src/instructions/handle_message.rs b/contracts/solana/programs/xcall/src/instructions/handle_message.rs index 562ea80e..ef3e6617 100644 --- a/contracts/solana/programs/xcall/src/instructions/handle_message.rs +++ b/contracts/solana/programs/xcall/src/instructions/handle_message.rs @@ -26,6 +26,8 @@ use crate::{ /// - `message`: The encoded message payload received from the chain. /// - `sequence_no`: The sequence number associated with the message, used to track message /// ordering and responses. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if successful, or an appropriate error if any validation or @@ -35,6 +37,7 @@ pub fn handle_message<'info>( from_nid: String, message: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { let config = &ctx.accounts.config; if config.network_id == from_nid.to_string() { @@ -48,7 +51,7 @@ pub fn handle_message<'info>( return Err(XcallError::PendingResponseAccountMustNotBeSpecified.into()); } - invoke_handle_request(ctx, from_nid, cs_message.payload)? + invoke_handle_request(ctx, from_nid, cs_message.payload, conn_sn)? } CSMessageType::CSMessageResult => { let rollback_account = ctx @@ -67,7 +70,7 @@ pub fn handle_message<'info>( return Ok(()); } - invoke_handle_result(ctx, from_nid, cs_message.payload, sequence_no)?; + invoke_handle_result(ctx, from_nid, cs_message.payload, sequence_no, conn_sn)?; } } Ok(()) @@ -84,6 +87,8 @@ pub fn handle_message<'info>( /// - `ctx`: Context containing all relevant accounts and program-specific information. /// - `from_nid`: Network ID of the source chain that sent the request. /// - `payload`: Encoded payload of the request message. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the request is successfully processed, or an error if @@ -92,6 +97,7 @@ pub fn handle_request( ctx: Context, from_nid: String, payload: &[u8], + conn_sn: u128, ) -> Result<()> { let mut req: CSMessageRequest = payload.try_into()?; @@ -116,6 +122,11 @@ pub fn handle_request( pending_request.sources.push(source.owner.to_owned()) } if pending_request.sources.len() != req.protocols().len() { + // close the proxy request as it's no longer needed + ctx.accounts + .proxy_request + .close(ctx.accounts.signer.to_account_info())?; + return Ok(()); } pending_request.close(ctx.accounts.admin.clone())?; @@ -128,7 +139,9 @@ pub fn handle_request( to: req.to().clone(), sn: req.sequence_no(), reqId: req_id, - data: req.data() + data: req.data(), + connection: source.owner.to_owned(), + connSn: conn_sn }); let proxy_request = &mut ctx.accounts.proxy_request; @@ -151,11 +164,13 @@ pub fn handle_request( /// # Arguments /// - `ctx`: The context of accounts involved in the operation. /// - `payload`: The raw result data from the cross-chain operation, which is decoded and processed. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the operation completes successfully, or an error if something /// goes wrong. -pub fn handle_result(ctx: Context, payload: &[u8]) -> Result<()> { +pub fn handle_result(ctx: Context, payload: &[u8], conn_sn: u128) -> Result<()> { let result: CSMessageResult = payload.try_into()?; let proxy_request = &ctx.accounts.proxy_request; let rollback_account = &mut ctx.accounts.rollback_account; @@ -180,7 +195,7 @@ pub fn handle_result(ctx: Context, payload: &[u8]) -> Result<() success_res.success = true; if let Some(message) = &mut result.message() { - handle_reply(ctx, message)?; + handle_reply(ctx, message, conn_sn)?; } else { if proxy_request.is_some() { return Err(XcallError::ProxyRequestAccountMustNotBeSpecified.into()); @@ -250,11 +265,17 @@ pub fn handle_error(ctx: Context, sequence_no: u128) -> Result<( /// # Arguments /// * `ctx` - The context containing relevant accounts for handling the reply. /// * `reply` - The mutable reference to the incoming reply message to be processed. +/// * `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the operation completes successfully, or an error if something /// goes wrong. -pub fn handle_reply(ctx: Context, reply: &mut CSMessageRequest) -> Result<()> { +pub fn handle_reply( + ctx: Context, + reply: &mut CSMessageRequest, + conn_sn: u128, +) -> Result<()> { let rollback = &ctx.accounts.rollback_account.rollback; if rollback.to().nid() != reply.from().nid() { return Err(XcallError::InvalidReplyReceived.into()); @@ -267,7 +288,9 @@ pub fn handle_reply(ctx: Context, reply: &mut CSMessageRequest) to: reply.to().clone(), sn: reply.sequence_no(), reqId: req_id, - data: reply.data() + data: reply.data(), + connection: ctx.accounts.connection.owner.to_owned(), + connSn: conn_sn }); let proxy_request = ctx @@ -295,6 +318,8 @@ pub fn handle_reply(ctx: Context, reply: &mut CSMessageRequest) /// - `ctx`: The context containing the accounts and program-specific info needed for the instruction. /// - `from_nid`: The network ID of the chain that sent the request. /// - `msg_payload`: The payload of the request message received from the source chain. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Indicates whether the invocation was successful or encountered an error. @@ -302,11 +327,13 @@ pub fn invoke_handle_request<'info>( ctx: Context<'_, '_, '_, 'info, HandleMessageCtx<'info>>, from_nid: String, msg_payload: Vec, + conn_sn: u128, ) -> Result<()> { let mut data = vec![]; let args = xcall_lib::xcall_type::HandleRequestArgs { from_nid, msg_payload, + conn_sn, }; args.serialize(&mut data)?; let ix_data = helper::get_instruction_data(xcall_lib::xcall_type::HANDLE_REQUEST_IX, data); @@ -362,6 +389,8 @@ pub fn invoke_handle_request<'info>( /// - `from_nid`: The network ID of the chain that sent the response. /// - `msg_payload`: The payload of the message received from the destination chain. /// - `sequence_no`: The sequence number associated with the original request message. +/// - `conn_sn`: The sequence number of connection associated with the message, used to derive +/// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Indicates whether the invocation was successful or encountered an error. @@ -370,12 +399,14 @@ pub fn invoke_handle_result<'info>( from_nid: String, msg_payload: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { let mut data = vec![]; let args = xcall_lib::xcall_type::HandleResultArgs { from_nid, msg_payload, sequence_no, + conn_sn, }; args.serialize(&mut data)?; let ix_data = helper::get_instruction_data(xcall_lib::xcall_type::HANDLE_RESULT_IX, data); @@ -547,7 +578,7 @@ pub struct HandleMessageCtx<'info> { } #[derive(Accounts)] -#[instruction(from_nid: String, msg_payload: Vec)] +#[instruction(from_nid: String, msg_payload: Vec, conn_sn: u128)] pub struct HandleRequestCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable /// because it will be debited for any fees or rent required during the transaction. @@ -590,7 +621,7 @@ pub struct HandleRequestCtx<'info> { init_if_needed, payer = signer, space = ProxyRequest::SIZE, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &(config.last_req_id + 1).to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.owner.to_bytes()], bump )] pub proxy_request: Account<'info, ProxyRequest>, @@ -609,7 +640,7 @@ pub struct HandleRequestCtx<'info> { } #[derive(Accounts)] -#[instruction(from_nid: String, msg_payload: Vec, sequence_no: u128)] +#[instruction(from_nid: String, msg_payload: Vec, sequence_no: u128, conn_sn: u128)] pub struct HandleResultCtx<'info> { /// The account that signs and pays for the transaction. This account is mutable /// because it will be debited for any fees or rent required during the transaction. @@ -664,7 +695,7 @@ pub struct HandleResultCtx<'info> { init_if_needed, payer = signer, space = ProxyRequest::SIZE, - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &(config.last_req_id + 1).to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.owner.to_bytes()], bump )] pub proxy_request: Option>, diff --git a/contracts/solana/programs/xcall/src/instructions/query_accounts.rs b/contracts/solana/programs/xcall/src/instructions/query_accounts.rs index 23038e50..296e6483 100644 --- a/contracts/solana/programs/xcall/src/instructions/query_accounts.rs +++ b/contracts/solana/programs/xcall/src/instructions/query_accounts.rs @@ -28,15 +28,20 @@ use crate::{ pub fn query_handle_message_accounts( ctx: Context, + from_nid: String, msg: Vec, + conn_sn: u128, ) -> Result { + let connection = &ctx.accounts.connection; let config = &ctx.accounts.config; let admin = config.admin; let (proxy_request, _) = Pubkey::find_program_address( &[ ProxyRequest::SEED_PREFIX.as_bytes(), - &(config.last_req_id + 1).to_be_bytes(), + from_nid.as_bytes(), + &conn_sn.to_be_bytes(), + &connection.owner.to_bytes(), ], &id(), ); @@ -149,7 +154,9 @@ pub fn query_handle_message_accounts( pub fn query_execute_call_accounts( ctx: Context, - req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, data: Vec, page: u8, limit: u8, @@ -158,7 +165,12 @@ pub fn query_execute_call_accounts( let req = &ctx.accounts.proxy_request.req; let (proxy_request, _) = Pubkey::find_program_address( - &[ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + &[ + ProxyRequest::SEED_PREFIX.as_bytes(), + from_nid.as_bytes(), + &conn_sn.to_be_bytes(), + &connection.to_bytes(), + ], &id(), ); @@ -342,7 +354,7 @@ pub fn query_connection_send_message_accoounts<'info>( } #[derive(Accounts)] -#[instruction(req_id: u128, data: Vec)] +#[instruction(req_id: u128, from_nid: String, conn_sn: u128, connection: Pubkey, data: Vec)] pub struct QueryExecuteCallAccountsCtx<'info> { #[account( seeds = [Config::SEED_PREFIX.as_bytes()], @@ -351,7 +363,7 @@ pub struct QueryExecuteCallAccountsCtx<'info> { pub config: Account<'info, Config>, #[account( - seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), &req_id.to_be_bytes()], + seeds = [ProxyRequest::SEED_PREFIX.as_bytes(), from_nid.as_bytes(), &conn_sn.to_be_bytes(), &connection.to_bytes()], bump = proxy_request.bump )] pub proxy_request: Account<'info, ProxyRequest>, @@ -360,6 +372,8 @@ pub struct QueryExecuteCallAccountsCtx<'info> { #[derive(Accounts)] #[instruction(from_nid: String, msg: Vec, sequence_no: u128)] pub struct QueryHandleMessageAccountsCtx<'info> { + pub connection: Signer<'info>, + #[account( seeds = [Config::SEED_PREFIX.as_bytes()], bump = config.bump, diff --git a/contracts/solana/programs/xcall/src/lib.rs b/contracts/solana/programs/xcall/src/lib.rs index 4506dc4e..c4179343 100644 --- a/contracts/solana/programs/xcall/src/lib.rs +++ b/contracts/solana/programs/xcall/src/lib.rs @@ -137,6 +137,8 @@ pub mod xcall { /// - `msg`: The encoded message payload received from the chain. /// - `sequence_no`: The sequence number associated with the message, used to track message /// ordering and responses. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the message is successfully handled, or an error if any @@ -146,8 +148,9 @@ pub mod xcall { from_nid: String, msg: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { - instructions::handle_message(ctx, from_nid, msg, sequence_no) + instructions::handle_message(ctx, from_nid, msg, sequence_no, conn_sn) } /// Instruction: Handle Request @@ -162,16 +165,20 @@ pub mod xcall { /// - `ctx`: Context containing all relevant accounts and program-specific information. /// - `from_nid`: Network ID of the chain that sent the request. /// - `msg_payload`: Encoded payload of the request message. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the request is processed successfully, or an error if /// validation or processing fails. + #[allow(unused_variables)] pub fn handle_request<'info>( ctx: Context<'_, '_, '_, 'info, HandleRequestCtx<'info>>, from_nid: String, msg_payload: Vec, + conn_sn: u128, ) -> Result<()> { - instructions::handle_request(ctx, from_nid, &msg_payload) + instructions::handle_request(ctx, from_nid, &msg_payload, conn_sn) } /// Instruction: Handle Result @@ -187,6 +194,8 @@ pub mod xcall { /// - `from_nid`: Network ID of the chain that sent the result. /// - `msg_payload`: Encoded payload of the result message. /// - `sequence_no`: Unique sequence number of the result message. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the result is processed successfully, or an error if @@ -197,8 +206,9 @@ pub mod xcall { from_nid: String, msg_payload: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result<()> { - instructions::handle_result(ctx, &msg_payload) + instructions::handle_result(ctx, &msg_payload, conn_sn) } /// Instruction: Handle Error @@ -357,13 +367,22 @@ pub mod xcall { /// # Parameters /// - `ctx`: The context of the solana program instruction /// - `req_id`: The unique identifier for the request being processed. + /// - `from_nid`: Network ID of the chain that sent the request. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters + /// - `connection`: The connection key used to derive proxy request account with the combination + /// of other parameters /// - `data`: The data associated with the call request, which will be verified and processed. /// /// # Returns /// - `Result<()>`: Returns `Ok(())` if the call was executed successfully, or an error if it failed. + #[allow(unused_variables)] pub fn execute_call<'info>( ctx: Context<'_, '_, '_, 'info, ExecuteCallCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, data: Vec, ) -> Result<()> { instructions::execute_call(ctx, req_id, data) @@ -402,6 +421,11 @@ pub mod xcall { /// # Arguments /// * `ctx` - Context containing the accounts required for processing the forced rollback. /// * `req_id` - The unique request ID associated with the message being rolled back. + /// - `from_nid`: Network ID of the chain that sent the request. + /// - `conn_sn`: The sequence number of connection associated with the message, used to derive + /// unique proxy request account with the combination of other parameters + /// - `connection`: The connection key used to derive proxy request account with the combination + /// of other parameters /// /// # Returns /// * `Result<()>` - Returns `Ok(())` on successful execution, or an error if the rollback process @@ -410,18 +434,27 @@ pub mod xcall { pub fn handle_forced_rollback<'info>( ctx: Context<'_, '_, '_, 'info, HandleForcedRollbackCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, ) -> Result<()> { instructions::handle_forced_rollback(ctx) } + #[allow(unused_variables)] pub fn query_execute_call_accounts<'info>( ctx: Context<'_, '_, '_, 'info, QueryExecuteCallAccountsCtx<'info>>, req_id: u128, + from_nid: String, + conn_sn: u128, + connection: Pubkey, data: Vec, page: u8, limit: u8, ) -> Result { - instructions::query_execute_call_accounts(ctx, req_id, data, page, limit) + instructions::query_execute_call_accounts( + ctx, from_nid, conn_sn, connection, data, page, limit, + ) } #[allow(unused_variables)] @@ -440,8 +473,9 @@ pub mod xcall { from_nid: String, msg: Vec, sequence_no: u128, + conn_sn: u128, ) -> Result { - instructions::query_handle_message_accounts(ctx, msg) + instructions::query_handle_message_accounts(ctx, from_nid, msg, conn_sn) } pub fn query_handle_error_accounts( diff --git a/contracts/solana/tests/centralized-connection/centralized-connection.ts b/contracts/solana/tests/centralized-connection/centralized-connection.ts index 7d3d51f2..8b873a64 100644 --- a/contracts/solana/tests/centralized-connection/centralized-connection.ts +++ b/contracts/solana/tests/centralized-connection/centralized-connection.ts @@ -182,6 +182,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + fromNetwork, connSn, nextSequenceNo, cs_message, @@ -212,7 +213,11 @@ describe("CentralizedConnection", () => { expect(await ctx.getReceipt(fromNetwork, nextSequenceNo)).to.be.empty; // expect proxy request in xcall PDA's account - let proxyRequest = await xcallCtx.getProxyRequest(nextReqId); + let proxyRequest = await xcallCtx.getProxyRequest( + fromNetwork, + connSn, + connectionProgram.programId + ); expect(proxyRequest.req.protocols).to.includes( connectionProgram.programId.toString() ); @@ -229,17 +234,30 @@ describe("CentralizedConnection", () => { // call xcall execute_call let executeCallAccounts = await xcallCtx.getExecuteCallAccounts( nextReqId, + fromNetwork, + connSn, + connectionProgram.programId, data ); await xcallProgram.methods - .executeCall(new anchor.BN(nextReqId), Buffer.from(data)) + .executeCall( + new anchor.BN(nextReqId), + fromNetwork, + new anchor.BN(connSn), + connectionProgram.programId, + Buffer.from(data) + ) .accounts({ signer: ctx.admin.publicKey, systemProgram: SYSTEM_PROGRAM_ID, config: XcallPDA.config().pda, admin: xcallConfig.admin, - proxyRequest: XcallPDA.proxyRequest(nextReqId).pda, + proxyRequest: XcallPDA.proxyRequest( + fromNetwork, + connSn, + connectionProgram.programId + ).pda, }) .remainingAccounts([...executeCallAccounts.slice(4)]) .signers([ctx.admin]) @@ -348,6 +366,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + ctx.dstNetworkId, connSn, nextSequenceNo, csMessage, @@ -467,6 +486,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + ctx.dstNetworkId, connSn, nextSequenceNo, csMessage, @@ -664,6 +684,7 @@ describe("CentralizedConnection", () => { ).encode(); let recvMessageAccounts = await ctx.getRecvMessageAccounts( + fromNetwork, connSn, nextSequenceNo, cs_message, @@ -691,7 +712,12 @@ describe("CentralizedConnection", () => { await sleep(2); let executeForcedRollbackIx = await mockDappProgram.methods - .executeForcedRollback(new anchor.BN(nextReqId)) + .executeForcedRollback( + new anchor.BN(nextReqId), + fromNetwork, + new anchor.BN(connSn), + connectionProgram.programId + ) .accountsStrict({ config: DappPDA.config().pda, systemProgram: SYSTEM_PROGRAM_ID, @@ -710,7 +736,11 @@ describe("CentralizedConnection", () => { isWritable: true, }, { - pubkey: XcallPDA.proxyRequest(nextReqId).pda, + pubkey: XcallPDA.proxyRequest( + fromNetwork, + connSn, + connectionProgram.programId + ).pda, isSigner: false, isWritable: true, }, diff --git a/contracts/solana/tests/centralized-connection/setup.ts b/contracts/solana/tests/centralized-connection/setup.ts index de6eacb2..02e7dee6 100644 --- a/contracts/solana/tests/centralized-connection/setup.ts +++ b/contracts/solana/tests/centralized-connection/setup.ts @@ -77,6 +77,7 @@ export class TestContext { } async getRecvMessageAccounts( + fromNetwork: string, connSn: number, sequenceNo: number, csMessage: Uint8Array, @@ -106,7 +107,7 @@ export class TestContext { let res = await connectionProgram.methods .queryRecvMessageAccounts( - this.dstNetworkId, + fromNetwork, new anchor.BN(connSn), Buffer.from(csMessage), new anchor.BN(sequenceNo), diff --git a/contracts/solana/tests/xcall/setup.ts b/contracts/solana/tests/xcall/setup.ts index 7cf19fee..f826cd43 100644 --- a/contracts/solana/tests/xcall/setup.ts +++ b/contracts/solana/tests/xcall/setup.ts @@ -82,12 +82,27 @@ export class TestContext { await sleep(2); } - async getExecuteCallAccounts(reqId: number, data: Uint8Array) { + async getExecuteCallAccounts( + reqId: number, + fromNetwork: string, + connSn: number, + connection: PublicKey, + data: Uint8Array + ) { const res = await xcallProgram.methods - .queryExecuteCallAccounts(new anchor.BN(reqId), Buffer.from(data), 1, 30) + .queryExecuteCallAccounts( + new anchor.BN(reqId), + fromNetwork, + new anchor.BN(connSn), + connection, + Buffer.from(data), + 1, + 30 + ) .accountsStrict({ config: XcallPDA.config().pda, - proxyRequest: XcallPDA.proxyRequest(reqId).pda, + proxyRequest: XcallPDA.proxyRequest(fromNetwork, connSn, connection) + .pda, }) .remainingAccounts([ { @@ -145,9 +160,13 @@ export class TestContext { return await xcallProgram.account.config.fetch(pda); } - async getProxyRequest(requestId: number) { + async getProxyRequest( + fromNetwork: string, + connSn: number, + connection: PublicKey + ) { return await xcallProgram.account.proxyRequest.fetch( - XcallPDA.proxyRequest(requestId).pda, + XcallPDA.proxyRequest(fromNetwork, connSn, connection).pda, "confirmed" ); } @@ -192,9 +211,18 @@ export class XcallPDA { return { bump, pda }; } - static proxyRequest(requestId: number) { + static proxyRequest( + fromNetwork: string, + connSn: number, + connection: PublicKey + ) { const [pda, bump] = PublicKey.findProgramAddressSync( - [Buffer.from("proxy"), uint128ToArray(requestId)], + [ + Buffer.from("proxy"), + Buffer.from(fromNetwork), + uint128ToArray(connSn), + connection.toBuffer(), + ], xcallProgram.programId ); diff --git a/contracts/soroban/Cargo.lock b/contracts/soroban/Cargo.lock index e571a9ff..9ac56a3a 100644 --- a/contracts/soroban/Cargo.lock +++ b/contracts/soroban/Cargo.lock @@ -130,6 +130,8 @@ name = "centralized-connection" version = "0.0.0" dependencies = [ "soroban-sdk", + "soroban-xcall-lib", + "xcall", ] [[package]] @@ -338,7 +340,6 @@ dependencies = [ "elliptic-curve", "rfc6979", "signature", - "spki", ] [[package]] @@ -353,15 +354,16 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7277392b266383ef8396db7fdeb1e77b6c52fed775f5df15bb24f35b72156980" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "serde", "sha2", + "subtle", "zeroize", ] @@ -383,7 +385,6 @@ dependencies = [ "ff", "generic-array", "group", - "pkcs8", "rand_core", "sec1", "subtle", @@ -597,9 +598,7 @@ dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", - "once_cell", "sha2", - "signature", ] [[package]] @@ -716,6 +715,18 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "p256" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.14" @@ -760,6 +771,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro2" version = "1.0.69" @@ -848,7 +868,6 @@ dependencies = [ "base16ct", "der", "generic-array", - "pkcs8", "subtle", "zeroize", ] @@ -959,9 +978,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "soroban-builtin-sdk-macros" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cc32c6e817f3ca269764ec0d7d14da6210b74a5bf14d4e745aa3ee860558900" +checksum = "2f57a68ef8777e28e274de0f3a88ad9a5a41d9a2eb461b4dd800b086f0e83b80" dependencies = [ "itertools", "proc-macro2", @@ -971,9 +990,9 @@ dependencies = [ [[package]] name = "soroban-env-common" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c14e18d879c520ff82612eaae0590acaf6a7f3b977407e1abb1c9e31f94c7814" +checksum = "2fd1c89463835fe6da996318156d39f424b4f167c725ec692e5a7a2d4e694b3d" dependencies = [ "arbitrary", "crate-git-revision", @@ -985,13 +1004,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-xdr", + "wasmparser", ] [[package]] name = "soroban-env-guest" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5122ca2abd5ebcc1e876a96b9b44f87ce0a0e06df8f7c09772ddb58b159b7454" +checksum = "6bfb2536811045d5cd0c656a324cbe9ce4467eb734c7946b74410d90dea5d0ce" dependencies = [ "soroban-env-common", "static_assertions", @@ -999,13 +1019,16 @@ dependencies = [ [[package]] name = "soroban-env-host" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "114a0fa0d0cc39d0be16b1ee35b6e5f4ee0592ddcf459bde69391c02b03cf520" +checksum = "2b7a32c28f281c423189f1298960194f0e0fc4eeb72378028171e556d8cd6160" dependencies = [ "backtrace", "curve25519-dalek", + "ecdsa", "ed25519-dalek", + "elliptic-curve", + "generic-array", "getrandom", "hex-literal", "hmac", @@ -1013,8 +1036,10 @@ dependencies = [ "num-derive", "num-integer", "num-traits", + "p256", "rand", "rand_chacha", + "sec1", "sha2", "sha3", "soroban-builtin-sdk-macros", @@ -1022,13 +1047,14 @@ dependencies = [ "soroban-wasmi", "static_assertions", "stellar-strkey", + "wasmparser", ] [[package]] name = "soroban-env-macros" -version = "20.3.0" +version = "21.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13e3f8c86f812e0669e78fcb3eae40c385c6a9dd1a4886a1de733230b4fcf27" +checksum = "242926fe5e0d922f12d3796cd7cd02dd824e5ef1caa088f45fce20b618309f64" dependencies = [ "itertools", "proc-macro2", @@ -1041,9 +1067,9 @@ dependencies = [ [[package]] name = "soroban-ledger-snapshot" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61a54708f44890e0546180db6b4f530e2a88d83b05a9b38a131caa21d005e25a" +checksum = "956476365ff3f9bf429ff23fa11ac75798347a2bfc3c9e5e12638dbe3a6b17a8" dependencies = [ "serde", "serde_json", @@ -1062,9 +1088,9 @@ dependencies = [ [[package]] name = "soroban-sdk" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84fc8be9068dd4e0212d8b13ad61089ea87e69ac212c262914503a961c8dc3a3" +checksum = "c7767472f00a4053e86d5c37b3c814a6bc01c9230004713328d73d2a3444e72e" dependencies = [ "arbitrary", "bytes-lit", @@ -1082,9 +1108,9 @@ dependencies = [ [[package]] name = "soroban-sdk-macros" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db20def4ead836663633f58d817d0ed8e1af052c9650a04adf730525af85b964" +checksum = "be8cf8fa10f3ad62509ff7b25cd696fb837da692c40264d1abb393e117aad75c" dependencies = [ "crate-git-revision", "darling", @@ -1102,9 +1128,9 @@ dependencies = [ [[package]] name = "soroban-spec" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eefeb5d373b43f6828145d00f0c5cc35e96db56a6671ae9614f84beb2711cab" +checksum = "12d306f61ef5c1247dca1562e04cc74b6e3adf107631c168b2ce0d5f1cf1fa13" dependencies = [ "base64 0.13.1", "stellar-xdr", @@ -1114,9 +1140,9 @@ dependencies = [ [[package]] name = "soroban-spec-rust" -version = "20.5.0" +version = "21.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3152bca4737ef734ac37fe47b225ee58765c9095970c481a18516a2b287c7a33" +checksum = "bed06e0f622fb878fc439643f2fd86163223ac33a468beeea96e5d33f79b08b3" dependencies = [ "prettyplease", "proc-macro2", @@ -1183,9 +1209,9 @@ dependencies = [ [[package]] name = "stellar-xdr" -version = "20.1.0" +version = "21.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e59cdf3eb4467fb5a4b00b52e7de6dca72f67fac6f9b700f55c95a5d86f09c9d" +checksum = "2675a71212ed39a806e415b0dbf4702879ff288ec7f5ee996dda42a135512b50" dependencies = [ "arbitrary", "base64 0.13.1", @@ -1369,11 +1395,12 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.88.0" +version = "0.116.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb8cf7dd82407fe68161bedcd57fde15596f32ebf6e9b3bdbf3ae1da20e38e5e" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" dependencies = [ - "indexmap 1.9.3", + "indexmap 2.2.6", + "semver", ] [[package]] diff --git a/contracts/soroban/Cargo.toml b/contracts/soroban/Cargo.toml index cddad916..7cb79f87 100644 --- a/contracts/soroban/Cargo.toml +++ b/contracts/soroban/Cargo.toml @@ -6,7 +6,7 @@ members = [ ] [workspace.dependencies] -soroban-sdk = "20.5.0" +soroban-sdk = "21.7.4" [profile.release] opt-level = "z" diff --git a/contracts/soroban/contracts/centralized-connection/Cargo.toml b/contracts/soroban/contracts/centralized-connection/Cargo.toml index c4f294fa..e34ece52 100644 --- a/contracts/soroban/contracts/centralized-connection/Cargo.toml +++ b/contracts/soroban/contracts/centralized-connection/Cargo.toml @@ -10,6 +10,8 @@ doctest = false [dependencies] soroban-sdk = { workspace = true, features = ["alloc"] } +xcall = { path = "../xcall" } +soroban-xcall-lib = { path = "../../libs/soroban-xcall-lib" } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/contracts/soroban/contracts/centralized-connection/src/contract.rs b/contracts/soroban/contracts/centralized-connection/src/contract.rs index 507f5451..03444560 100644 --- a/contracts/soroban/contracts/centralized-connection/src/contract.rs +++ b/contracts/soroban/contracts/centralized-connection/src/contract.rs @@ -125,11 +125,13 @@ impl CentralizedConnection { helpers::ensure_upgrade_authority(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } - pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> { - storage::extend_instance(&env); - Ok(()) + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) } } diff --git a/contracts/soroban/contracts/centralized-connection/src/storage.rs b/contracts/soroban/contracts/centralized-connection/src/storage.rs index 4a410aa0..a4a2fa24 100644 --- a/contracts/soroban/contracts/centralized-connection/src/storage.rs +++ b/contracts/soroban/contracts/centralized-connection/src/storage.rs @@ -50,13 +50,6 @@ pub fn native_token(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } -pub fn get_conn_sn(e: &Env) -> Result { - e.storage() - .instance() - .get(&StorageKey::ConnSn) - .ok_or(ContractError::Uninitialized) -} - pub fn get_next_conn_sn(e: &Env) -> u128 { let mut sn = e.storage().instance().get(&StorageKey::ConnSn).unwrap_or(0); sn += 1; @@ -103,6 +96,19 @@ pub fn get_sn_receipt(e: &Env, network_id: String, sn: u128) -> bool { is_received } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_receipt(e: &Env, network_id: String, sn: u128) { let key = StorageKey::Receipts(network_id, sn); e.storage().persistent().set(&key, &true); diff --git a/contracts/soroban/contracts/centralized-connection/src/test.rs b/contracts/soroban/contracts/centralized-connection/src/test.rs index a44bd217..7556c384 100644 --- a/contracts/soroban/contracts/centralized-connection/src/test.rs +++ b/contracts/soroban/contracts/centralized-connection/src/test.rs @@ -2,9 +2,14 @@ extern crate std; -mod xcall { +mod xcall_module { soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); } +mod connection { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" + ); +} use crate::{ contract::{CentralizedConnection, CentralizedConnectionClient}, @@ -13,9 +18,14 @@ use crate::{ types::InitializeMsg, }; use soroban_sdk::{ - symbol_short, + bytes, symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, - token, vec, Address, Bytes, Env, IntoVal, String, Symbol, + token, vec, Address, Bytes, Env, IntoVal, String, Symbol, Vec, +}; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; +use xcall::{ + storage as xcall_storage, + types::{message::CSMessage, request::CSMessageRequest, rollback::Rollback}, }; pub struct TestContext { @@ -33,11 +43,12 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let token_admin = Address::generate(&env); + let native_token_contract = env.register_stellar_asset_contract_v2(token_admin.clone()); Self { - xcall: env.register_contract_wasm(None, xcall::WASM), + xcall: env.register_contract_wasm(None, xcall_module::WASM), contract: env.register_contract(None, CentralizedConnection), relayer: Address::generate(&env), - native_token: env.register_stellar_asset_contract(token_admin.clone()), + native_token: native_token_contract.address(), nid: String::from_str(&env, "icon"), upgrade_authority: Address::generate(&env), env, @@ -54,6 +65,23 @@ impl TestContext { xcall_address: self.xcall.clone(), upgrade_authority: self.upgrade_authority.clone(), }); + + self.init_xcall_state(); + } + + pub fn init_xcall_state(&self) { + let xcall_client = xcall_module::Client::new(&self.env, &self.xcall); + + let initialize_msg = xcall_module::InitializeMsg { + native_token: self.native_token.clone(), + network_id: self.nid.clone(), + sender: Address::generate(&self.env), + upgrade_authority: self.upgrade_authority.clone(), + }; + xcall_client.initialize(&initialize_msg); + + xcall_client.set_protocol_fee(&100_u128); + xcall_client.set_default_connection(&self.nid, &self.contract) } pub fn init_send_message(&self, client: &CentralizedConnectionClient<'static>) { @@ -65,9 +93,11 @@ impl TestContext { } fn get_dummy_initialize_msg(env: &Env) -> InitializeMsg { + let native_token_contract = env.register_stellar_asset_contract_v2(Address::generate(&env)); + InitializeMsg { relayer: Address::generate(&env), - native_token: env.register_stellar_asset_contract(Address::generate(&env)), + native_token: native_token_contract.address(), xcall_address: Address::generate(&env), upgrade_authority: Address::generate(&env), } @@ -343,3 +373,108 @@ fn test_get_receipt_returns_false() { let receipt = client.get_receipt(&ctx.nid, &sequence_no); assert_eq!(receipt, true) } + +#[test] +fn test_recv_message() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + protocols, + MessageType::CallMessagePersisted, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request); + let encoded = cs_message.encode(&ctx.env); + + let conn_sn = 1; + let from_nid = String::from_str(&ctx.env, "0x2.icon"); + client.recv_message(&from_nid, &conn_sn, &encoded); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #5)")] +fn test_recv_message_duplicate_connection_sequence() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + protocols, + MessageType::CallMessagePersisted, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request); + let encoded = cs_message.encode(&ctx.env); + + let conn_sn = 1; + let from_nid = String::from_str(&ctx.env, "0x2.icon"); + client.recv_message(&from_nid, &conn_sn, &encoded); + + client.recv_message(&from_nid, &conn_sn, &encoded); +} + +#[test] +pub fn test_revert_message() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let protocols: Vec = vec![&ctx.env, ctx.contract.to_string()]; + let to = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "0x2.icon"), + ctx.xcall.to_string(), + ); + let rollback = Rollback::new( + Address::generate(&ctx.env), + to, + protocols.clone(), + bytes!(&ctx.env, 0xabc), + false, + ); + ctx.env.as_contract(&ctx.xcall, || { + xcall_storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.revert_message(&sequence_no); + + ctx.env.as_contract(&ctx.xcall, || { + // rollback should be enabled + let rollback = xcall_storage::get_rollback(&ctx.env, sequence_no).unwrap(); + assert_eq!(rollback.enabled, true); + }); +} + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = CentralizedConnectionClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(connection::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/centralized-connection/src/types.rs b/contracts/soroban/contracts/centralized-connection/src/types.rs index e3f08bdb..6b8c2fab 100644 --- a/contracts/soroban/contracts/centralized-connection/src/types.rs +++ b/contracts/soroban/contracts/centralized-connection/src/types.rs @@ -8,6 +8,7 @@ pub enum StorageKey { UpgradeAuthority, Xlm, ConnSn, + Version, NetworkFee(String), Receipts(String, u128), } diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs b/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs index dc3c8573..cd47ec6a 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/contract.rs @@ -125,9 +125,16 @@ impl MockDapp { helpers::ensure_admin(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) + } + fn process_message( message_type: u8, data: Bytes, diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs b/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs index 2a866ea2..e7f2533f 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/storage.rs @@ -26,6 +26,19 @@ pub fn native_token(e: &Env) -> Result { .ok_or(ContractError::Uninitialized) } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_admin(e: &Env, admin: Address) { e.storage().instance().set(&StorageKey::Admin, &admin); } diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs index 09977786..037f1876 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/contract.rs @@ -213,3 +213,16 @@ fn test_handle_call_message_reply() { &Some(vec![&ctx.env]), ); } + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = MockDappClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(mock_dapp::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs index ccaa3877..17e173ec 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/test/setup.rs @@ -3,16 +3,22 @@ use soroban_xcall_lib::network_address::NetworkAddress; use crate::contract::{MockDapp, MockDappClient}; -mod connection { +pub mod connection { soroban_sdk::contractimport!( file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" ); } -mod xcall_module { +pub mod xcall_module { soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); } +pub mod mock_dapp { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/mock_dapp_multi.wasm" + ); +} + pub fn get_dummy_network_address(env: &Env) -> NetworkAddress { let network_id = String::from_str(&env, "stellar"); let account = String::from_str( @@ -31,6 +37,7 @@ pub struct TestContext { pub env: Env, pub native_token: Address, pub xcall: Address, + pub upgrade_authority: Address, pub centralized_connection: Address, } @@ -38,14 +45,16 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let address = Address::generate(&env); + let native_token_contract = env.register_stellar_asset_contract_v2(address); Self { contract: env.register_contract(None, MockDapp), nid: String::from_str(&env, "stellar"), admin: Address::generate(&env), - native_token: env.register_stellar_asset_contract(address), + native_token: native_token_contract.address(), network_address: get_dummy_network_address(&env), xcall: env.register_contract_wasm(None, xcall_module::WASM), + upgrade_authority: Address::generate(&env), centralized_connection: env.register_contract_wasm(None, connection::WASM), env, } @@ -72,6 +81,7 @@ impl TestContext { native_token: self.native_token.clone(), network_id: self.nid.clone(), sender: Address::generate(&self.env), + upgrade_authority: self.upgrade_authority.clone(), }; xcall_client.initialize(&initialize_msg); @@ -86,6 +96,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: Address::generate(&self.env), xcall_address: self.xcall.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); diff --git a/contracts/soroban/contracts/mock-dapp-multi/src/types.rs b/contracts/soroban/contracts/mock-dapp-multi/src/types.rs index 88ab759e..8b23c376 100644 --- a/contracts/soroban/contracts/mock-dapp-multi/src/types.rs +++ b/contracts/soroban/contracts/mock-dapp-multi/src/types.rs @@ -6,6 +6,7 @@ pub enum StorageKey { Admin, Xlm, Sn, + Version, Rollback(u128), Connections(String), } diff --git a/contracts/soroban/contracts/xcall/src/contract.rs b/contracts/soroban/contracts/xcall/src/contract.rs index 47c2eec9..8d81f6ac 100644 --- a/contracts/soroban/contracts/xcall/src/contract.rs +++ b/contracts/soroban/contracts/xcall/src/contract.rs @@ -153,11 +153,13 @@ impl Xcall { helpers::ensure_upgrade_authority(&env)?; env.deployer().update_current_contract_wasm(new_wasm_hash); + let current_version = storage::get_contract_version(&env); + storage::set_contract_version(&env, current_version + 1); + Ok(()) } - pub fn extend_instance_storage(env: Env) -> Result<(), ContractError> { - storage::extend_instance(&env); - Ok(()) + pub fn version(env: Env) -> u32 { + storage::get_contract_version(&env) } } diff --git a/contracts/soroban/contracts/xcall/src/errors.rs b/contracts/soroban/contracts/xcall/src/errors.rs index e9896817..ad05c5c3 100644 --- a/contracts/soroban/contracts/xcall/src/errors.rs +++ b/contracts/soroban/contracts/xcall/src/errors.rs @@ -21,4 +21,6 @@ pub enum ContractError { InvalidReplyReceived = 15, InvalidRlpLength = 16, NoRollbackData = 17, + NetworkIdMismatch = 18, + InvalidSourceNetwork = 19, } diff --git a/contracts/soroban/contracts/xcall/src/handle_message.rs b/contracts/soroban/contracts/xcall/src/handle_message.rs index 3506b45e..e76bcfed 100644 --- a/contracts/soroban/contracts/xcall/src/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/handle_message.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{Address, Bytes, Env, String, Vec}; +use soroban_sdk::{Address, Bytes, BytesN, Env, String, Vec}; use crate::{ errors::ContractError, @@ -21,7 +21,7 @@ pub fn handle_message( let config = storage::get_config(&env)?; if config.network_id == from_nid { - return Err(ContractError::ProtocolsMismatch); + return Err(ContractError::InvalidSourceNetwork); } let cs_message: CSMessage = CSMessage::decode(&env, msg)?; @@ -43,7 +43,7 @@ pub fn handle_request( let (src_net, _) = req.from().parse_network_address(&env); if src_net != from_net { - return Err(ContractError::ProtocolsMismatch); + return Err(ContractError::NetworkIdMismatch); } let source = sender.to_string(); let source_valid = is_valid_source(&env, &source, src_net, &req.protocols())?; @@ -52,7 +52,7 @@ pub fn handle_request( } if req.protocols().len() > 1 { - let hash = env.crypto().keccak256(&data); + let hash: BytesN<32> = env.crypto().keccak256(&data).into(); let mut pending_request = storage::get_pending_request(&env, hash.clone()); if !pending_request.contains(source.clone()) { @@ -96,7 +96,7 @@ pub fn handle_result(env: &Env, sender: &Address, data: Bytes) -> Result<(), Con } if rollback.protocols().len() > 1 { - let hash = env.crypto().keccak256(&data); + let hash: BytesN<32> = env.crypto().keccak256(&data).into(); let mut pending_response = storage::get_pending_response(&env, hash.clone()); if !pending_response.contains(source.clone()) { @@ -160,6 +160,7 @@ pub fn handle_reply( } pub fn handle_error(env: &Env, sender: Address, sequence_no: u128) -> Result<(), ContractError> { + sender.require_auth(); let cs_message_result = CSMessageResult::new( sequence_no, CSResponseType::CSResponseFailure, @@ -178,8 +179,10 @@ pub fn is_valid_source( return Ok(true); } if protocols.len() == 0 { - let default_connection = storage::default_connection(e, src_net)?; - return Ok(sender.clone() == default_connection.to_string()); + let default_connection = storage::default_connection(e, src_net); + if default_connection.is_ok() { + return Ok(sender.clone() == default_connection.unwrap().to_string()); + } } Ok(false) } diff --git a/contracts/soroban/contracts/xcall/src/helpers.rs b/contracts/soroban/contracts/xcall/src/helpers.rs index 5c6ff75d..6bd74db4 100644 --- a/contracts/soroban/contracts/xcall/src/helpers.rs +++ b/contracts/soroban/contracts/xcall/src/helpers.rs @@ -24,13 +24,6 @@ pub fn ensure_upgrade_authority(e: &Env) -> Result { Ok(authority) } -pub fn ensure_fee_handler(e: &Env) -> Result { - let fee_handler = storage::get_fee_handler(&e)?; - fee_handler.require_auth(); - - Ok(fee_handler) -} - pub fn ensure_data_size(len: usize) -> Result<(), ContractError> { if len > MAX_DATA_SIZE as usize { return Err(ContractError::MaxDataSizeExceeded); diff --git a/contracts/soroban/contracts/xcall/src/storage.rs b/contracts/soroban/contracts/xcall/src/storage.rs index 5deef0c5..cfa43a98 100644 --- a/contracts/soroban/contracts/xcall/src/storage.rs +++ b/contracts/soroban/contracts/xcall/src/storage.rs @@ -153,6 +153,19 @@ pub fn get_own_network_address(e: &Env) -> Result Ok(from) } +pub fn get_contract_version(e: &Env) -> u32 { + e.storage() + .instance() + .get(&StorageKey::Version) + .unwrap_or(1) +} + +pub fn set_contract_version(e: &Env, new_version: u32) { + e.storage() + .instance() + .set(&StorageKey::Version, &new_version); +} + pub fn store_admin(e: &Env, address: &Address) { e.storage().instance().set(&StorageKey::Admin, &address); extend_instance(&e); diff --git a/contracts/soroban/contracts/xcall/src/test/contract.rs b/contracts/soroban/contracts/xcall/src/test/contract.rs index e7a4d06f..5ad3267a 100644 --- a/contracts/soroban/contracts/xcall/src/test/contract.rs +++ b/contracts/soroban/contracts/xcall/src/test/contract.rs @@ -156,6 +156,20 @@ fn test_get_fee() { assert_eq!(fee, protocol_fee + centralized_conn_fee) } +#[test] +fn test_get_network_address() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let network_address = client.get_network_address(); + let expected_network_address = String::from_str( + &ctx.env, + "icon/CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM", + ); + assert_eq!(network_address, expected_network_address); +} + #[test] fn test_set_upgrade_authority() { let ctx = TestContext::default(); @@ -183,3 +197,16 @@ fn test_set_upgrade_authority() { let autorhity = client.get_upgrade_authority(); assert_eq!(autorhity, new_upgrade_authority); } + +#[test] +fn test_upgrade() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let wasm_hash = ctx.env.deployer().upload_contract_wasm(xcall::WASM); + assert_eq!(client.version(), 1); + + client.upgrade(&wasm_hash); + assert_eq!(client.version(), 2); +} diff --git a/contracts/soroban/contracts/xcall/src/test/execute_call.rs b/contracts/soroban/contracts/xcall/src/test/execute_call.rs index 108ef360..69ca24c7 100644 --- a/contracts/soroban/contracts/xcall/src/test/execute_call.rs +++ b/contracts/soroban/contracts/xcall/src/test/execute_call.rs @@ -1,10 +1,188 @@ #![cfg(test)] -use crate::{contract::XcallClient, storage, types::rollback::Rollback}; -use soroban_sdk::{bytes, testutils::Address as _, Address}; +use soroban_rlp::encoder; +use soroban_sdk::{ + bytes, + testutils::{Address as _, Events}, + vec, Address, Bytes, IntoVal, String, Vec, +}; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; + +use crate::{ + contract::XcallClient, + event::{CallExecutedEvent, RollbackExecutedEvent}, + storage, + types::{request::CSMessageRequest, rollback::Rollback}, +}; use super::setup::*; +#[test] +fn test_execute_call_with_persistent_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let req_id = 1; + let sequence_no = 1; + let from = NetworkAddress::new(&ctx.env, ctx.nid, ctx.contract.to_string()); + let mut req = CSMessageRequest::new( + from, + ctx.dapp.to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessagePersisted, + Bytes::new(&ctx.env), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &Bytes::new(&ctx.env)); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 1, + msg: String::from_str(&ctx.env, "success"), + }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +fn test_execute_call_with_call_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg_data = encoder::encode_string(&ctx.env, String::from_str(&ctx.env, "rollback")); + + let req_id = 1; + let sequence_no = 1; + let mut req = CSMessageRequest::new( + ctx.network_address, + ctx.dapp.to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessage, + msg_data.clone(), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &msg_data); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 0, + msg: String::from_str(&ctx.env, "unknown error"), + }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +fn test_execute_call_with_rollback_message_type() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg_data = encoder::encode_string(&ctx.env, String::from_str(&ctx.env, "abc")); + + let req_id = 1; + let sequence_no = 1; + let mut req = CSMessageRequest::new( + ctx.network_address, + ctx.dapp.to_string(), + sequence_no, + Vec::new(&ctx.env), + MessageType::CallMessageWithRollback, + msg_data.clone(), + ); + req.hash_data(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &msg_data); + + let call_executed_event = CallExecutedEvent { + reqId: req_id, + code: 1, + msg: String::from_str(&ctx.env, "success"), + }; + let events = vec![&ctx.env, ctx.env.events().all().get(1).unwrap()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("CallExecuted",).into_val(&ctx.env), + call_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // request should be removed + assert!(storage::get_proxy_request(&ctx.env, req_id).is_err()); + }); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #11)")] +fn test_execute_call_data_mismatch() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let req_id = 1; + let req = get_dummy_message_request(&ctx.env); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_proxy_request(&ctx.env, req_id.clone(), &req); + }); + + client.execute_call(&ctx.admin, &req_id, &Bytes::new(&ctx.env)); +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #14)")] fn test_execute_rollback_fail_for_invalid_sequence_number() { @@ -36,3 +214,44 @@ fn test_execute_rollback_fail_not_enabled() { client.execute_rollback(&sequence_no); } + +#[test] +fn test_execute_rollback_success() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = Rollback::new( + ctx.dapp, + ctx.network_address, + get_dummy_sources(&ctx.env), + Bytes::new(&ctx.env), + true, + ); + + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.execute_rollback(&sequence_no); + + let rollback_executed_event = RollbackExecutedEvent { sn: sequence_no }; + let events = vec![&ctx.env, ctx.env.events().all().last_unchecked()]; + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("RollbackExecuted",).into_val(&ctx.env), + rollback_executed_event.into_val(&ctx.env) + ), + ] + ); + + ctx.env.as_contract(&ctx.contract, || { + // rollback should be removed + assert!(storage::get_rollback(&ctx.env, sequence_no).is_err()); + }); +} diff --git a/contracts/soroban/contracts/xcall/src/test/handle_message.rs b/contracts/soroban/contracts/xcall/src/test/handle_message.rs index 286213a1..5788e7d9 100644 --- a/contracts/soroban/contracts/xcall/src/test/handle_message.rs +++ b/contracts/soroban/contracts/xcall/src/test/handle_message.rs @@ -1,11 +1,13 @@ #![cfg(test)] +extern crate std; + use soroban_sdk::{ bytes, - testutils::{Address as _, Events}, - vec, Address, IntoVal, String, + testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation, Events}, + vec, Address, Bytes, BytesN, IntoVal, String, Symbol, }; -use soroban_xcall_lib::messages::msg_type::MessageType; +use soroban_xcall_lib::{messages::msg_type::MessageType, network_address::NetworkAddress}; use crate::{ contract::XcallClient, @@ -15,13 +17,14 @@ use crate::{ message::CSMessage, request::CSMessageRequest, result::{CSMessageResult, CSResponseType}, + rollback::Rollback, }, }; use super::setup::*; #[test] -#[should_panic(expected = "HostError: Error(Contract, #9)")] +#[should_panic(expected = "HostError: Error(Contract, #19)")] fn test_handle_message_fail_for_same_network_id() { let ctx = TestContext::default(); let client = XcallClient::new(&ctx.env, &ctx.contract); @@ -35,7 +38,7 @@ fn test_handle_message_fail_for_same_network_id() { } #[test] -#[should_panic(expected = "HostError: Error(Contract, #9)")] +#[should_panic(expected = "HostError: Error(Contract, #18)")] fn test_handle_message_request_fail_for_invalid_network_id() { let ctx = TestContext::default(); let client = XcallClient::new(&ctx.env, &ctx.contract); @@ -82,6 +85,35 @@ fn test_handle_message_request_fail_for_invalid_source() { ); } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #9)")] +fn test_handle_message_request_fail_for_invalid_source_2() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "cosmos"), + ctx.dapp.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + 1, + vec![&ctx.env], + MessageType::CallMessage, + bytes!(&ctx.env, 0xabc), + ); + let cs_message = CSMessage::from_request(&ctx.env, &request).encode(&ctx.env); + + client.handle_message( + &Address::generate(&ctx.env), + &String::from_str(&ctx.env, "cosmos"), + &cs_message, + ); +} + #[test] fn test_handle_message_request_from_default_connection() { let ctx = TestContext::default(); @@ -142,7 +174,7 @@ fn test_handle_message_request_from_multiple_sources() { assert_eq!(res, ()); let cs_message = CSMessage::decode(&ctx.env, encoded.clone()).unwrap(); - let hash = ctx.env.crypto().keccak256(cs_message.payload()); + let hash: BytesN<32> = ctx.env.crypto().keccak256(cs_message.payload()).into(); ctx.env.as_contract(&ctx.contract, || { let pending_requests = storage::get_pending_request(&ctx.env, hash); @@ -177,6 +209,56 @@ fn test_handle_message_request_from_multiple_sources() { ) } +#[test] +fn test_handle_message_result_from_multiple_protocols() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let protocols = get_dummy_protocols(&ctx.env); + + let sequence_no = 1; + let rollback = Rollback::new( + Address::generate(&ctx.env), + get_dummy_network_address(&ctx.env), + protocols.clone(), + bytes!(&ctx.env, 0xabc), + false, + ); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + let result = CSMessageResult::new( + sequence_no, + CSResponseType::CSResponseSuccess, + Bytes::new(&ctx.env), + ); + let encoded = CSMessage::from_result(&ctx.env, &result).encode(&ctx.env); + + for (i, protocol) in protocols.iter().enumerate() { + let from_nid = String::from_str(&ctx.env, "s"); + let sender = Address::from_string(&protocol); + + let res = client.handle_message(&sender, &from_nid, &encoded); + assert_eq!(res, ()); + + let cs_message = CSMessage::decode(&ctx.env, encoded.clone()).unwrap(); + let hash: BytesN<32> = ctx.env.crypto().keccak256(cs_message.payload()).into(); + + ctx.env.as_contract(&ctx.contract, || { + let pending_responses = storage::get_pending_response(&ctx.env, hash); + + let i = i as u32 + 1; + if i < protocols.len() { + assert_eq!(pending_responses.len(), i) + } else { + assert_eq!(pending_responses.len(), 0) + } + }) + } +} + #[test] #[should_panic(expected = "HostError: Error(Contract, #14)")] fn test_handle_message_result_fail_for_invalid_sequence_no() { @@ -284,6 +366,46 @@ fn test_handle_message_result_should_enable_rollback_when_response_is_failure_fr ) } +#[test] +#[should_panic(expected = "HostError: Error(Contract, #15)")] +fn test_handle_message_result_when_invalid_reply_received() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = get_dummy_rollback(&ctx.env); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + let from = NetworkAddress::new( + &ctx.env, + String::from_str(&ctx.env, "cosmos"), + ctx.dapp.to_string(), + ); + let request = CSMessageRequest::new( + from, + Address::generate(&ctx.env).to_string(), + sequence_no, + get_dummy_protocols(&ctx.env), + MessageType::CallMessage, + bytes!(&ctx.env, 0xabc), + ); + let result = CSMessageResult::new( + sequence_no, + CSResponseType::CSResponseSuccess, + request.encode(&ctx.env), + ); + let cs_message = CSMessage::from_result(&ctx.env, &result).encode(&ctx.env); + + client.handle_message( + &ctx.centralized_connection, + &String::from_str(&ctx.env, "cosmos"), + &cs_message, + ); +} + #[test] fn test_handle_message_result_when_response_is_success_from_dst_chain() { let ctx = TestContext::default(); @@ -318,6 +440,26 @@ fn test_handle_message_result_when_response_is_success_from_dst_chain() { &cs_message, ); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.centralized_connection.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.contract.clone(), + Symbol::new(&ctx.env, "handle_message"), + ( + &ctx.centralized_connection, + String::from_str(&ctx.env, "cosmos"), + cs_message + ) + .into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + ctx.env.as_contract(&ctx.contract, || { // rollback should be removed assert!(storage::get_rollback(&ctx.env, sequence_no).is_err()); @@ -366,5 +508,66 @@ fn test_handle_message_result_when_response_is_success_from_dst_chain() { call_msg_event.into_val(&ctx.env) ) ] - ) + ); +} + +#[test] +fn test_handle_error() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let sequence_no = 1; + let rollback = get_dummy_rollback(&ctx.env); + ctx.env.as_contract(&ctx.contract, || { + storage::store_rollback(&ctx.env, sequence_no, &rollback); + }); + + client.handle_error(&ctx.centralized_connection, &sequence_no); + + let response_msg_event = ResponseMsgEvent { + sn: sequence_no, + code: 0_u32, + }; + let rollback_msg_event = RollbackMsgEvent { sn: sequence_no }; + + let mut events = ctx.env.events().all(); + events.pop_front(); + + assert_eq!( + events, + vec![ + &ctx.env, + ( + client.address.clone(), + ("ResponseMessage",).into_val(&ctx.env), + response_msg_event.into_val(&ctx.env) + ), + ( + client.address.clone(), + ("RollbackMessage",).into_val(&ctx.env), + rollback_msg_event.into_val(&ctx.env) + ) + ] + ); + assert_eq!( + ctx.env.auths(), + std::vec![( + ctx.centralized_connection.clone(), + AuthorizedInvocation { + function: AuthorizedFunction::Contract(( + ctx.contract.clone(), + Symbol::new(&ctx.env, "handle_error"), + (&ctx.centralized_connection, sequence_no,).into_val(&ctx.env) + )), + sub_invocations: std::vec![] + } + )] + ); + + ctx.env.as_contract(&ctx.contract, || { + // rollback should be enabled + let rollback = storage::get_rollback(&ctx.env, sequence_no).unwrap(); + assert_eq!(rollback.enabled, true); + }); } diff --git a/contracts/soroban/contracts/xcall/src/test/send_message.rs b/contracts/soroban/contracts/xcall/src/test/send_message.rs index c46af375..f605046d 100644 --- a/contracts/soroban/contracts/xcall/src/test/send_message.rs +++ b/contracts/soroban/contracts/xcall/src/test/send_message.rs @@ -5,11 +5,11 @@ extern crate std; use soroban_sdk::{ bytes, symbol_short, testutils::{Address as _, AuthorizedFunction, AuthorizedInvocation}, - vec, Address, Bytes, IntoVal, String, + vec, Address, Bytes, IntoVal, String, Vec, }; use soroban_xcall_lib::messages::{ - call_message::CallMessage, call_message_rollback::CallMessageWithRollback, envelope::Envelope, - AnyMessage, + call_message::CallMessage, call_message_persisted::CallMessagePersisted, + call_message_rollback::CallMessageWithRollback, envelope::Envelope, AnyMessage, }; use super::setup::*; @@ -211,6 +211,30 @@ fn test_process_rollback_message_with_empty_rollback_data() { .unwrap(); } +#[test] +fn test_process_persisted_message() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + + let msg = CallMessagePersisted { + data: bytes!(&ctx.env, 0xab), + }; + let message = AnyMessage::CallMessagePersisted(msg); + let envelope = &get_dummy_envelope_msg(&ctx.env, message); + + ctx.env.as_contract(&client.address, || { + let res = send_message::process_message( + &ctx.env, + &ctx.network_address, + 1, + &ctx.contract, + envelope, + ); + assert!(res.is_ok()) + }); +} + #[test] fn test_process_rollback_message() { let ctx = TestContext::default(); @@ -304,6 +328,32 @@ fn test_call_connection_for_call_message() { assert_eq!(connection_balance, fee); } +#[test] +fn test_call_connection_with_empty_sources() { + let ctx = TestContext::default(); + let client = XcallClient::new(&ctx.env, &ctx.contract); + ctx.init_context(&client); + ctx.env.mock_all_auths_allowing_non_root_auth(); + + let sender = Address::generate(&ctx.env); + let need_response = false; + let fee = ctx.get_centralized_connection_fee(need_response); + ctx.mint_native_token(&sender, fee); + + ctx.env.as_contract(&ctx.contract, || { + send_message::call_connection( + &ctx.env, + &sender, + &ctx.nid, + 1, + Vec::new(&ctx.env), + need_response, + Bytes::new(&ctx.env), + ) + .unwrap(); + }) +} + #[test] fn test_calim_protocol_fee() { let ctx = TestContext::default(); diff --git a/contracts/soroban/contracts/xcall/src/test/setup.rs b/contracts/soroban/contracts/xcall/src/test/setup.rs index e4591430..8aa7d49e 100644 --- a/contracts/soroban/contracts/xcall/src/test/setup.rs +++ b/contracts/soroban/contracts/xcall/src/test/setup.rs @@ -10,11 +10,20 @@ use soroban_xcall_lib::{ network_address::NetworkAddress, }; -mod connection { +pub mod connection { soroban_sdk::contractimport!( file = "../../target/wasm32-unknown-unknown/release/centralized_connection.wasm" ); } +pub mod dapp { + soroban_sdk::contractimport!( + file = "../../target/wasm32-unknown-unknown/release/mock_dapp_multi.wasm" + ); +} + +pub mod xcall { + soroban_sdk::contractimport!(file = "../../target/wasm32-unknown-unknown/release/xcall.wasm"); +} use crate::{ contract::{Xcall, XcallClient}, @@ -115,6 +124,7 @@ pub struct TestContext { pub token_admin: Address, pub network_address: NetworkAddress, pub upgrade_authority: Address, + pub dapp: Address, pub centralized_connection: Address, } @@ -122,18 +132,21 @@ impl TestContext { pub fn default() -> Self { let env = Env::default(); let token_admin = Address::generate(&env); + let dapp = env.register_contract_wasm(None, dapp::WASM); let centralized_connection = env.register_contract_wasm(None, connection::WASM); + let native_token_contract = env.register_stellar_asset_contract_v2(token_admin.clone()); Self { contract: env.register_contract(None, Xcall), admin: Address::generate(&env), fee_handler: Address::generate(&env), - native_token: env.register_stellar_asset_contract(token_admin.clone()), + native_token: native_token_contract.address(), nid: String::from_str(&env, "stellar"), network_address: get_dummy_network_address(&env), upgrade_authority: Address::generate(&env), env, token_admin, + dapp, centralized_connection, } } @@ -151,6 +164,8 @@ impl TestContext { self.init_connection_state(); client.set_protocol_fee(&100); client.set_default_connection(&self.nid, &self.centralized_connection); + + self.init_dapp_state(); } pub fn init_connection_state(&self) { @@ -160,6 +175,7 @@ impl TestContext { native_token: self.native_token.clone(), relayer: self.admin.clone(), xcall_address: self.contract.clone(), + upgrade_authority: self.upgrade_authority.clone(), }; connection_client.initialize(&initialize_msg); @@ -168,6 +184,17 @@ impl TestContext { connection_client.set_fee(&self.nid, &message_fee, &response_fee); } + pub fn init_dapp_state(&self) { + let dapp_client = dapp::Client::new(&self.env, &self.dapp); + dapp_client.init(&self.admin, &self.contract.clone(), &self.native_token); + + dapp_client.add_connection( + &self.centralized_connection.to_string(), + &Address::generate(&self.env).to_string(), + &self.nid, + ); + } + pub fn mint_native_token(&self, address: &Address, amount: u128) { let native_token_client = token::StellarAssetClient::new(&self.env, &self.native_token); native_token_client.mint(&address, &(*&amount as i128)); diff --git a/contracts/soroban/contracts/xcall/src/types/request.rs b/contracts/soroban/contracts/xcall/src/types/request.rs index cb91857e..4decd4a0 100644 --- a/contracts/soroban/contracts/xcall/src/types/request.rs +++ b/contracts/soroban/contracts/xcall/src/types/request.rs @@ -61,11 +61,6 @@ impl CSMessageRequest { msg_type == MessageType::CallMessageWithRollback } - pub fn allow_retry(&self) -> bool { - let msg_type: MessageType = (self.msg_type as u8).into(); - msg_type == MessageType::CallMessagePersisted - } - pub fn data(&self) -> &Bytes { &self.data } diff --git a/contracts/soroban/contracts/xcall/src/types/storage_types.rs b/contracts/soroban/contracts/xcall/src/types/storage_types.rs index f24d82e7..fec97168 100644 --- a/contracts/soroban/contracts/xcall/src/types/storage_types.rs +++ b/contracts/soroban/contracts/xcall/src/types/storage_types.rs @@ -15,6 +15,7 @@ pub enum StorageKey { PendingResponses(BytesN<32>), LastReqId, UpgradeAuthority, + Version, } #[contracttype] diff --git a/contracts/sui/libs/sui_rlp/sources/decoder.move b/contracts/sui/libs/sui_rlp/sources/decoder.move index 3fa18a96..acd27d51 100644 --- a/contracts/sui/libs/sui_rlp/sources/decoder.move +++ b/contracts/sui/libs/sui_rlp/sources/decoder.move @@ -20,7 +20,6 @@ module sui_rlp::decoder { } else { let length_len = byte - 0xb7; let length_bytes = utils::slice_vector(encoded, 1, length_len as u64); - //debug::print(&length_bytes); let length = utils::from_bytes_u64(&length_bytes); let data_start = (length_len + 1) as u64; utils::slice_vector(encoded, data_start, length) @@ -151,5 +150,11 @@ module sui_rlp::decoder { bcs::peel_address(&mut bcs) } + public fun decode_bool(vec:&vector):bool{ + let val= *vector::borrow(vec,0); + val==1 + + } + } \ No newline at end of file diff --git a/contracts/sui/libs/sui_rlp/sources/encoder.move b/contracts/sui/libs/sui_rlp/sources/encoder.move index 88c43c3d..5e2b93b1 100644 --- a/contracts/sui/libs/sui_rlp/sources/encoder.move +++ b/contracts/sui/libs/sui_rlp/sources/encoder.move @@ -21,14 +21,11 @@ module sui_rlp::encoder { vector::append(&mut result,*bytes); result }; - //std::debug::print(&encoded); encoded } public fun encode_list(list:&vector>,raw:bool):vector{ - //std::debug::print(&b"ENCODELIST".to_string()); - //std::debug::print(list); let mut result=vector::empty(); let mut encoded_list = vector::empty(); let mut list=*list; @@ -40,7 +37,6 @@ module sui_rlp::encoder { vector::append(&mut result,encode(&vector::pop_back(&mut list))); }else{ vector::append(&mut result,vector::pop_back(&mut list)); - //std::debug::print(&result); }; }; @@ -48,32 +44,22 @@ module sui_rlp::encoder { let total_length = result.length(); let len=vector::length(&result); - if( total_length<= 55){ - encoded_list=encode_length(len,0xc0); - vector::append(&mut encoded_list,result); + if( total_length<= 55){ + encoded_list=encode_length(len,0xc0); + vector::append(&mut encoded_list,result); - } else { - let length_bytes = utils::to_bytes_u64(len); - let prefix = (0xf7 + vector::length(&length_bytes)) as u8; - //std::debug::print(&b"PREFIX".to_string()); - //std::debug::print(&prefix); - vector::push_back(&mut encoded_list, prefix); - //std::debug::print(&encoded_list); - vector::append(&mut encoded_list, length_bytes); - //std::debug::print(&encoded_list); - - vector::append(&mut encoded_list, result); - //std::debug::print(&encoded_list); - - - } + } else { + let length_bytes = utils::to_bytes_u64(len,false); + let prefix = (0xf7 + vector::length(&length_bytes)) as u8; + vector::push_back(&mut encoded_list, prefix); + vector::append(&mut encoded_list, length_bytes); + vector::append(&mut encoded_list, result); + } }else{ vector::push_back(&mut encoded_list,0xc0); }; - //std::debug::print(&b"FINAL_ENCODED_LIST".to_string()); - //std::debug::print(&encoded_list); encoded_list } @@ -83,11 +69,11 @@ module sui_rlp::encoder { let len_u8=(len as u8); vector::push_back(&mut length_info,(offset+len_u8)); }else { - let length_bytes=utils::to_bytes_u64(len); - let length_byte_len=vector::length(&length_bytes); - let length_byte_len=offset+(length_byte_len as u8); - vector::push_back(&mut length_info,length_byte_len); - vector::append(&mut length_info,length_bytes); + let length_bytes=utils::to_bytes_u64(len,false); + let length_byte_len=vector::length(&length_bytes); + let length_byte_len=offset+(length_byte_len as u8); + vector::push_back(&mut length_info,length_byte_len); + vector::append(&mut length_info,length_bytes); }; length_info } @@ -97,16 +83,22 @@ module sui_rlp::encoder { let vec=vector::singleton(num); encode(&vec) + } + + public fun encode_u32(num:u32):vector{ + let vec= utils::to_bytes_u32(num,true); + encode(&vec) + } public fun encode_u64(num:u64):vector{ - let vec= utils::to_bytes_u64(num); + let vec= utils::to_bytes_u64(num,true); encode(&vec) } public fun encode_u128(num:u128):vector{ - let vec= utils::to_bytes_u128(num); + let vec= utils::to_bytes_u128(num,true); encode(&vec) } @@ -132,8 +124,32 @@ module sui_rlp::encoder { let vec= bcs::to_bytes(addr); encode(&vec) } -} + + public fun encode_bool(val:bool):vector{ + if(val==true){ + return vector[1] + }; + vector[0] + } + #[test] + fun test_encode_zero_value(){ + let num=0_u128; + let bytes=encode_u128(num); + assert!(bytes==x"00"); + let num=0_u64; + let bytes=encode_u64(num); + assert!(bytes==x"00"); - + let num=0_u32; + let bytes=encode_u32(num); + assert!(bytes==x"00"); + + let num=0_u8; + let bytes=encode_u8(num); + assert!(bytes==x"00"); + } + + +} \ No newline at end of file diff --git a/contracts/sui/libs/sui_rlp/sources/utils.move b/contracts/sui/libs/sui_rlp/sources/utils.move index f73f0472..af37a07d 100644 --- a/contracts/sui/libs/sui_rlp/sources/utils.move +++ b/contracts/sui/libs/sui_rlp/sources/utils.move @@ -1,50 +1,79 @@ module sui_rlp::utils { - use std::vector::{Self}; - use std::string::{Self,String}; - public fun to_bytes_u32(number: u32): vector { - let mut bytes: vector = vector::empty(); - let mut i:u8=0; - while(i < 4){ - let val =( (number>>(i * 8) & 0xFF) as u8) ; - vector::push_back(&mut bytes,val); - i=i+1; - }; + use std::bcs; + + + // Convert bytes to u32 + public fun from_bytes_u32(bytes: &vector): u32 { + let mut bytes= truncate_zeros(bytes); bytes.reverse(); - bytes + let mut diff= 4-bytes.length(); + while (diff > 0) { + bytes.push_back(0_u8); + diff=diff-1; + }; + sui::bcs::peel_u32(&mut sui::bcs::new(bytes)) } - // Convert bytes to u32 - public fun from_bytes_u32(bytes: &vector): u32 {let mut result = 0; - let mut multiplier = 1; - let length = vector::length(bytes); - - let mut i = length; - while (i > 0) { - i = i - 1; - //std::debug::print(vector::borrow(bytes, i)); - result = result + ((*vector::borrow(bytes, i) as u32) * multiplier); - //std::debug::print(&result); - - if (i > 0) { - multiplier = multiplier * 256 - }; - + + // Convert bytes to u64 + public fun from_bytes_u64(bytes: &vector): u64 { + let mut bytes= truncate_zeros(bytes); + bytes.reverse(); + let mut diff= 8-bytes.length(); + while (diff > 0) { + bytes.push_back(0_u8); + diff=diff-1; }; - result + sui::bcs::peel_u64(&mut sui::bcs::new(bytes)) + } - public fun to_bytes_u64(number: u64): vector { - let mut bytes: vector = vector::empty(); - let mut i:u8=0; - while(i < 8){ - let val =( (number>>(i * 8) & 0xFF) as u8) ; - vector::push_back(&mut bytes,val); - i=i+1; + // Convert bytes to u128 + public fun from_bytes_u128(bytes: &vector): u128 { + let mut bytes= truncate_zeros(bytes); + bytes.reverse(); + let mut diff= 16-bytes.length(); + while (diff > 0) { + bytes.push_back(0_u8); + diff=diff-1; }; + sui::bcs::peel_u128(&mut sui::bcs::new(bytes)) + + } + + public fun to_bytes_u128(number:u128,signed:bool):vector{ + let bytes=bcs::to_bytes(&number); + to_signed_bytes(bytes,signed) + } + + + public fun to_bytes_u64(number:u64,signed:bool):vector{ + let bytes=bcs::to_bytes(&number); + to_signed_bytes(bytes,signed) + } + + public fun to_bytes_u32(number: u32,signed:bool): vector { + let bytes=bcs::to_bytes(&number); + to_signed_bytes(bytes,signed) + } + + fun to_signed_bytes(mut bytes:vector,signed:bool):vector{ bytes.reverse(); - let mut prefix = vector[0]; - prefix.append(truncate_zeros(&bytes)); - prefix + let truncated=truncate_zeros(&bytes); + if(signed==false){ + return truncated + }; + let first_byte=*truncated.borrow(0); + + if (first_byte >= 128) { + let mut prefix = vector[0]; + prefix.append(truncated); + prefix + + }else { + truncated + } + } fun truncate_zeros(bytes: &vector): vector { @@ -60,72 +89,17 @@ module sui_rlp::utils { i = i + 1; }; - - result + if (result.length()==0){ + vector[0] + }else{ + result + } } - // Convert bytes to u64 - public fun from_bytes_u64(bytes: &vector): u64 { - let bytes = truncate_zeros(bytes); - let mut result = 0; - let mut multiplier = 1; - let length = vector::length(&bytes); - - let mut i = length; - while (i > 0) { - i = i - 1; - //std::debug::print(vector::borrow(bytes, i)); - result = result + ((*vector::borrow(&bytes, i) as u64) * (multiplier)); - //std::debug::print(&result); - if (i > 0) { - multiplier = multiplier * 256 - }; - - }; - result - } - - - // Convert u128 to bytes - public fun to_bytes_u128(number: u128): vector { - let mut bytes: vector = vector::empty(); - let mut i:u8=0; - while(i < 16){ - let val = ((number>>(i * 8) & 0xFF) as u8) ; - vector::push_back(&mut bytes,val); - i=i+1; - }; - bytes.reverse(); - let mut prefix = vector[0]; - prefix.append(truncate_zeros(&bytes)); - prefix - } - - // Convert bytes to u128 - public fun from_bytes_u128(bytes: &vector): u128 { - let bytes = truncate_zeros(bytes); - let mut result = 0; - let mut multiplier = 1; - let length = vector::length(&bytes); - - let mut i = length; - while (i > 0) { - i = i - 1; - //std::debug::print(vector::borrow(bytes, i)); - result = result + ((*vector::borrow(&bytes, i) as u128) * multiplier); - //std::debug::print(&result); - - if (i > 0) { - multiplier = multiplier * 256 - }; - - }; - result - } /* end is exclusive in slice*/ - public fun slice_vector(vec: &vector, start: u64, length: u64): vector { + public fun slice_vector(vec: &vector, start: u64, length: u64): vector { let mut result = vector::empty(); let mut i = 0; while (i < length) { @@ -133,49 +107,45 @@ module sui_rlp::utils { vector::push_back(&mut result, value); i = i + 1; }; - //std::debug::print(&result); result } - + } module sui_rlp::utils_test { use sui_rlp::utils::{Self}; - use std::vector::{Self}; - use std::debug; - use sui::bcs::{Self}; - #[test] + #[test] fun test_u32_conversion() { let num= (122 as u32); - let bytes= utils::to_bytes_u32(num); + let bytes= utils::to_bytes_u32(num,true); let converted=utils::from_bytes_u32(&bytes); assert!(num==converted,0x01); - + } #[test] fun test_u64_conversion() { let num= (55000 as u64); - let bytes= utils::to_bytes_u64(num); + let bytes= utils::to_bytes_u64(num,true); let converted=utils::from_bytes_u64(&bytes); std::debug::print(&bytes); std::debug::print(&converted); assert!(num==converted,0x01); - + } #[test] fun test_u128_conversion() { let num= (1222223333 as u128); - let bytes= utils::to_bytes_u128(num); + let bytes= utils::to_bytes_u128(num,true); std::debug::print(&bytes); let converted=utils::from_bytes_u128(&bytes); std::debug::print(&converted); assert!(num==converted,0x01); - + } #[test] @@ -183,16 +153,13 @@ module sui_rlp::utils_test { let data=create_vector(10); let slice= utils::slice_vector(&data,0,3); let expected= create_vector(3); - //debug::print(&expected); - //debug::print(&slice); - //debug::print(&data); assert!(slice==expected,0x01); - + } fun create_vector(n:u8):vector{ - let mut data=vector::empty(); + let mut data=vector::empty(); let mut i=0; while(i < n){ vector::push_back(&mut data,i); diff --git a/contracts/sui/libs/sui_rlp/tests/rlp_tests.move b/contracts/sui/libs/sui_rlp/tests/rlp_tests.move index 2632c074..11ded18d 100644 --- a/contracts/sui/libs/sui_rlp/tests/rlp_tests.move +++ b/contracts/sui/libs/sui_rlp/tests/rlp_tests.move @@ -80,6 +80,80 @@ module sui_rlp::rlp_tests { list } + + #[test] + fun test_encoding_u128(){ + let num:u128=100; + let bytes=x"64"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + //// + /// + let num:u128=200; + let bytes=x"8200c8"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + /// + /// + let num:u128=3000000; + let bytes=x"832dc6c0"; + let encoded= encoder::encode_u128(num); + std::debug::print(&encoded); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=273468273; + let bytes=x"84104ccb71"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=2342312; + let bytes=x"8323bda8"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=1233; + let bytes=x"8204d1"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=412926; + let bytes=x"83064cfe"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=9434628989898; + let bytes=x"860894abb5a3ca"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + let num:u128=92625222222121112; + let bytes=x"88014912261bca8898"; + let encoded= encoder::encode_u128(num); + assert!(encoded==bytes); + let decoded=decoder::decode_u128(&decoder::decode(&encoded)); + assert!(decoded==num); + + + + } + diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_connection.move b/contracts/sui/xcall/sources/cluster_connection/cluster_connection.move new file mode 100644 index 00000000..0f72f53a --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_connection.move @@ -0,0 +1,54 @@ +#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment)] +module xcall::cluster_connection { + use xcall::cluster_state::{Self,State, ReceiptKey,get_state,get_state_mut}; + use std::string::{Self, String}; + use sui::bag::{Bag, Self}; + use sui::event; + use sui::table::{Self, Table}; + use sui::sui::SUI; + use sui::coin::{Self, Coin}; + use sui::balance; + use xcall::xcall_utils::{Self as utils}; + use xcall::xcall_state::{Self,ConnCap}; + use xcall::xcall_state::{Storage as XCallState}; + + const ENotEnoughFee: u64 = 10; + /* ================= events ================= */ + + public struct Message has copy, drop { + to:String, + conn_sn:u128, + msg:vector, + // same package can instantiate multiple connection so this is required + connection_id:String, + } + + public(package) fun connect():State{ + cluster_state::create() + } + + public fun get_fee(states:&Bag,connection_id:String,netId:String,response:bool):u64{ + let state= get_state(states,connection_id); + cluster_state::get_fee(state,&netId,response) + } + + public(package) fun get_next_connection_sn(state:&mut State):u128 { + let sn = cluster_state::get_next_conn_sn(state); + sn + } + // this is safe because only package can call this other xcall will call other deployed instance + public(package) fun send_message(states:&mut Bag,connection_id:String,coin:Coin,to:String,sn:u128,msg:vector,is_response:bool,ctx: &mut TxContext){ + let mut fee = 0; + if(!is_response){ + fee = get_fee(states,connection_id, to, sn>0); + }; + assert!(coin.value() >= fee, ENotEnoughFee); + let balance = coin.into_balance(); + cluster_state::deposit(get_state_mut(states,connection_id),balance); + let conn_sn = get_next_connection_sn(get_state_mut(states,connection_id)); + + event::emit(Message { to, conn_sn, msg, connection_id }); + } + +} + diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move b/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move new file mode 100644 index 00000000..1fab2e8f --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_entry.move @@ -0,0 +1,64 @@ +module xcall::cluster_entry{ + + use xcall::main::{Self as xcall}; + use xcall::xcall_state::{Self,Storage as XCallState,ConnCap}; + use xcall::cluster_state::{Self,get_state,get_state_mut,validate_admin_cap, AdminCap}; + use xcall::xcall_utils::{Self as utils}; + use std::string::{String}; + + entry public fun receive_message(xcall:&mut XCallState,cap:&ConnCap,src_net_id:String,sn:u128,msg:vector,ctx: &mut TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::check_save_receipt(state, src_net_id, sn); + xcall::handle_message(xcall, cap,src_net_id, msg,ctx); + } + + + entry fun claim_fees(xcall:&mut XCallState,cap:&ConnCap,ctx: &mut TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::claim_fees(state,ctx); + } + + entry fun set_fee(xcall:&mut XCallState,cap:&ConnCap,net_id:String,message_fee:u64,response_fee:u64, ctx: &TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::set_fee(state,net_id,message_fee,response_fee,ctx.sender()); + } + + entry fun get_receipt(states: &XCallState,connection_id:String,net_id:String,sn:u128,_ctx: &TxContext):bool{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_receipt(state,net_id,sn) + } + + entry fun get_fee(states: &XCallState,connection_id:String,net_id:String,response:bool,_ctx: &TxContext):u64{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_fee(state,&net_id,response) + } + + entry fun set_validators(xcall:&mut XCallState,cap:&AdminCap,connection_id:String,validator_pubkey:vector>,threshold:u64,_ctx: &mut TxContext){ + validate_admin_cap(cap,connection_id); + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),connection_id); + cluster_state::set_validators(state,validator_pubkey,threshold); + } + + entry fun set_validator_threshold(xcall:&mut XCallState,cap:&AdminCap,connection_id:String,threshold:u64,_ctx: &mut TxContext){ + validate_admin_cap(cap,connection_id); + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),connection_id); + cluster_state::set_validator_threshold(state,threshold); + } + + entry fun get_validators(states: &XCallState,connection_id:String,_ctx: &TxContext):vector>{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_validators(state) + } + + entry fun get_validators_threshold(states: &XCallState,connection_id:String,_ctx: &TxContext):u64{ + let state = get_state(states.get_connection_states(),connection_id); + cluster_state::get_validator_threshold(state) + } + + entry fun recieve_message_with_signatures(xcall:&mut XCallState,cap:&ConnCap,src_net_id:String,sn:u128,msg:vector,signatures:vector>,ctx: &mut TxContext){ + let state=get_state_mut(xcall_state::get_connection_states_mut(xcall),cap.connection_id()); + cluster_state::verify_signatures(state, src_net_id, sn, msg, signatures); + cluster_state::check_save_receipt(state, src_net_id, sn); + xcall::handle_message(xcall, cap,src_net_id, msg,ctx); + } +} \ No newline at end of file diff --git a/contracts/sui/xcall/sources/cluster_connection/cluster_state.move b/contracts/sui/xcall/sources/cluster_connection/cluster_state.move new file mode 100644 index 00000000..07742c14 --- /dev/null +++ b/contracts/sui/xcall/sources/cluster_connection/cluster_state.move @@ -0,0 +1,366 @@ +module xcall::cluster_state { + use std::string::{String}; + use sui::vec_map::{Self, VecMap}; + use xcall::xcall_utils::{Self as utils}; + use sui::coin::{Self}; + use sui::balance::{Self, Balance}; + use sui::sui::SUI; + use sui::bag::{Bag, Self}; + use sui::event; + use sui::address::{Self}; + use sui::hash::{Self}; + use 0x2::ecdsa_k1::{secp256k1_ecrecover, decompress_pubkey}; + + //ERRORS + const VerifiedSignaturesLessThanThreshold: u64 = 100; + const NotEnoughSignatures: u64 = 101; + const InvalidThreshold: u64 = 102; + const ValidatorCountMustBeGreaterThanThreshold: u64 = 105; + const InvalidAdminCap: u64 = 106; + + /* hash algorithm*/ + const KECCAK256: u8 = 0x00; + const SHA256: u8 = 0x01; + + //EVENTS + public struct ValidatorSetAdded has copy, drop { + validators: vector>, + threshold: u64 + } + + public struct AdminCap has key,store { + id: UID, + connection_id: String + } + + public(package) fun create_admin_cap(connection_id:String,ctx: &mut TxContext):AdminCap { + let admin = AdminCap { + id: object::new(ctx), + connection_id: connection_id + }; + admin + } + + public(package) fun validate_admin_cap(self:&AdminCap,connection_id:String){ + assert!(self.connection_id == connection_id, InvalidAdminCap); + } + + public(package) fun get_state_mut(states:&mut Bag,connection_id:String):&mut State { + let state:&mut State=bag::borrow_mut(states,connection_id); + state + } + + public fun get_state(states:&Bag,connection_id:String):&State { + let state:&State=bag::borrow(states,connection_id); + state + } + + + public struct ReceiptKey has copy, drop, store { + conn_sn: u128, + nid: String, + } + + public struct State has store{ + message_fee: VecMap, + response_fee: VecMap, + receipts: VecMap, + conn_sn: u128, + balance: Balance, + validators: vector>, + validators_threshold:u64, + + } + + public(package) fun create(): State { + State { + message_fee: vec_map::empty(), + response_fee: vec_map::empty(), + conn_sn: 0, + receipts: vec_map::empty(), + balance:balance::zero(), + validators: vector::empty(), + validators_threshold:0 + } + } + + public(package) fun get_next_conn_sn(self:&mut State):u128 { + let sn=self.conn_sn+1; + self.conn_sn=sn; + sn + } + + public fun get_fee(self: &State, netId: &String, need_response: bool): u64 { + let fee: u64 = if (need_response == true) { + utils::get_or_default(&self.message_fee, netId, 0) + + utils::get_or_default(&self.response_fee, netId, 0) + } else { + utils::get_or_default(&self.message_fee, netId, 0) + }; + fee + } + + public(package) fun set_fee(self: &mut State, net_id: String, message_fee: u64, response_fee: u64,caller:address) { + if (vec_map::contains(&self.message_fee,&net_id)){ + vec_map::remove(&mut self.message_fee,&net_id); + }; + if (vec_map::contains(&self.response_fee,&net_id)){ + vec_map::remove(&mut self.response_fee,&net_id); + }; + vec_map::insert(&mut self.message_fee, net_id, message_fee); + vec_map::insert(&mut self.response_fee, net_id, response_fee); + } + + public(package) fun check_save_receipt(self: &mut State, net_id: String, sn: u128) { + let receipt_key = ReceiptKey { nid: net_id, conn_sn: sn }; + assert!(!vec_map::contains(&self.receipts, &receipt_key), 100); + vec_map::insert(&mut self.receipts, receipt_key, true); + } + + public(package) fun get_receipt(self: &State, net_id: String, sn: u128): bool { + let receipt_key = ReceiptKey { nid: net_id, conn_sn: sn }; + vec_map::contains(&self.receipts, &receipt_key) + } + + public(package) fun deposit(self:&mut State,balance:Balance){ + balance::join(&mut self.balance,balance); + + } + + public(package) fun claim_fees(self:&mut State,ctx:&mut TxContext){ + let total= self.balance.withdraw_all(); + let coin= coin::from_balance(total,ctx); + transfer::public_transfer(coin,ctx.sender()); + + } + + public(package) fun verify_signatures( + self: &State, + src_net_id: String, + sn: u128, + msg: vector, + signatures: vector> + ) { + let message_hash = utils::get_message_hash(src_net_id, sn, msg); + let threshold = self.get_validator_threshold(); + let validators = self.get_validators(); + + // Ensure the number of signatures meets the threshold + assert!(signatures.length() >= threshold, NotEnoughSignatures); + + let mut i = 0; + let mut unique_verified_pubkey = vector::empty(); + + while (i < signatures.length()) { + let mut signature = *signatures.borrow(i); + let mut recovery_code = signature.pop_back(); + let code = 27 as u8; + + if (recovery_code >= code) { + recovery_code = recovery_code - code; + }; + + signature.push_back(recovery_code); + + let pub_key = decompress_pubkey( + &secp256k1_ecrecover(&signature, &message_hash, KECCAK256) + ); + + if (validators.contains(&pub_key)) { + if (!unique_verified_pubkey.contains(&pub_key)) { + unique_verified_pubkey.push_back(pub_key); + }; + + // Exit early if the threshold is met + if (unique_verified_pubkey.length() >= threshold) { + return; + }; + }; + + i = i + 1; + }; + + // Assert that the unique verified public keys meet the threshold + assert!( + unique_verified_pubkey.length() >= threshold, + VerifiedSignaturesLessThanThreshold + ); + } + + + public(package) fun get_validator_threshold(self:&State):u64{ + self.validators_threshold + } + + public(package) fun set_validator_threshold(self:&mut State,threshold:u64){ + assert!(threshold <= self.validators.length(), InvalidThreshold); + self.validators_threshold=threshold + } + + public(package) fun set_validators(self:&mut State,validator_pub_keys:vector>,threshold:u64){ + self.validators=vector::empty(); + let mut validator_pub_keys = validator_pub_keys; + while (validator_pub_keys.length() > 0) { + let validator = validator_pub_keys.pop_back(); + if(self.validators.contains(&validator)){ + continue + }; + self.validators.push_back(validator); + }; + assert!(self.validators.length() >= threshold, ValidatorCountMustBeGreaterThanThreshold); + self.validators_threshold=threshold; + event::emit(ValidatorSetAdded { validators: self.validators, threshold }); + } + + public(package) fun get_validators(self:&State):vector>{ + self.validators + } + + #[test_only] + public(package) fun create_state():State{ + State { + message_fee: vec_map::empty(), + response_fee: vec_map::empty(), + conn_sn: 0, + receipts: vec_map::empty(), + balance:balance::zero(), + validators: vector::empty(), + validators_threshold:0 + } + } + +} + +#[test_only] +module xcall::cluster_state_tests { + use xcall::cluster_state::{State, get_validators, get_validator_threshold, set_validator_threshold, set_validators, verify_signatures}; + use sui::test_scenario::{Self, Scenario}; + + #[test] + fun test_add_validator(): State { + let mut state = xcall::cluster_state::create_state(); + + let validators = vector[ + x"045b419bdec0d2bbc16ce8ae144ff8e825123fd0cb3e36d0075b6d8de5aab53388ac8fb4c28a8a3843f3073cdaa40c943f74737fc0cea4a95f87778affac738190", + x"04ae36a8bfd8cf6586f34c688528894835f5e7c19d36689bac5460656b613c5eabf1fa982212aa27caece23a2708eb3c8936e132b9fd82c5aee2aa4b06917b5713", + x"04f8c0afc6e4fa149e17fbb0f4d09647971bd016291e9ac66d0a708ec82fc8d5d2ac878d81b7d3f1d37f1013439fc3eb58a4df2f802f931c791c5d81b09034f337", + x"046bc928ee4932efd619ec4c00e0591e932cf2cfef13a59f6027da1c6cba36b35d91238b54aece19825025a9c7cb0bc58a60d5c49e7fc8e5b39fcc4c2193f5feb2" + ]; + + set_validators(&mut state, validators, 2); + + let validators = get_validators(&state); + assert!((validators.length() == 4)); + + + set_validator_threshold(&mut state, 3); + assert!(get_validator_threshold(&state)==3); + + state + } + + + #[test] + fun test_set_get_threshold(): State { + let mut state = test_add_validator(); + set_validator_threshold(&mut state, 1); + assert!(get_validator_threshold(&state)==1); + state + } + + #[test] + #[expected_failure(abort_code = 102)] + fun test_set_threshold_too_high(): State { + let mut state = test_set_get_threshold(); + set_validator_threshold(&mut state, 5); + state + } + + #[test] + fun test_get_fee(): State { + let mut state = xcall::cluster_state::create_state(); + xcall::cluster_state::set_fee(&mut state, b"net1".to_string(), 100, 50, @0xadd); + xcall::cluster_state::set_fee(&mut state, b"net2".to_string(), 200, 100, @0xadd); + + let fee_without_response = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), false); + assert!(fee_without_response == 100); + + let fee_with_response = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), true); + assert!(fee_with_response == 150); + + state + } + + #[test] + fun test_update_fee(): State { + let mut state = xcall::cluster_state::create_state(); + xcall::cluster_state::set_fee(&mut state, b"net1".to_string(), 200, 100, @0xadd); + + let fee = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), true); + assert!(fee == 300); // 200 message_fee + 100 response_fee + + // Update the fee + xcall::cluster_state::set_fee(&mut state, b"net1".to_string(), 300, 200, @0xadd); + let updated_fee = xcall::cluster_state::get_fee(&state, &b"net1".to_string(), true); + assert!(updated_fee == 500); // 300 message_fee + 200 response_fee + + state + } + + #[test] + fun test_receipts(): State { + let mut state = xcall::cluster_state::create_state(); + let sn = xcall::cluster_state::get_next_conn_sn(&mut state); + + xcall::cluster_state::check_save_receipt(&mut state, b"net1".to_string(), sn); + let receipt_exists = xcall::cluster_state::get_receipt(&state, b"net1".to_string(), sn); + assert!(receipt_exists == true); + + state + } + + #[test] + #[expected_failure(abort_code = 101)] + fun test_verify_signatures_less_than_threshold(): State { + let state = test_add_validator(); + let msg: vector = x"68656c6c6f"; + let src_net_id = b"0x2.icon".to_string(); + let conn_sn = 456456; + + let signatures = vector[x"23f731c7fb3553337394233055cbb9ec05abdd1df7cbbec3d0dacced58bf5b4b30576ca14bea93ea4186e920f99f2b9f56d30175b0a7356322f3a5d75de843b81b", + ]; + + xcall::cluster_state::verify_signatures(&state,src_net_id, conn_sn, msg, signatures); + state + } + + #[test] + #[expected_failure(abort_code = 100)] + fun test_verify_signatures_invalid(): State { + let state = test_set_get_threshold(); + let msg: vector = x"68656c6c6f"; + let src_net_id = b"0x2.icon".to_string(); + let conn_sn = 456456; + + let signatures = vector[x"23f731c7fb3553337394233055cbb9ec05abdd1df7cbbec3d0dacced58bf5b4b30576ca14bea93ea4186e920f99f2b9f56d30175b0a7356322f3a5d75de843b81c", + ]; + + xcall::cluster_state::verify_signatures(&state,src_net_id, conn_sn, msg, signatures); + state + } + + #[test] + fun test_verify_signatures(): State { + let state = test_set_get_threshold(); + let msg: vector = x"68656c6c6f"; + let src_net_id = b"0x2.icon".to_string(); + let conn_sn = 456456; + + let signatures = vector[x"23f731c7fb3553337394233055cbb9ec05abdd1df7cbbec3d0dacced58bf5b4b30576ca14bea93ea4186e920f99f2b9f56d30175b0a7356322f3a5d75de843b81b", + ]; + + xcall::cluster_state::verify_signatures(&state,src_net_id, conn_sn, msg, signatures); + state + } + +} \ No newline at end of file diff --git a/contracts/sui/xcall/sources/connections.move b/contracts/sui/xcall/sources/connections.move index 170e2d0f..044ffddc 100644 --- a/contracts/sui/xcall/sources/connections.move +++ b/contracts/sui/xcall/sources/connections.move @@ -1,24 +1,33 @@ -#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment)] +#[allow(unused_field,unused_use,unused_const,unused_mut_parameter,unused_variable,unused_assignment,implicit_const_copy)] module xcall::connections{ use std::string::{Self, String}; use sui::bag::{Bag, Self}; use xcall::centralized_connection::{Self}; - use xcall::centralized_state::{Self,State}; + use xcall::cluster_connection::{Self}; + use xcall::cluster_state::{Self,State,create_admin_cap}; use xcall::xcall_state::{ConnCap}; use sui::coin::{Self,Coin}; use sui::balance::{Self, Balance}; use sui::sui::SUI; + const EConnectionNotFound:u64=0; const ConnCentralized:vector =b"centralized"; + const ConnCluster:vector =b"cluster"; public(package) fun register(states:&mut Bag,connection_id:String,ctx:&mut TxContext){ - if (get_connection_type(&connection_id).bytes()==ConnCentralized){ + if (get_connection_type(&connection_id).as_bytes()==ConnCentralized){ let state= centralized_connection::connect(); bag::add(states, connection_id, state); + }else + if (get_connection_type(&connection_id).as_bytes()==ConnCluster){ + let state= cluster_connection::connect(); + let admin_cap=cluster_state::create_admin_cap(connection_id,ctx); + transfer::public_transfer(admin_cap, ctx.sender()); + bag::add(states, connection_id, state); }else{ abort EConnectionNotFound } @@ -27,26 +36,33 @@ module xcall::connections{ public(package) fun get_fee(states:&Bag,connection_id:String,netId:String,response:bool):u64{ - if (get_connection_type(&connection_id).bytes()==ConnCentralized){ + if (get_connection_type(&connection_id).as_bytes()==ConnCentralized){ let fee= centralized_connection::get_fee(states,connection_id,netId,response); fee + }else + if (get_connection_type(&connection_id).as_bytes()==ConnCluster){ + let fee= cluster_connection::get_fee(states,connection_id,netId,response); + fee }else{ abort EConnectionNotFound - } + } } public(package) fun send_message(states:&mut Bag,connection_id:String,coin:Coin,netId:String,sn:u128,msg:vector,is_response:bool,ctx:&mut TxContext){ - if (get_connection_type(&connection_id).bytes()==ConnCentralized){ + if (get_connection_type(&connection_id).as_bytes()==ConnCentralized){ centralized_connection::send_message(states,connection_id,coin,netId,sn,msg,is_response,ctx); + }else + if (get_connection_type(&connection_id).as_bytes()==ConnCluster){ + cluster_connection::send_message(states,connection_id,coin,netId,sn,msg,is_response,ctx); }else{ abort EConnectionNotFound - } + } } fun get_connection_type(connection_id:&String):String{ let separator_index=string::index_of(connection_id,&string::utf8(b"-")); - let connType=string::sub_string(connection_id,0,separator_index); + let connType=string::substring(connection_id,0,separator_index); connType } diff --git a/contracts/sui/xcall/sources/types/message_result.move b/contracts/sui/xcall/sources/types/message_result.move index 0f53df48..9130009c 100644 --- a/contracts/sui/xcall/sources/types/message_result.move +++ b/contracts/sui/xcall/sources/types/message_result.move @@ -87,7 +87,7 @@ module xcall::message_result_tests { let msg= message_result::create(1,message_result::success(),vector::empty()); let encoded= message_result::encode(&msg); std::debug::print(&encoded); - assert!(encoded==x"c58200010180",0x01); + assert!(encoded==x"c3010180",0x01); let decoded=message_result::decode(&encoded); assert!(decoded==msg,0x01); @@ -99,7 +99,7 @@ module xcall::message_result_tests { let msg= message_result::create(2,message_result::failure(),vector::empty()); let encoded= message_result::encode(&msg); std::debug::print(&encoded); - assert!(encoded==x"c58200020080",0x01); + assert!(encoded==x"c3020080",0x01); let decoded=message_result::decode(&encoded); assert!(decoded==msg,0x01); diff --git a/contracts/sui/xcall/sources/utils.move b/contracts/sui/xcall/sources/utils.move index 66e0ff9d..489a4481 100644 --- a/contracts/sui/xcall/sources/utils.move +++ b/contracts/sui/xcall/sources/utils.move @@ -8,7 +8,9 @@ module xcall::xcall_utils { use std::string::{Self, String}; use sui::hex; use sui::bcs::{Self}; - + use sui_rlp::encoder::{Self}; + use sui::hash::{Self}; + public fun are_equal(a1:&vector,a2:&vector): bool { if(length(a1)!=length(a2)){ @@ -24,6 +26,14 @@ module xcall::xcall_utils { } } + public fun get_message_hash(src_net_id: String, sn: u128, msg: vector): vector { + let mut list=vector::empty>(); + vector::push_back(&mut list, encoder::encode_string(&src_net_id)); + vector::push_back(&mut list, encoder::encode_u128(sn)); + vector::push_back(&mut list, encoder::encode(&msg)); + let encoded=encoder::encode_list(&list,false); + encoded + } public fun id_to_hex_string(id:&ID): String { let bytes = object::id_to_bytes(id);